Browse Source

coins: Add LTC MWEB wallet

pull/40/head
tecnovert 6 months ago
parent
commit
38fa498b0b
No known key found for this signature in database GPG Key ID: 8ED6D8750C4E3F93
  1. 2
      basicswap/__init__.py
  2. 130
      basicswap/basicswap.py
  3. 1
      basicswap/chainparams.py
  4. 14
      basicswap/http_server.py
  5. 149
      basicswap/interface/btc.py
  6. 10
      basicswap/interface/dash.py
  7. 71
      basicswap/interface/firo.py
  8. 106
      basicswap/interface/ltc.py
  9. 73
      basicswap/interface/nav.py
  10. 2
      basicswap/interface/nmc.py
  11. 150
      basicswap/interface/part.py
  12. 31
      basicswap/interface/pivx.py
  13. 91
      basicswap/interface/xmr.py
  14. 24
      basicswap/js_server.py
  15. 12
      basicswap/templates/style.html
  16. 96
      basicswap/templates/wallet.html
  17. 29
      basicswap/templates/wallets.html
  18. 32
      basicswap/ui/page_wallet.py
  19. 7
      basicswap/ui/util.py
  20. 24
      bin/basicswap_prepare.py
  21. 8
      doc/release-notes.md
  22. 2
      tests/basicswap/extended/test_dash.py
  23. 2
      tests/basicswap/extended/test_nav.py
  24. 2
      tests/basicswap/extended/test_pivx.py
  25. 213
      tests/basicswap/test_btc_xmr.py
  26. 146
      tests/basicswap/test_ltc_xmr.py
  27. 28
      tests/basicswap/test_partblind_xmr.py
  28. 11
      tests/basicswap/test_run.py
  29. 40
      tests/basicswap/test_xmr.py

2
basicswap/__init__.py

@ -1,3 +1,3 @@
name = "basicswap"
__version__ = "0.12.3"
__version__ = "0.12.4"

130
basicswap/basicswap.py

@ -256,6 +256,7 @@ class BasicSwap(BaseApp):
self._is_locked = None
# TODO: Set dynamically
self.balance_only_coins = (Coins.LTC_MWEB, )
self.scriptless_coins = (Coins.XMR, Coins.PART_ANON, Coins.FIRO)
self.adaptor_swap_only_coins = self.scriptless_coins + (Coins.PART_BLIND, )
self.coins_without_segwit = (Coins.PIVX, Coins.DASH, Coins.NMC)
@ -480,6 +481,9 @@ class BasicSwap(BaseApp):
self.coin_clients[Coins.PART_ANON] = self.coin_clients[coin]
self.coin_clients[Coins.PART_BLIND] = self.coin_clients[coin]
if coin == Coins.LTC:
self.coin_clients[Coins.LTC_MWEB] = self.coin_clients[coin]
if self.coin_clients[coin]['connection_type'] == 'rpc':
if coin == Coins.XMR:
if chain_client_settings.get('automatically_select_daemon', False):
@ -510,8 +514,8 @@ class BasicSwap(BaseApp):
if current_daemon_url in remote_daemon_urls:
self.log.info(f'Trying last used url {rpchost}:{rpcport}.')
try:
rpc_cb2 = make_xmr_rpc2_func(rpcport, daemon_login, rpchost)
test = rpc_cb2('get_height', timeout=20)['height']
rpc2 = make_xmr_rpc2_func(rpcport, daemon_login, rpchost)
test = rpc2('get_height', timeout=20)['height']
return True
except Exception as e:
self.log.warning(f'Failed to set XMR remote daemon to {rpchost}:{rpcport}, {e}')
@ -520,8 +524,8 @@ class BasicSwap(BaseApp):
self.log.info(f'Trying url {url}.')
try:
rpchost, rpcport = url.rsplit(':', 1)
rpc_cb2 = make_xmr_rpc2_func(rpcport, daemon_login, rpchost)
test = rpc_cb2('get_height', timeout=20)['height']
rpc2 = make_xmr_rpc2_func(rpcport, daemon_login, rpchost)
test = rpc2('get_height', timeout=20)['height']
coin_settings['rpchost'] = rpchost
coin_settings['rpcport'] = rpcport
data = {
@ -544,6 +548,9 @@ class BasicSwap(BaseApp):
if coin == Coins.PART_BLIND:
use_coinid = Coins.PART
interface_ind = 'interface_blind'
if coin == Coins.LTC_MWEB:
use_coinid = Coins.LTC
interface_ind = 'interface_mweb'
if use_coinid not in self.coin_clients:
raise ValueError('Unknown coinid {}'.format(int(coin)))
@ -558,6 +565,9 @@ class BasicSwap(BaseApp):
if coin == Coins.PART_BLIND:
use_coinid = Coins.PART
interface_ind = 'interface_blind'
if coin == Coins.LTC_MWEB:
use_coinid = Coins.LTC
interface_ind = 'interface_mweb'
if use_coinid not in self.coin_clients:
raise ValueError('Unknown coinid {}'.format(int(coin)))
@ -573,13 +583,18 @@ class BasicSwap(BaseApp):
def createInterface(self, coin):
if coin == Coins.PART:
return PARTInterface(self.coin_clients[coin], self.chain, self)
interface = PARTInterface(self.coin_clients[coin], self.chain, self)
self.coin_clients[coin]['interface_anon'] = PARTInterfaceAnon(self.coin_clients[coin], self.chain, self)
self.coin_clients[coin]['interface_blind'] = PARTInterfaceBlind(self.coin_clients[coin], self.chain, self)
return interface
elif coin == Coins.BTC:
from .interface.btc import BTCInterface
return BTCInterface(self.coin_clients[coin], self.chain, self)
elif coin == Coins.LTC:
from .interface.ltc import LTCInterface
return LTCInterface(self.coin_clients[coin], self.chain, self)
from .interface.ltc import LTCInterface, LTCInterfaceMWEB
interface = LTCInterface(self.coin_clients[coin], self.chain, self)
self.coin_clients[coin]['interface_mweb'] = LTCInterfaceMWEB(self.coin_clients[coin], self.chain, self)
return interface
elif coin == Coins.NMC:
from .interface.nmc import NMCInterface
return NMCInterface(self.coin_clients[coin], self.chain, self)
@ -662,9 +677,6 @@ class BasicSwap(BaseApp):
def createCoinInterface(self, coin):
if self.coin_clients[coin]['connection_type'] == 'rpc':
self.coin_clients[coin]['interface'] = self.createInterface(coin)
if coin == Coins.PART:
self.coin_clients[coin]['interface_anon'] = PARTInterfaceAnon(self.coin_clients[coin], self.chain, self)
self.coin_clients[coin]['interface_blind'] = PARTInterfaceBlind(self.coin_clients[coin], self.chain, self)
elif self.coin_clients[coin]['connection_type'] == 'passthrough':
self.coin_clients[coin]['interface'] = self.createPassthroughInterface(coin)
@ -685,13 +697,11 @@ class BasicSwap(BaseApp):
self.createCoinInterface(c)
if self.coin_clients[c]['connection_type'] == 'rpc':
if c in (Coins.BTC, ):
self.waitForDaemonRPC(c, with_wallet=False)
if len(self.callcoinrpc(c, 'listwallets')) >= 1:
self.waitForDaemonRPC(c)
else:
self.waitForDaemonRPC(c)
ci = self.ci(c)
self.waitForDaemonRPC(c, with_wallet=False)
if c not in (Coins.XMR,) and ci.checkWallets() >= 1:
self.waitForDaemonRPC(c)
core_version = ci.getDaemonVersion()
self.log.info('%s Core version %d', ci.coin_name(), core_version)
self.coin_clients[c]['core_version'] = core_version
@ -721,6 +731,11 @@ class BasicSwap(BaseApp):
except Exception as e:
self.log.warning('Can\'t open XMR wallet, could be locked.')
continue
elif c == Coins.LTC:
ci_mweb = self.ci(Coins.LTC_MWEB)
is_encrypted, _ = self.getLockedState()
if not is_encrypted and not ci_mweb.has_mweb_wallet():
ci_mweb.init_wallet()
self.checkWalletSeed(c)
@ -850,6 +865,16 @@ class BasicSwap(BaseApp):
if self.coin_clients[c]['connection_type'] == 'rpc':
yield c
def getListOfWalletCoins(self):
coins_list = copy.deepcopy(self.activeCoins())
# Always unlock Particl first
if Coins.PART in coins_list:
coins_list.pop(Coins.PART)
coins_list = [Coins.PART,] + coins_list
if Coins.LTC in coins_list:
coins_list.append(Coins.LTC_MWEB)
return coins_list
def changeWalletPasswords(self, old_password: str, new_password: str, coin=None) -> None:
# Only the main wallet password is changed for monero, avoid issues by preventing until active swaps are complete
if len(self.swaps_in_progress) > 0:
@ -861,8 +886,10 @@ class BasicSwap(BaseApp):
if len(new_password) < 4:
raise ValueError('New password is too short')
coins_list = self.getListOfWalletCoins()
# Unlock wallets to ensure they all have the same password.
for c in self.activeCoins():
for c in coins_list:
if coin and c != coin:
continue
ci = self.ci(c)
@ -871,7 +898,7 @@ class BasicSwap(BaseApp):
except Exception as e:
raise ValueError('Failed to unlock {}'.format(ci.coin_name()))
for c in self.activeCoins():
for c in coins_list:
if coin and c != coin:
continue
self.ci(c).changeWalletPassword(old_password, new_password)
@ -882,7 +909,7 @@ class BasicSwap(BaseApp):
def unlockWallets(self, password: str, coin=None) -> None:
self._read_zmq_queue = False
for c in self.activeCoins():
for c in self.getListOfWalletCoins():
if coin and c != coin:
continue
self.ci(c).unlockWallet(password)
@ -896,7 +923,7 @@ class BasicSwap(BaseApp):
self._read_zmq_queue = False
self.swaps_in_progress.clear()
for c in self.activeCoins():
for c in self.getListOfWalletCoins():
if coin and c != coin:
continue
self.ci(c).lockWallet()
@ -923,7 +950,6 @@ class BasicSwap(BaseApp):
root_key = self.getWalletKey(coin_type, 1)
root_hash = ci.getSeedHash(root_key)
try:
ci.initialiseWallet(root_key)
except Exception as e:
@ -931,6 +957,8 @@ class BasicSwap(BaseApp):
self.log.error('initialiseWallet failed: {}'.format(str(e)))
if raise_errors:
raise e
if self.debug:
self.log.error(traceback.format_exc())
return
try:
@ -1167,6 +1195,11 @@ class BasicSwap(BaseApp):
return coin_from in self.scriptless_coins + self.coins_without_segwit
def validateSwapType(self, coin_from, coin_to, swap_type):
for coin in (coin_from, coin_to):
if coin in self.balance_only_coins:
raise ValueError('Invalid coin: {}'.format(coin.name))
if swap_type == SwapTypes.XMR_SWAP:
reverse_bid: bool = self.is_reverse_ads_bid(coin_from)
itx_coin = coin_to if reverse_bid else coin_from
@ -1812,6 +1845,14 @@ class BasicSwap(BaseApp):
self.log.debug('In txn: {}'.format(txid))
return txid
def withdrawLTC(self, type_from, value, addr_to, subfee):
ci = self.ci(Coins.LTC)
self.log.info('withdrawLTC %s %s to %s %s', value, ci.ticker(), addr_to, ' subfee' if subfee else '')
txid = ci.withdrawCoin(value, type_from, addr_to, subfee)
self.log.debug('In txn: {}'.format(txid))
return txid
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 '')
@ -1827,7 +1868,7 @@ class BasicSwap(BaseApp):
def cacheNewAddressForCoin(self, coin_type):
self.log.debug('cacheNewAddressForCoin %s', coin_type)
key_str = 'receive_addr_' + chainparams[coin_type]['name']
key_str = 'receive_addr_' + self.ci(coin_type).coin_name().lower()
addr = self.getReceiveAddressForCoin(coin_type)
self.setStringKV(key_str, addr)
return addr
@ -1864,7 +1905,7 @@ class BasicSwap(BaseApp):
if expect_seedid is None:
self.log.warning('Can\'t find expected wallet seed id for coin {}'.format(ci.coin_name()))
return False
if c == Coins.BTC and len(ci.rpc_callback('listwallets')) < 1:
if c == Coins.BTC and len(ci.rpc('listwallets')) < 1:
self.log.warning('Missing wallet for coin {}'.format(ci.coin_name()))
return False
if ci.checkExpectedSeed(expect_seedid):
@ -1893,7 +1934,8 @@ class BasicSwap(BaseApp):
self.log.debug('getCachedAddressForCoin %s', coin_type)
# TODO: auto refresh after used
key_str = 'receive_addr_' + chainparams[coin_type]['name']
ci = self.ci(coin_type)
key_str = 'receive_addr_' + ci.coin_name().lower()
session = self.openSession()
try:
try:
@ -1908,9 +1950,22 @@ class BasicSwap(BaseApp):
self.closeSession(session)
return addr
def cacheNewStealthAddressForCoin(self, coin_type):
self.log.debug('cacheNewStealthAddressForCoin %s', coin_type)
if coin_type == Coins.LTC_MWEB:
coin_type = Coins.LTC
ci = self.ci(coin_type)
key_str = 'stealth_addr_' + ci.coin_name().lower()
addr = ci.getNewStealthAddress()
self.setStringKV(key_str, addr)
return addr
def getCachedStealthAddressForCoin(self, coin_type):
self.log.debug('getCachedStealthAddressForCoin %s', coin_type)
if coin_type == Coins.LTC_MWEB:
coin_type = Coins.LTC
ci = self.ci(coin_type)
key_str = 'stealth_addr_' + ci.coin_name().lower()
session = self.openSession()
@ -2559,7 +2614,7 @@ class BasicSwap(BaseApp):
address_out = self.getReceiveAddressFromPool(coin_from, offer_id, TxTypes.XMR_SWAP_A_LOCK)
if coin_from == Coins.PART_BLIND:
addrinfo = ci_from.rpc_callback('getaddressinfo', [address_out])
addrinfo = ci_from.rpc('getaddressinfo', [address_out])
msg_buf.dest_af = bytes.fromhex(addrinfo['pubkey'])
else:
msg_buf.dest_af = ci_from.decodeAddress(address_out)
@ -2870,7 +2925,7 @@ class BasicSwap(BaseApp):
address_out = self.getReceiveAddressFromPool(coin_from, bid.offer_id, TxTypes.XMR_SWAP_A_LOCK)
if coin_from == Coins.PART_BLIND:
addrinfo = ci_from.rpc_callback('getaddressinfo', [address_out])
addrinfo = ci_from.rpc('getaddressinfo', [address_out])
xmr_swap.dest_af = bytes.fromhex(addrinfo['pubkey'])
else:
xmr_swap.dest_af = ci_from.decodeAddress(address_out)
@ -3339,7 +3394,8 @@ class BasicSwap(BaseApp):
bid.participate_tx.chain_height = participate_txn_height
# Start checking for spends of participate_txn before fully confirmed
self.log.debug('Watching %s chain for spend of output %s %d', chainparams[coin_type]['name'], txid_hex, vout)
ci = self.ci(coin_type)
self.log.debug('Watching %s chain for spend of output %s %d', ci.coin_name().lower(), txid_hex, vout)
self.addWatchedOutput(coin_type, bid_id, txid_hex, vout, BidStates.SWAP_PARTICIPATING)
def participateTxnConfirmed(self, bid_id: bytes, bid, offer) -> None:
@ -4151,7 +4207,7 @@ class BasicSwap(BaseApp):
last_height_checked += 1
if c['last_height_checked'] != last_height_checked:
c['last_height_checked'] = last_height_checked
self.setIntKV('last_height_checked_' + chainparams[coin_type]['name'], last_height_checked)
self.setIntKV('last_height_checked_' + ci.coin_name().lower(), last_height_checked)
def expireMessages(self) -> None:
if self._is_locked is True:
@ -6413,6 +6469,11 @@ class BasicSwap(BaseApp):
rv['main_address'] = self.getCachedMainWalletAddress(ci)
elif coin == Coins.NAV:
rv['immature'] = walletinfo['immature_balance']
elif coin == Coins.LTC:
rv['mweb_address'] = self.getCachedStealthAddressForCoin(Coins.LTC_MWEB)
rv['mweb_balance'] = walletinfo['mweb_balance']
rv['mweb_pending'] = walletinfo['mweb_unconfirmed'] + walletinfo['mweb_immature']
rv['mweb_pending'] = walletinfo['mweb_unconfirmed'] + walletinfo['mweb_immature']
return rv
except Exception as e:
@ -6505,10 +6566,17 @@ class BasicSwap(BaseApp):
wallet_data['lastupdated'] = row[3]
wallet_data['updating'] = self._updating_wallets_info.get(coin_id, False)
# Ensure the latest deposit address is displayed
q = session.execute('SELECT value FROM kv_string WHERE key = "receive_addr_{}"'.format(chainparams[coin_id]['name']))
# Ensure the latest addresses are displayed
q = session.execute('SELECT key, value FROM kv_string WHERE key = "receive_addr_{0}" OR key = "stealth_addr_{0}"'.format(chainparams[coin_id]['name']))
for row in q:
wallet_data['deposit_address'] = row[0]
if row[0].startswith('stealth'):
if coin_id == Coins.LTC:
wallet_data['mweb_address'] = row[1]
else:
wallet_data['stealth_address'] = row[1]
else:
wallet_data['deposit_address'] = row[1]
if coin_id in rv:
rv[coin_id].update(wallet_data)

1
basicswap/chainparams.py

@ -32,6 +32,7 @@ class Coins(IntEnum):
DASH = 12
FIRO = 13
NAV = 14
LTC_MWEB = 15
chainparams = {

14
basicswap/http_server.py

@ -280,6 +280,8 @@ class HttpHandler(BaseHTTPRequestHandler):
coin_id = int(get_data_entry(form_data, 'coin_type'))
if coin_id in (-2, -3, -4):
coin_type = Coins(Coins.XMR)
elif coin_id in (-5,):
coin_type = Coins(Coins.LTC)
else:
coin_type = Coins(coin_id)
except Exception:
@ -295,20 +297,23 @@ class HttpHandler(BaseHTTPRequestHandler):
method = arr[0]
params = json.loads(arr[1]) if len(arr) > 1 else []
if coin_id == -4:
rv = ci.rpc_wallet_cb(method, params)
rv = ci.rpc_wallet(method, params)
elif coin_id == -3:
rv = ci.rpc_cb(method, params)
rv = ci.rpc(method, params)
elif coin_id == -2:
if params == []:
params = None
rv = ci.rpc_cb2(method, params)
rv = ci.rpc2(method, params)
else:
raise ValueError('Unknown XMR RPC variant')
result = json.dumps(rv, indent=4)
else:
if call_type == 'http':
method, params = parse_cmd(cmd, type_map)
rv = swap_client.ci(coin_type).rpc_callback(method, params)
if coin_id == -5:
rv = swap_client.ci(coin_type).rpc_wallet_mweb(method, params)
else:
rv = swap_client.ci(coin_type).rpc_wallet(method, params)
if not isinstance(rv, str):
rv = json.dumps(rv, indent=4)
result = cmd + '\n' + rv
@ -323,6 +328,7 @@ class HttpHandler(BaseHTTPRequestHandler):
coins = listAvailableCoins(swap_client, with_variants=False)
coins = [c for c in coins if c[0] != Coins.XMR]
coins.append((-5, 'Litecoin MWEB Wallet'))
coins.append((-2, 'Monero'))
coins.append((-3, 'Monero JSON'))
coins.append((-4, 'Monero Wallet'))

149
basicswap/interface/btc.py

@ -195,7 +195,9 @@ class BTCInterface(CoinInterface):
self._rpc_host = coin_settings.get('rpchost', '127.0.0.1')
self._rpcport = coin_settings['rpcport']
self._rpcauth = coin_settings['rpcauth']
self.rpc_callback = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
self.rpc = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
self._rpc_wallet = 'wallet.dat'
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet)
self.blocks_confirmed = coin_settings['blocks_confirmed']
self.setConfTarget(coin_settings['conf_target'])
self._use_segwit = coin_settings['use_segwit']
@ -204,6 +206,23 @@ class BTCInterface(CoinInterface):
self._log = self._sc.log if self._sc and self._sc.log else logging
self._expect_seedid_hex = None
def checkWallets(self) -> int:
wallets = self.rpc('listwallets')
# 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()))
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))
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet)
break
return len(wallets)
def using_segwit(self) -> bool:
# Using btc native segwit
return self._use_segwit
@ -239,34 +258,34 @@ class BTCInterface(CoinInterface):
self._conf_target = new_conf_target
def testDaemonRPC(self, with_wallet=True) -> None:
self.rpc_callback('getwalletinfo' if with_wallet else 'getblockchaininfo')
self.rpc_wallet('getwalletinfo' if with_wallet else 'getblockchaininfo')
def getDaemonVersion(self):
return self.rpc_callback('getnetworkinfo')['version']
return self.rpc('getnetworkinfo')['version']
def getBlockchainInfo(self):
return self.rpc_callback('getblockchaininfo')
return self.rpc('getblockchaininfo')
def getChainHeight(self) -> int:
return self.rpc_callback('getblockcount')
return self.rpc('getblockcount')
def getMempoolTx(self, txid):
return self.rpc_callback('getrawtransaction', [txid.hex()])
return self.rpc('getrawtransaction', [txid.hex()])
def getBlockHeaderFromHeight(self, height):
block_hash = self.rpc_callback('getblockhash', [height])
return self.rpc_callback('getblockheader', [block_hash])
block_hash = self.rpc('getblockhash', [height])
return self.rpc('getblockheader', [block_hash])
def getBlockHeader(self, block_hash):
return self.rpc_callback('getblockheader', [block_hash])
return self.rpc('getblockheader', [block_hash])
def getBlockHeaderAt(self, time: int, block_after=False):
blockchaininfo = self.rpc_callback('getblockchaininfo')
last_block_header = self.rpc_callback('getblockheader', [blockchaininfo['bestblockhash']])
blockchaininfo = self.rpc('getblockchaininfo')
last_block_header = self.rpc('getblockheader', [blockchaininfo['bestblockhash']])
max_tries = 5000
for i in range(max_tries):
prev_block_header = self.rpc_callback('getblock', [last_block_header['previousblockhash']])
prev_block_header = self.rpc('getblock', [last_block_header['previousblockhash']])
if prev_block_header['time'] <= time:
return last_block_header if block_after else prev_block_header
@ -275,11 +294,10 @@ class BTCInterface(CoinInterface):
def initialiseWallet(self, key_bytes: bytes) -> None:
key_wif = self.encodeKey(key_bytes)
self.rpc_callback('sethdseed', [True, key_wif])
self.rpc_wallet('sethdseed', [True, key_wif])
def getWalletInfo(self):
rv = self.rpc_callback('getwalletinfo')
rv = self.rpc_wallet('getwalletinfo')
rv['encrypted'] = 'unlocked_until' in rv
rv['locked'] = rv.get('unlocked_until', 1) <= 0
return rv
@ -288,7 +306,7 @@ class BTCInterface(CoinInterface):
return self._restore_height
def getWalletRestoreHeight(self) -> int:
start_time = self.rpc_callback('getwalletinfo')['keypoololdest']
start_time = self.rpc_wallet('getwalletinfo')['keypoololdest']
blockchaininfo = self.getBlockchainInfo()
best_block = blockchaininfo['bestblockhash']
@ -312,7 +330,7 @@ class BTCInterface(CoinInterface):
raise ValueError('{} wallet restore height not found.'.format(self.coin_name()))
def getWalletSeedID(self) -> str:
wi = self.rpc_callback('getwalletinfo')
wi = self.rpc_wallet('getwalletinfo')
return 'Not found' if 'hdseedid' not in wi else wi['hdseedid']
def checkExpectedSeed(self, expect_seedid) -> bool:
@ -323,11 +341,11 @@ class BTCInterface(CoinInterface):
args = [label]
if use_segwit:
args.append('bech32')
return self.rpc_callback('getnewaddress', args)
return self.rpc_wallet('getnewaddress', args)
def isValidAddress(self, address: str) -> bool:
try:
rv = self.rpc_callback('validateaddress', [address])
rv = self.rpc_wallet('validateaddress', [address])
if rv['isvalid'] is True:
return True
except Exception as ex:
@ -347,13 +365,13 @@ class BTCInterface(CoinInterface):
return False
def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
addr_info = self.rpc_callback('getaddressinfo', [address])
addr_info = self.rpc_wallet('getaddressinfo', [address])
if not or_watch_only:
return addr_info['ismine']
return addr_info['ismine'] or addr_info['iswatchonly']
def checkAddressMine(self, address: str) -> None:
addr_info = self.rpc_callback('getaddressinfo', [address])
addr_info = self.rpc_wallet('getaddressinfo', [address])
ensure(addr_info['ismine'], 'ismine is false')
if self.sc._restrict_unknown_seed_wallets:
ensure(addr_info['hdseedid'] == self._expect_seedid_hex, 'unexpected seedid')
@ -369,16 +387,16 @@ class BTCInterface(CoinInterface):
def try_get_fee_rate(self, conf_target):
try:
fee_rate = self.rpc_callback('estimatesmartfee', [conf_target])['feerate']
fee_rate = self.rpc_wallet('estimatesmartfee', [conf_target])['feerate']
assert (fee_rate > 0.0), 'Non positive feerate'
return fee_rate, 'estimatesmartfee'
except Exception:
try:
fee_rate = self.rpc_callback('getwalletinfo')['paytxfee']
fee_rate = self.rpc_wallet('getwalletinfo')['paytxfee']
assert (fee_rate > 0.0), 'Non positive feerate'
return fee_rate, 'paytxfee'
except Exception:
return self.rpc_callback('getnetworkinfo')['relayfee'], 'relayfee'
return self.rpc('getnetworkinfo')['relayfee'], 'relayfee'
fee_rate, rate_src = try_get_fee_rate(self, conf_target)
if min_relay_fee and min_relay_fee > fee_rate:
@ -734,7 +752,7 @@ class BTCInterface(CoinInterface):
add_bytes = 0
add_witness_bytes = getCompactSizeLen(len(tx.vin))
for pi in tx.vin:
ptx = self.rpc_callback('getrawtransaction', [i2h(pi.prevout.hash), True])
ptx = self.rpc('getrawtransaction', [i2h(pi.prevout.hash), True])
prevout = ptx['vout'][pi.prevout.n]
inputs_value += make_int(prevout['value'])
@ -942,13 +960,13 @@ class BTCInterface(CoinInterface):
'lockUnspents': True,
'feeRate': feerate_str,
}
rv = self.rpc_callback('fundrawtransaction', [tx.hex(), options])
rv = self.rpc_wallet('fundrawtransaction', [tx.hex(), options])
return bytes.fromhex(rv['hex'])
def listInputs(self, tx_bytes):
tx = self.loadTx(tx_bytes)
all_locked = self.rpc_callback('listlockunspent')
all_locked = self.rpc_wallet('listlockunspent')
inputs = []
for pi in tx.vin:
txid_hex = i2h(pi.prevout.hash)
@ -962,19 +980,19 @@ class BTCInterface(CoinInterface):
inputs = []
for pi in tx.vin:
inputs.append({'txid': i2h(pi.prevout.hash), 'vout': pi.prevout.n})
self.rpc_callback('lockunspent', [True, inputs])
self.rpc('lockunspent', [True, inputs])
def signTxWithWallet(self, tx: bytes) -> bytes:
rv = self.rpc_callback('signrawtransactionwithwallet', [tx.hex()])
rv = self.rpc_wallet('signrawtransactionwithwallet', [tx.hex()])
return bytes.fromhex(rv['hex'])
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
key_wif = self.encodeKey(key)
rv = self.rpc_callback('signrawtransactionwithkey', [tx.hex(), [key_wif, ]])
rv = self.rpc('signrawtransactionwithkey', [tx.hex(), [key_wif, ]])
return bytes.fromhex(rv['hex'])
def publishTx(self, tx: bytes):
return self.rpc_callback('sendrawtransaction', [tx.hex()])
return self.rpc('sendrawtransaction', [tx.hex()])
def encodeTx(self, tx) -> bytes:
return tx.serialize()
@ -1018,18 +1036,18 @@ class BTCInterface(CoinInterface):
return self.getScriptForPubkeyHash(self.getPubkeyHash(K))
def scanTxOutset(self, dest):
return self.rpc_callback('scantxoutset', ['start', ['raw({})'.format(dest.hex())]])
return self.rpc('scantxoutset', ['start', ['raw({})'.format(dest.hex())]])
def getTransaction(self, txid: bytes):
try:
return bytes.fromhex(self.rpc_callback('getrawtransaction', [txid.hex()]))
return bytes.fromhex(self.rpc('getrawtransaction', [txid.hex()]))
except Exception as ex:
# TODO: filter errors
return None
def getWalletTransaction(self, txid: bytes):
try:
return bytes.fromhex(self.rpc_callback('gettransaction', [txid.hex()]))
return bytes.fromhex(self.rpc('gettransaction', [txid.hex()]))
except Exception as ex:
# TODO: filter errors
return None
@ -1115,7 +1133,7 @@ class BTCInterface(CoinInterface):
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int) -> bytes:
self._log.info('spendBLockTx %s:\n', chain_b_lock_txid.hex())
wtx = self.rpc_callback('gettransaction', [chain_b_lock_txid.hex(), ])
wtx = self.rpc_wallet('gettransaction', [chain_b_lock_txid.hex(), ])
lock_tx = self.loadTx(bytes.fromhex(wtx['hex']))
Kbs = self.getPubkey(kbs)
@ -1144,10 +1162,10 @@ class BTCInterface(CoinInterface):
return bytes.fromhex(self.publishTx(b_lock_spend_tx))
def importWatchOnlyAddress(self, address: str, label: str):
self.rpc_callback('importaddress', [address, label, False])
self.rpc_wallet('importaddress', [address, label, False])
def isWatchOnlyAddress(self, address: str):
addr_info = self.rpc_callback('getaddressinfo', [address])
addr_info = self.rpc_wallet('getaddressinfo', [address])
return addr_info['iswatchonly']
def getSCLockScriptAddress(self, lock_script):
@ -1161,11 +1179,11 @@ class BTCInterface(CoinInterface):
self.importWatchOnlyAddress(dest_address, 'bid')
self._log.info('Imported watch-only addr: {}'.format(dest_address))
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), rescan_from))
self.rpc_callback('rescanblockchain', [rescan_from])
self.rpc_wallet('rescanblockchain', [rescan_from])
return_txid = True if txid is None else False
if txid is None:
txns = self.rpc_callback('listunspent', [0, 9999999, [dest_address, ]])
txns = self.rpc_wallet('listunspent', [0, 9999999, [dest_address, ]])
for tx in txns:
if self.make_int(tx['amount']) == bid_amount:
@ -1176,11 +1194,11 @@ class BTCInterface(CoinInterface):
return None
try:
tx = self.rpc_callback('gettransaction', [txid.hex()])
tx = self.rpc_wallet('gettransaction', [txid.hex()])
block_height = 0
if 'blockhash' in tx:
block_header = self.rpc_callback('getblockheader', [tx['blockhash']])
block_header = self.rpc('getblockheader', [tx['blockhash']])
block_height = block_header['height']
rv = {
@ -1192,7 +1210,7 @@ class BTCInterface(CoinInterface):
return None
if find_index:
tx_obj = self.rpc_callback('decoderawtransaction', [tx['hex']])
tx_obj = self.rpc('decoderawtransaction', [tx['hex']])
rv['index'] = find_vout_for_address_from_txobj(tx_obj, dest_address)
if return_txid:
@ -1202,7 +1220,7 @@ class BTCInterface(CoinInterface):
def getOutput(self, txid, dest_script, expect_value, xmr_swap=None):
# TODO: Use getrawtransaction if txindex is active
utxos = self.rpc_callback('scantxoutset', ['start', ['raw({})'.format(dest_script.hex())]])
utxos = self.rpc('scantxoutset', ['start', ['raw({})'.format(dest_script.hex())]])
if 'height' in utxos: # chain_height not returned by v18 codebase
chain_height = utxos['height']
else:
@ -1225,7 +1243,7 @@ class BTCInterface(CoinInterface):
def withdrawCoin(self, value, addr_to, subfee):
params = [addr_to, value, '', '', subfee, True, self._conf_target]
return self.rpc_callback('sendtoaddress', params)
return self.rpc_wallet('sendtoaddress', params)
def signCompact(self, k, message):
message_hash = hashlib.sha256(bytes(message, 'utf-8')).digest()
@ -1318,10 +1336,10 @@ class BTCInterface(CoinInterface):
return length
def describeTx(self, tx_hex: str):
return self.rpc_callback('decoderawtransaction', [tx_hex])
return self.rpc('decoderawtransaction', [tx_hex])
def getSpendableBalance(self) -> int:
return self.make_int(self.rpc_callback('getbalances')['mine']['trusted'])
return self.make_int(self.rpc_wallet('getbalances')['mine']['trusted'])
def createUTXO(self, value_sats: int):
# Create a new address and send value_sats to it
@ -1334,7 +1352,7 @@ class BTCInterface(CoinInterface):
return self.withdrawCoin(self.format_amount(value_sats), address, False), address
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
options = {
'lockUnspents': lock_unspents,
@ -1342,18 +1360,18 @@ class BTCInterface(CoinInterface):
}
if sub_fee:
options['subtractFeeFromOutputs'] = [0,]
return self.rpc_callback('fundrawtransaction', [txn, options])['hex']
return self.rpc_wallet('fundrawtransaction', [txn, options])['hex']
def createRawSignedTransaction(self, addr_to, amount) -> str:
txn_funded = self.createRawFundedTransaction(addr_to, amount)
return self.rpc_callback('signrawtransactionwithwallet', [txn_funded])['hex']
return self.rpc_wallet('signrawtransactionwithwallet', [txn_funded])['hex']
def getBlockWithTxns(self, block_hash: str):
return self.rpc_callback('getblock', [block_hash, 2])
return self.rpc('getblock', [block_hash, 2])
def getUnspentsByAddr(self):
unspent_addr = dict()
unspent = self.rpc_callback('listunspent')
unspent = self.rpc_wallet('listunspent')
for u in unspent:
if u['spendable'] is not True:
continue
@ -1361,11 +1379,11 @@ class BTCInterface(CoinInterface):
return unspent_addr
def getUTXOBalance(self, address: str):
num_blocks = self.rpc_callback('getblockcount')
num_blocks = self.rpc('getblockcount')
sum_unspent = 0
self._log.debug('[rm] scantxoutset start') # scantxoutset is slow
ro = self.rpc_callback('scantxoutset', ['start', ['addr({})'.format(address)]]) # TODO: Use combo(address) where possible
ro = self.rpc('scantxoutset', ['start', ['addr({})'.format(address)]]) # TODO: Use combo(address) where possible
self._log.debug('[rm] scantxoutset end')
for o in ro['unspents']:
sum_unspent += self.make_int(o['amount'])
@ -1391,7 +1409,7 @@ class BTCInterface(CoinInterface):
sign_for_addr = self.pkh_to_address(pkh)
self._log.debug('sign_for_addr converted %s', sign_for_addr)
signature = self.rpc_callback('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + extra_commit_bytes.hex()])
signature = self.rpc_wallet('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + extra_commit_bytes.hex()])
prove_utxos = [] # TODO: Send specific utxos
return (sign_for_addr, signature, prove_utxos)
@ -1416,17 +1434,17 @@ class BTCInterface(CoinInterface):
return self.getUTXOBalance(address)
def isWalletEncrypted(self) -> bool:
wallet_info = self.rpc_callback('getwalletinfo')
wallet_info = self.rpc_wallet('getwalletinfo')
return 'unlocked_until' in wallet_info
def isWalletLocked(self) -> bool:
wallet_info = self.rpc_callback('getwalletinfo')
wallet_info = self.rpc_wallet('getwalletinfo')
if 'unlocked_until' in wallet_info and wallet_info['unlocked_until'] <= 0:
return True
return False
def isWalletEncryptedLocked(self):
wallet_info = self.rpc_callback('getwalletinfo')
wallet_info = self.rpc_wallet('getwalletinfo')
encrypted = 'unlocked_until' in wallet_info
locked = encrypted and wallet_info['unlocked_until'] <= 0
return encrypted, locked
@ -1436,8 +1454,8 @@ class BTCInterface(CoinInterface):
if old_password == '':
if self.isWalletEncrypted():
raise ValueError('Old password must be set')
return self.rpc_callback('encryptwallet', [new_password])
self.rpc_callback('walletpassphrasechange', [old_password, new_password])
return self.rpc_wallet('encryptwallet', [new_password])
self.rpc_wallet('walletpassphrasechange', [old_password, new_password])
def unlockWallet(self, password: str):
if password == '':
@ -1447,21 +1465,20 @@ class BTCInterface(CoinInterface):
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_callback('listwallets')
wallets = self.rpc('listwallets')
if len(wallets) < 1:
self._log.info('Creating wallet.dat for {}.'.format(self.coin_name()))
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
self.rpc_callback('createwallet', ['wallet.dat', False, True, '', False, False])
self.rpc_callback('encryptwallet', [password])
self.rpc('createwallet', ['wallet.dat', False, True, '', False, False])
self.rpc_wallet('encryptwallet', [password])
# Max timeout value, ~3 years
self.rpc_callback('walletpassphrase', [password, 100000000])
self.rpc_wallet('walletpassphrase', [password, 100000000])
self._sc.checkWalletSeed(self.coin_type())
def lockWallet(self):
self._log.info('lockWallet - {}'.format(self.ticker()))
self.rpc_callback('walletlock')
self.rpc_wallet('walletlock')
def get_p2sh_script_pubkey(self, script: bytearray) -> bytearray:
script_hash = hash160(script)
@ -1474,7 +1491,7 @@ class BTCInterface(CoinInterface):
def findTxnByHash(self, txid_hex: str):
# Only works for wallet txns
try:
rv = self.rpc_callback('gettransaction', [txid_hex])
rv = self.rpc_wallet('gettransaction', [txid_hex])
except Exception as ex:
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
return None

10
basicswap/interface/dash.py

@ -32,7 +32,7 @@ class DASHInterface(BTCInterface):
words = self.seedToMnemonic(key)
mnemonic_passphrase = ''
self.rpc_callback('upgradetohd', [words, mnemonic_passphrase, self._wallet_passphrase])
self.rpc_wallet('upgradetohd', [words, mnemonic_passphrase, self._wallet_passphrase])
self._have_checked_seed = False
if self._wallet_passphrase != '':
self.unlockWallet(self._wallet_passphrase)
@ -42,7 +42,7 @@ class DASHInterface(BTCInterface):
def checkExpectedSeed(self, key_hash: str):
try:
rv = self.rpc_callback('dumphdinfo')
rv = self.rpc_wallet('dumphdinfo')
entropy = Mnemonic('english').to_entropy(rv['mnemonic'].split(' '))
entropy_hash = self.getAddressHashFromKey(entropy)[::-1].hex()
self._have_checked_seed = True
@ -53,10 +53,10 @@ class DASHInterface(BTCInterface):
def withdrawCoin(self, value, addr_to, subfee):
params = [addr_to, value, '', '', subfee, False, False, self._conf_target]
return self.rpc_callback('sendtoaddress', params)
return self.rpc_wallet('sendtoaddress', params)
def getSpendableBalance(self) -> int:
return self.make_int(self.rpc_callback('getwalletinfo')['balance'])
return self.make_int(self.rpc_wallet('getwalletinfo')['balance'])
def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray:
# Return P2PKH
@ -72,7 +72,7 @@ class DASHInterface(BTCInterface):
def findTxnByHash(self, txid_hex: str):
# Only works for wallet txns
try:
rv = self.rpc_callback('gettransaction', [txid_hex])
rv = self.rpc_wallet('gettransaction', [txid_hex])
except Exception as ex:
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
return None

71
basicswap/interface/firo.py

@ -13,6 +13,7 @@ from basicswap.util import (
i2b,
ensure,
)
from basicswap.rpc import make_rpc_func
from basicswap.util.crypto import hash160
from basicswap.util.address import decodeAddress
from basicswap.chainparams import Coins
@ -36,6 +37,14 @@ class FIROInterface(BTCInterface):
def coin_type():
return Coins.FIRO
def __init__(self, coin_settings, network, swap_client=None):
super(FIROInterface, self).__init__(coin_settings, network, swap_client)
# No multiwallet support
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
def checkWallets(self) -> int:
return 1
def getExchangeName(self, exchange_name):
return 'zcoin'
@ -44,9 +53,9 @@ class FIROInterface(BTCInterface):
pass
def getNewAddress(self, use_segwit, label='swap_receive'):
return self.rpc_callback('getnewaddress', [label])
# addr_plain = self.rpc_callback('getnewaddress', [label])
# return self.rpc_callback('addwitnessaddress', [addr_plain])
return self.rpc('getnewaddress', [label])
# addr_plain = self.rpc('getnewaddress', [label])
# return self.rpc('addwitnessaddress', [addr_plain])
def decodeAddress(self, address):
return decodeAddress(address)[1:]
@ -58,11 +67,11 @@ class FIROInterface(BTCInterface):
raise ValueError('TODO')
def isWatchOnlyAddress(self, address):
addr_info = self.rpc_callback('validateaddress', [address])
addr_info = self.rpc('validateaddress', [address])
return addr_info['iswatchonly']
def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
addr_info = self.rpc_callback('validateaddress', [address])
addr_info = self.rpc('validateaddress', [address])
if not or_watch_only:
return addr_info['ismine']
return addr_info['ismine'] or addr_info['iswatchonly']
@ -73,8 +82,8 @@ class FIROInterface(BTCInterface):
if not self.isAddressMine(address, or_watch_only=True):
# Expects P2WSH nested in BIP16_P2SH
ro = self.rpc_callback('importaddress', [lock_tx_dest.hex(), 'bid lock', False, True])
addr_info = self.rpc_callback('validateaddress', [address])
ro = self.rpc('importaddress', [lock_tx_dest.hex(), 'bid lock', False, True])
addr_info = self.rpc('validateaddress', [address])
return address
@ -89,7 +98,7 @@ class FIROInterface(BTCInterface):
return_txid = True if txid is None else False
if txid is None:
txns = self.rpc_callback('listunspent', [0, 9999999, [dest_address, ]])
txns = self.rpc('listunspent', [0, 9999999, [dest_address, ]])
for tx in txns:
if self.make_int(tx['amount']) == bid_amount:
@ -100,11 +109,11 @@ class FIROInterface(BTCInterface):
return None
try:
tx = self.rpc_callback('gettransaction', [txid.hex()])
tx = self.rpc('gettransaction', [txid.hex()])
block_height = 0
if 'blockhash' in tx:
block_header = self.rpc_callback('getblockheader', [tx['blockhash']])
block_header = self.rpc('getblockheader', [tx['blockhash']])
block_height = block_header['height']
rv = {
@ -116,7 +125,7 @@ class FIROInterface(BTCInterface):
return None
if find_index:
tx_obj = self.rpc_callback('decoderawtransaction', [tx['hex']])
tx_obj = self.rpc('decoderawtransaction', [tx['hex']])
rv['index'] = find_vout_for_address_from_txobj(tx_obj, dest_address)
if return_txid:
@ -135,11 +144,11 @@ class FIROInterface(BTCInterface):
return self.fundTx(tx_bytes, feerate)
def signTxWithWallet(self, tx):
rv = self.rpc_callback('signrawtransaction', [tx.hex()])
rv = self.rpc('signrawtransaction', [tx.hex()])
return bytes.fromhex(rv['hex'])
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}')
options = {
@ -148,11 +157,11 @@ class FIROInterface(BTCInterface):
}
if sub_fee:
options['subtractFeeFromOutputs'] = [0,]
return self.rpc_callback('fundrawtransaction', [txn, options])['hex']
return self.rpc('fundrawtransaction', [txn, options])['hex']
def createRawSignedTransaction(self, addr_to, amount) -> str:
txn_funded = self.createRawFundedTransaction(addr_to, amount)
return self.rpc_callback('signrawtransaction', [txn_funded])['hex']
return self.rpc('signrawtransaction', [txn_funded])['hex']
def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray:
# Return P2PKH
@ -180,13 +189,13 @@ class FIROInterface(BTCInterface):
def withdrawCoin(self, value, addr_to, subfee):
params = [addr_to, value, '', '', subfee]
return self.rpc_callback('sendtoaddress', params)
return self.rpc('sendtoaddress', params)
def getWalletSeedID(self):
return self.rpc_callback('getwalletinfo')['hdmasterkeyid']
return self.rpc('getwalletinfo')['hdmasterkeyid']
def getSpendableBalance(self) -> int:
return self.make_int(self.rpc_callback('getwalletinfo')['balance'])
return self.make_int(self.rpc('getwalletinfo')['balance'])
def getBLockSpendTxFee(self, tx, fee_rate: int) -> int:
add_bytes = 107
@ -197,13 +206,13 @@ class FIROInterface(BTCInterface):
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
key_wif = self.encodeKey(key)
rv = self.rpc_callback('signrawtransaction', [tx.hex(), [], [key_wif, ]])
rv = self.rpc('signrawtransaction', [tx.hex(), [], [key_wif, ]])
return bytes.fromhex(rv['hex'])
def findTxnByHash(self, txid_hex: str):
# Only works for wallet txns
try:
rv = self.rpc_callback('gettransaction', [txid_hex])
rv = self.rpc('gettransaction', [txid_hex])
except Exception as ex:
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
return None
@ -216,7 +225,7 @@ class FIROInterface(BTCInterface):
# TODO: Lock unspent and use same output/s to fund bid
unspents_by_addr = dict()
unspents = self.rpc_callback('listunspent')
unspents = self.rpc('listunspent')
for u in unspents:
if u['spendable'] is not True:
continue
@ -276,7 +285,7 @@ class FIROInterface(BTCInterface):
sign_for_addr = self.pkh_to_address(pkh)
self._log.debug('sign_for_addr converted %s', sign_for_addr)
signature = self.rpc_callback('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex()])
signature = self.rpc('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex()])
return (sign_for_addr, signature, prove_utxos)
@ -296,7 +305,7 @@ class FIROInterface(BTCInterface):
sum_value: int = 0
for outpoint in utxos:
txout = self.rpc_callback('gettxout', [outpoint[0].hex(), outpoint[1]])
txout = self.rpc('gettxout', [outpoint[0].hex(), outpoint[1]])
sum_value += self.make_int(txout['value'])
return sum_value
@ -307,15 +316,15 @@ class FIROInterface(BTCInterface):
chain_blocks: int = self.getChainHeight()
current_height: int = chain_blocks
block_hash = self.rpc_callback('getblockhash', [current_height])
block_hash = self.rpc('getblockhash', [current_height])
script_hash: bytes = self.decodeAddress(addr_find)
find_scriptPubKey = self.getDestForScriptHash(script_hash)
while current_height > height_start:
block_hash = self.rpc_callback('getblockhash', [current_height])
block_hash = self.rpc('getblockhash', [current_height])
block = self.rpc_callback('getblock', [block_hash, False])
block = self.rpc('getblock', [block_hash, False])
decoded_block = CBlock()
decoded_block = FromHex(decoded_block, block)
for tx in decoded_block.vtx:
@ -325,22 +334,22 @@ class FIROInterface(BTCInterface):
txid = i2b(tx.sha256)
self._log.info('Found output to addr: {} in tx {} in block {}'.format(addr_find, txid.hex(), block_hash))
self._log.info('rescanblockchain hack invalidateblock {}'.format(block_hash))
self.rpc_callback('invalidateblock', [block_hash])
self.rpc_callback('reconsiderblock', [block_hash])
self.rpc('invalidateblock', [block_hash])
self.rpc('reconsiderblock', [block_hash])
return
current_height -= 1
def getBlockWithTxns(self, block_hash):
# TODO: Bypass decoderawtransaction and getblockheader
block = self.rpc_callback('getblock', [block_hash, False])
block_header = self.rpc_callback('getblockheader', [block_hash])
block = self.rpc('getblock', [block_hash, False])
block_header = self.rpc('getblockheader', [block_hash])
decoded_block = CBlock()
decoded_block = FromHex(decoded_block, block)
tx_rv = []
for tx in decoded_block.vtx:
tx_hex = tx.serialize_with_witness().hex()
tx_dec = self.rpc_callback('decoderawtransaction', [tx_hex])
tx_dec = self.rpc('decoderawtransaction', [tx_hex])
if 'hex' not in tx_dec:
tx_dec['hex'] = tx_hex

106
basicswap/interface/ltc.py

@ -1,15 +1,117 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2020 tecnovert
# Copyright (c) 2020-2023 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
from .btc import BTCInterface
from basicswap.chainparams import Coins
from basicswap.rpc import make_rpc_func
from basicswap.chainparams import Coins, chainparams
class LTCInterface(BTCInterface):
@staticmethod
def coin_type():
return Coins.LTC
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 = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet_mweb)
def getNewMwebAddress(self, use_segwit=False, label='swap_receive') -> str:
return self.rpc_wallet_mweb('getnewaddress', [label, 'mweb'])
def getNewStealthAddress(self, label=''):
return self.getNewMwebAddress(False, label)
def withdrawCoin(self, value, type_from: str, addr_to: str, subfee: bool) -> str:
params = [addr_to, value, '', '', subfee, True, self._conf_target]
if type_from == 'mweb':
return self.rpc_wallet_mweb('sendtoaddress', params)
return self.rpc_wallet('sendtoaddress', params)
def getWalletInfo(self):
rv = self.rpc_wallet('getwalletinfo')
rv['encrypted'] = 'unlocked_until' in rv
rv['locked'] = rv.get('unlocked_until', 1) <= 0
mweb_info = self.rpc_wallet_mweb('getwalletinfo')
rv['mweb_balance'] = mweb_info['balance']
rv['mweb_unconfirmed'] = mweb_info['unconfirmed_balance']
rv['mweb_immature'] = mweb_info['immature_balance']
return rv
class LTCInterfaceMWEB(LTCInterface):
@staticmethod
def coin_type():
return Coins.LTC_MWEB
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 = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet)
def chainparams(self):
return chainparams[Coins.LTC]
def chainparams_network(self):
return chainparams[Coins.LTC][self._network]
def coin_name(self) -> str:
coin_chainparams = chainparams[Coins.LTC]
if coin_chainparams.get('use_ticker_as_name', False):
return coin_chainparams['ticker'] + ' MWEB'
return coin_chainparams['name'].capitalize() + ' MWEB'
def ticker(self) -> str:
ticker = chainparams[Coins.LTC]['ticker']
if self._network == 'testnet':
ticker = 't' + ticker
elif self._network == 'regtest':
ticker = 'rt' + ticker
return ticker + '_MWEB'
def getNewAddress(self, use_segwit=False, label='swap_receive') -> str:
return self.getNewMwebAddress()
def has_mweb_wallet(self) -> bool:
return 'mweb' in self.rpc('listwallets')
def init_wallet(self, password=None):
# If system is encrypted mweb wallet will be created at first unlock
self._log.info('init_wallet - {}'.format(self.ticker()))
self._log.info('Creating mweb wallet for {}.'.format(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])
if password is not None:
# Max timeout value, ~3 years
self.rpc_wallet('walletpassphrase', [password, 100000000])
if self.getWalletSeedID() == 'Not found':
self._sc.initialiseWallet(self.coin_type())
# Workaround to trigger mweb_spk_man->LoadMWEBKeychain()
self.rpc('unloadwallet', ['mweb'])
self.rpc('loadwallet', ['mweb'])
if password is not None:
self.rpc_wallet('walletpassphrase', [password, 100000000])
self.rpc_wallet('keypoolrefill')
def unlockWallet(self, password: str):
if password == '':
return
self._log.info('unlockWallet - {}'.format(self.ticker()))
if not self.has_mweb_wallet():
self.init_wallet(password)
else:
# Max timeout value, ~3 years
self.rpc_wallet('walletpassphrase', [password, 100000000])
self._sc.checkWalletSeed(self.coin_type())

73
basicswap/interface/nav.py

@ -14,6 +14,7 @@ from coincurve.keys import (
PrivateKey,
)
from .btc import BTCInterface, find_vout_for_address_from_txobj, findOutput
from basicswap.rpc import make_rpc_func
from basicswap.chainparams import Coins
from basicswap.interface.contrib.nav_test_framework.mininode import (
CTxIn,
@ -63,6 +64,14 @@ class NAVInterface(BTCInterface):
def txoType():
return CTxOut
def __init__(self, coin_settings, network, swap_client=None):
super(NAVInterface, self).__init__(coin_settings, network, swap_client)
# No multiwallet support
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
def checkWallets(self) -> int:
return 1
def use_p2shp2wsh(self) -> bool:
# p2sh-p2wsh
return True
@ -75,24 +84,24 @@ class NAVInterface(BTCInterface):
pass
def getWalletSeedID(self):
return self.rpc_callback('getwalletinfo')['hdmasterkeyid']
return self.rpc('getwalletinfo')['hdmasterkeyid']
def withdrawCoin(self, value, addr_to: str, subfee: bool):
strdzeel = ''
params = [addr_to, value, '', '', strdzeel, subfee]
return self.rpc_callback('sendtoaddress', params)
return self.rpc('sendtoaddress', params)
def getSpendableBalance(self) -> int:
return self.make_int(self.rpc_callback('getwalletinfo')['balance'])
return self.make_int(self.rpc('getwalletinfo')['balance'])
def signTxWithWallet(self, tx: bytes) -> bytes:
rv = self.rpc_callback('signrawtransaction', [tx.hex()])
rv = self.rpc('signrawtransaction', [tx.hex()])
return bytes.fromhex(rv['hex'])
def checkExpectedSeed(self, key_hash: str):
try:
rv = self.rpc_callback('dumpmnemonic')
rv = self.rpc('dumpmnemonic')
entropy = Mnemonic('english').to_entropy(rv.split(' '))
entropy_hash = self.getAddressHashFromKey(entropy)[::-1].hex()
@ -155,7 +164,7 @@ class NAVInterface(BTCInterface):
# TODO: Lock unspent and use same output/s to fund bid
unspents_by_addr = dict()
unspents = self.rpc_callback('listunspent')
unspents = self.rpc('listunspent')
for u in unspents:
if u['spendable'] is not True:
continue
@ -211,13 +220,13 @@ class NAVInterface(BTCInterface):
if self.using_segwit(): # TODO: Use isSegwitAddress when scantxoutset can use combo
# 'Address does not refer to key' for non p2pkh
addr_info = self.rpc_callback('validateaddress', [addr, ])
addr_info = self.rpc('validateaddress', [addr, ])
if 'isscript' in addr_info and addr_info['isscript'] and 'hex' in addr_info:
pkh = bytes.fromhex(addr_info['hex'])[2:]
sign_for_addr = self.pkh_to_address(pkh)
self._log.debug('sign_for_addr converted %s', sign_for_addr)
signature = self.rpc_callback('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex()])
signature = self.rpc('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex()])
return (sign_for_addr, signature, prove_utxos)
@ -237,13 +246,13 @@ class NAVInterface(BTCInterface):
sum_value: int = 0
for outpoint in utxos:
txout = self.rpc_callback('gettxout', [outpoint[0].hex(), outpoint[1]])
txout = self.rpc('gettxout', [outpoint[0].hex(), outpoint[1]])
sum_value += self.make_int(txout['value'])
return sum_value
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}')
if sub_fee:
@ -254,17 +263,17 @@ class NAVInterface(BTCInterface):
return self.fundTx(txn, fee_rate, lock_unspents).hex()
def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
addr_info = self.rpc_callback('validateaddress', [address])
addr_info = self.rpc('validateaddress', [address])
if not or_watch_only:
return addr_info['ismine']
return addr_info['ismine'] or addr_info['iswatchonly']
def createRawSignedTransaction(self, addr_to, amount) -> str:
txn_funded = self.createRawFundedTransaction(addr_to, amount)
return self.rpc_callback('signrawtransaction', [txn_funded])['hex']
return self.rpc('signrawtransaction', [txn_funded])['hex']
def getBlockchainInfo(self):
rv = self.rpc_callback('getblockchaininfo')
rv = self.rpc('getblockchaininfo')
synced = round(rv['verificationprogress'], 3)
if synced >= 0.997:
rv['verificationprogress'] = 1.0
@ -278,7 +287,7 @@ class NAVInterface(BTCInterface):
return pubkeyToAddress(self.chainparams_network()['script_address'], script)
def find_prevout_info(self, txn_hex: str, txn_script: bytes):
txjs = self.rpc_callback('decoderawtransaction', [txn_hex])
txjs = self.rpc('decoderawtransaction', [txn_hex])
n = getVoutByScriptPubKey(txjs, self.getScriptDest(txn_script).hex())
return {
@ -290,9 +299,9 @@ class NAVInterface(BTCInterface):
}
def getNewAddress(self, use_segwit: bool, label: str = 'swap_receive') -> str:
address: str = self.rpc_callback('getnewaddress', [label,])
address: str = self.rpc('getnewaddress', [label,])
if use_segwit:
return self.rpc_callback('addwitnessaddress', [address,])
return self.rpc('addwitnessaddress', [address,])
return address
def createRedeemTxn(self, prevout, output_addr: str, output_value: int, txn_script: bytes) -> str:
@ -385,15 +394,15 @@ class NAVInterface(BTCInterface):
chain_blocks: int = self.getChainHeight()
current_height: int = chain_blocks
block_hash = self.rpc_callback('getblockhash', [current_height])
block_hash = self.rpc('getblockhash', [current_height])
script_hash: bytes = self.decodeAddress(addr_find)
find_scriptPubKey = self.getDestForScriptHash(script_hash)
while current_height > height_start:
block_hash = self.rpc_callback('getblockhash', [current_height])
block_hash = self.rpc('getblockhash', [current_height])
block = self.rpc_callback('getblock', [block_hash, False])
block = self.rpc('getblock', [block_hash, False])
decoded_block = CBlock()
decoded_block = FromHex(decoded_block, block)
for tx in decoded_block.vtx:
@ -403,8 +412,8 @@ class NAVInterface(BTCInterface):
txid = i2b(tx.sha256)
self._log.info('Found output to addr: {} in tx {} in block {}'.format(addr_find, txid.hex(), block_hash))
self._log.info('rescanblockchain hack invalidateblock {}'.format(block_hash))
self.rpc_callback('invalidateblock', [block_hash])
self.rpc_callback('reconsiderblock', [block_hash])
self.rpc('invalidateblock', [block_hash])
self.rpc('reconsiderblock', [block_hash])
return
current_height -= 1
@ -419,7 +428,7 @@ class NAVInterface(BTCInterface):
return_txid = True if txid is None else False
if txid is None:
txns = self.rpc_callback('listunspent', [0, 9999999, [dest_address, ]])
txns = self.rpc('listunspent', [0, 9999999, [dest_address, ]])
for tx in txns:
if self.make_int(tx['amount']) == bid_amount:
@ -430,11 +439,11 @@ class NAVInterface(BTCInterface):
return None
try:
tx = self.rpc_callback('gettransaction', [txid.hex()])
tx = self.rpc('gettransaction', [txid.hex()])
block_height = 0
if 'blockhash' in tx:
block_header = self.rpc_callback('getblockheader', [tx['blockhash']])
block_header = self.rpc('getblockheader', [tx['blockhash']])
block_height = block_header['height']
rv = {
@ -446,7 +455,7 @@ class NAVInterface(BTCInterface):
return None
if find_index:
tx_obj = self.rpc_callback('decoderawtransaction', [tx['hex']])
tx_obj = self.rpc('decoderawtransaction', [tx['hex']])
rv['index'] = find_vout_for_address_from_txobj(tx_obj, dest_address)
if return_txid:
@ -456,15 +465,15 @@ class NAVInterface(BTCInterface):
def getBlockWithTxns(self, block_hash):
# TODO: Bypass decoderawtransaction and getblockheader
block = self.rpc_callback('getblock', [block_hash, False])
block_header = self.rpc_callback('getblockheader', [block_hash])
block = self.rpc('getblock', [block_hash, False])
block_header = self.rpc('getblockheader', [block_hash])
decoded_block = CBlock()
decoded_block = FromHex(decoded_block, block)
tx_rv = []
for tx in decoded_block.vtx:
tx_hex = tx.serialize_with_witness().hex()
tx_dec = self.rpc_callback('decoderawtransaction', [tx_hex])
tx_dec = self.rpc('decoderawtransaction', [tx_hex])
if 'hex' not in tx_dec:
tx_dec['hex'] = tx_hex
@ -505,7 +514,7 @@ class NAVInterface(BTCInterface):
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int) -> bytes:
self._log.info('spendBLockTx %s:\n', chain_b_lock_txid.hex())
wtx = self.rpc_callback('gettransaction', [chain_b_lock_txid.hex(), ])
wtx = self.rpc('gettransaction', [chain_b_lock_txid.hex(), ])
lock_tx = self.loadTx(bytes.fromhex(wtx['hex']))
Kbs = self.getPubkey(kbs)
@ -550,7 +559,7 @@ class NAVInterface(BTCInterface):
def findTxnByHash(self, txid_hex: str):
# Only works for wallet txns
try:
rv = self.rpc_callback('gettransaction', [txid_hex])
rv = self.rpc('gettransaction', [txid_hex])
except Exception as ex:
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
return None
@ -573,10 +582,10 @@ class NAVInterface(BTCInterface):
'lockUnspents': lock_unspents,
'feeRate': feerate_str,
}
rv = self.rpc_callback('fundrawtransaction', [tx_hex, options])
rv = self.rpc('fundrawtransaction', [tx_hex, options])
# Sign transaction then strip witness data to fill scriptsig
rv = self.rpc_callback('signrawtransaction', [rv['hex']])
rv = self.rpc('signrawtransaction', [rv['hex']])
tx_signed = self.loadTx(bytes.fromhex(rv['hex']))
if len(tx_signed.vin) != len(tx_signed.wit.vtxinwit):

2
basicswap/interface/nmc.py

@ -19,7 +19,7 @@ class NMCInterface(BTCInterface):
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index=False):
self._log.debug('[rm] scantxoutset start') # scantxoutset is slow
ro = self.rpc_callback('scantxoutset', ['start', ['addr({})'.format(dest_address)]]) # TODO: Use combo(address) where possible
ro = self.rpc('scantxoutset', ['start', ['addr({})'.format(dest_address)]]) # TODO: Use combo(address) where possible
self._log.debug('[rm] scantxoutset end')
return_txid = True if txid is None else False
for o in ro['unspents']:

150
basicswap/interface/part.py

@ -83,14 +83,14 @@ class PARTInterface(BTCInterface):
return True
def getNewAddress(self, use_segwit, label='swap_receive') -> str:
return self.rpc_callback('getnewaddress', [label])
return self.rpc_wallet('getnewaddress', [label])
def getNewStealthAddress(self, label='swap_stealth') -> str:
return self.rpc_callback('getnewstealthaddress', [label])
return self.rpc_wallet('getnewstealthaddress', [label])
def haveSpentIndex(self):
version = self.getDaemonVersion()
index_info = self.rpc_callback('getinsightinfo' if int(str(version)[:2]) > 19 else 'getindexinfo')
index_info = self.rpc('getinsightinfo' if int(str(version)[:2]) > 19 else 'getindexinfo')
return index_info['spentindex']
def initialiseWallet(self, key):
@ -98,14 +98,14 @@ class PARTInterface(BTCInterface):
def withdrawCoin(self, value, addr_to, subfee):
params = [addr_to, value, '', '', subfee, '', True, self._conf_target]
return self.rpc_callback('sendtoaddress', params)
return self.rpc_wallet('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)
return self.rpc_wallet('sendtypeto', params)
def getScriptForPubkeyHash(self, pkh: bytes) -> CScript:
return CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG])
@ -122,7 +122,7 @@ class PARTInterface(BTCInterface):
return length
def getWalletRestoreHeight(self) -> int:
start_time = self.rpc_callback('getwalletinfo')['keypoololdest']
start_time = self.rpc_wallet('getwalletinfo')['keypoololdest']
blockchaininfo = self.getBlockchainInfo()
best_block = blockchaininfo['bestblockhash']
@ -132,8 +132,8 @@ class PARTInterface(BTCInterface):
raise ValueError('{} chain isn\'t synced.'.format(self.coin_name()))
self._log.debug('Finding block at time: {}'.format(start_time))
block_hash = self.rpc_callback('getblockhashafter', [start_time])
block_header = self.rpc_callback('getblockheader', [block_hash])
block_hash = self.rpc('getblockhashafter', [start_time])
block_header = self.rpc('getblockheader', [block_hash])
return block_header['height']
def getHTLCSpendTxVSize(self, redeem: bool = True) -> int:
@ -171,16 +171,16 @@ class PARTInterfaceBlind(PARTInterface):
if txo['type'] != 'blind':
continue
try:
blinded_info = self.rpc_callback('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()])
blinded_info = self.rpc('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()])
output_n = txo['n']
self.rpc_callback('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()])
self.rpc('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()])
break
except Exception as e:
self._log.debug('Searching for locked output: {}'.format(str(e)))
continue
# Should not be possible for commitment not to match
v = self.rpc_callback('verifycommitment', [txo['valueCommitment'], blinded_info['blind'], blinded_info['amount']])
v = self.rpc('verifycommitment', [txo['valueCommitment'], blinded_info['blind'], blinded_info['amount']])
ensure(v['result'] is True, 'verifycommitment failed')
return output_n, blinded_info
@ -195,7 +195,7 @@ class PARTInterfaceBlind(PARTInterface):
inputs = []
outputs = [{'type': 'blind', 'amount': self.format_amount(value), 'address': p2wsh_addr, 'nonce': nonce.hex(), 'data': ephemeral_pubkey.hex()}]
params = [inputs, outputs]
rv = self.rpc_callback('createrawparttransaction', params)
rv = self.rpc_wallet('createrawparttransaction', params)
tx_bytes = bytes.fromhex(rv['hex'])
return tx_bytes
@ -207,11 +207,11 @@ class PARTInterfaceBlind(PARTInterface):
tx_hex = tx_bytes.hex()
nonce = self.getScriptLockTxNonce(vkbv)
tx_obj = self.rpc_callback('decoderawtransaction', [tx_hex])
tx_obj = self.rpc('decoderawtransaction', [tx_hex])
assert (len(tx_obj['vout']) == 1)
txo = tx_obj['vout'][0]
blinded_info = self.rpc_callback('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()])
blinded_info = self.rpc('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()])
outputs_info = {0: {'value': blinded_info['amount'], 'blind': blinded_info['blind'], 'nonce': nonce.hex()}}
@ -219,11 +219,11 @@ class PARTInterfaceBlind(PARTInterface):
'lockUnspents': True,
'feeRate': feerate_str,
}
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', tx_hex, {}, outputs_info, options])
rv = self.rpc('fundrawtransactionfrom', ['blind', tx_hex, {}, outputs_info, options])
return bytes.fromhex(rv['hex'])
def createSCLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv):
lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_bytes.hex()])
lock_tx_obj = self.rpc('decoderawtransaction', [tx_lock_bytes.hex()])
assert (self.getTxid(tx_lock_bytes).hex() == lock_tx_obj['txid'])
# Nonce is derived from vkbv, ephemeral_key isn't used
ephemeral_key = self.getNewSecretKey()
@ -244,7 +244,7 @@ class PARTInterfaceBlind(PARTInterface):
inputs = [{'txid': tx_lock_id, 'vout': spend_n, 'sequence': lock1_value, 'blindingfactor': input_blinded_info['blind']}]
outputs = [{'type': 'blind', 'amount': locked_coin, 'address': p2wsh_addr, 'nonce': output_nonce.hex(), 'data': ephemeral_pubkey.hex()}]
params = [inputs, outputs]
rv = self.rpc_callback('createrawparttransaction', params)
rv = self.rpc_wallet('createrawparttransaction', params)
lock_refund_tx_hex = rv['hex']
# Set dummy witness data for fee estimation
@ -261,7 +261,7 @@ class PARTInterfaceBlind(PARTInterface):
'feeRate': self.format_amount(tx_fee_rate),
'subtractFeeFromOutputs': [0, ]
}
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', lock_refund_tx_hex, inputs_info, outputs_info, options])
rv = self.rpc_wallet('fundrawtransactionfrom', ['blind', lock_refund_tx_hex, inputs_info, outputs_info, options])
lock_refund_tx_hex = rv['hex']
for vout, txo in rv['output_amounts'].items():
@ -275,7 +275,7 @@ class PARTInterfaceBlind(PARTInterface):
# The follower will sign the multisig path with a signature encumbered by the leader's coinB spend pubkey
# If the leader publishes the decrypted signature the leader's coinB spend privatekey will be revealed to the follower
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_refund_bytes.hex()])
lock_refund_tx_obj = self.rpc('decoderawtransaction', [tx_lock_refund_bytes.hex()])
# Nonce is derived from vkbv
nonce = self.getScriptLockRefundTxNonce(vkbv)
@ -285,7 +285,7 @@ class PARTInterfaceBlind(PARTInterface):
tx_lock_refund_id = lock_refund_tx_obj['txid']
addr_out = self.pkh_to_address(pkh_refund_to)
addr_info = self.rpc_callback('getaddressinfo', [addr_out])
addr_info = self.rpc_wallet('getaddressinfo', [addr_out])
output_pubkey_hex = addr_info['pubkey']
# Follower won't be able to decode output to check amount, shouldn't matter as fee is public and output is to leader, sum has to balance
@ -293,7 +293,7 @@ class PARTInterfaceBlind(PARTInterface):
inputs = [{'txid': tx_lock_refund_id, 'vout': spend_n, 'sequence': 0, 'blindingfactor': input_blinded_info['blind']}]
outputs = [{'type': 'blind', 'amount': input_blinded_info['amount'], 'address': addr_out, 'pubkey': output_pubkey_hex}]
params = [inputs, outputs]
rv = self.rpc_callback('createrawparttransaction', params)
rv = self.rpc_wallet('createrawparttransaction', params)
lock_refund_spend_tx_hex = rv['hex']
# Set dummy witness data for fee estimation
@ -311,7 +311,7 @@ class PARTInterfaceBlind(PARTInterface):
'subtractFeeFromOutputs': [0, ]
}
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', lock_refund_spend_tx_hex, inputs_info, outputs_info, options])
rv = self.rpc_wallet('fundrawtransactionfrom', ['blind', lock_refund_spend_tx_hex, inputs_info, outputs_info, options])
lock_refund_spend_tx_hex = rv['hex']
return bytes.fromhex(lock_refund_spend_tx_hex)
@ -321,7 +321,7 @@ class PARTInterfaceBlind(PARTInterface):
Kal, Kaf,
feerate,
check_lock_tx_inputs, vkbv):
lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
lock_tx_obj = self.rpc('decoderawtransaction', [tx_bytes.hex()])
lock_txid_hex = lock_tx_obj['txid']
self._log.info('Verifying lock tx: {}.'.format(lock_txid_hex))
@ -363,7 +363,7 @@ class PARTInterfaceBlind(PARTInterface):
def verifySCLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out,
prevout_id, prevout_n, prevout_seq, prevout_script,
Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv):
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
lock_refund_tx_obj = self.rpc('decoderawtransaction', [tx_bytes.hex()])
lock_refund_txid_hex = lock_refund_tx_obj['txid']
self._log.info('Verifying lock refund tx: {}.'.format(lock_refund_txid_hex))
@ -396,10 +396,10 @@ class PARTInterfaceBlind(PARTInterface):
ensure(C == Kaf, 'Bad script pubkey')
# Check rangeproofs and commitments sum
lock_tx_obj = self.rpc_callback('decoderawtransaction', [lock_tx_bytes.hex()])
lock_tx_obj = self.rpc('decoderawtransaction', [lock_tx_bytes.hex()])
prevout = lock_tx_obj['vout'][prevout_n]
prevtxns = [{'txid': prevout_id.hex(), 'vout': prevout_n, 'scriptPubKey': prevout['scriptPubKey']['hex'], 'amount_commitment': prevout['valueCommitment']}]
rv = self.rpc_callback('verifyrawtransaction', [tx_bytes.hex(), prevtxns])
rv = self.rpc('verifyrawtransaction', [tx_bytes.hex(), prevtxns])
ensure(rv['outputs_valid'] is True, 'Invalid outputs')
ensure(rv['inputs_valid'] is True, 'Invalid inputs')
@ -422,7 +422,7 @@ class PARTInterfaceBlind(PARTInterface):
lock_refund_tx_id, prevout_script,
Kal,
prevout_n, prevout_value, feerate, vkbv):
lock_refund_spend_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
lock_refund_spend_tx_obj = self.rpc('decoderawtransaction', [tx_bytes.hex()])
lock_refund_spend_txid_hex = lock_refund_spend_tx_obj['txid']
self._log.info('Verifying lock refund spend tx: {}.'.format(lock_refund_spend_txid_hex))
@ -441,10 +441,10 @@ class PARTInterfaceBlind(PARTInterface):
# Follower is not concerned with them as they pay to leader
# Check rangeproofs and commitments sum
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [lock_refund_tx_bytes.hex()])
lock_refund_tx_obj = self.rpc('decoderawtransaction', [lock_refund_tx_bytes.hex()])
prevout = lock_refund_tx_obj['vout'][prevout_n]
prevtxns = [{'txid': lock_refund_tx_id.hex(), 'vout': prevout_n, 'scriptPubKey': prevout['scriptPubKey']['hex'], 'amount_commitment': prevout['valueCommitment']}]
rv = self.rpc_callback('verifyrawtransaction', [tx_bytes.hex(), prevtxns])
rv = self.rpc('verifyrawtransaction', [tx_bytes.hex(), prevtxns])
ensure(rv['outputs_valid'] is True, 'Invalid outputs')
ensure(rv['inputs_valid'] is True, 'Invalid inputs')
@ -459,28 +459,28 @@ class PARTInterfaceBlind(PARTInterface):
return True
def getLockTxSwapOutputValue(self, bid, xmr_swap):
lock_tx_obj = self.rpc_callback('decoderawtransaction', [xmr_swap.a_lock_tx.hex()])
lock_tx_obj = self.rpc('decoderawtransaction', [xmr_swap.a_lock_tx.hex()])
nonce = self.getScriptLockTxNonce(xmr_swap.vkbv)
output_n, _ = self.findOutputByNonce(lock_tx_obj, nonce)
ensure(output_n is not None, 'Output not found in tx')
return bytes.fromhex(lock_tx_obj['vout'][output_n]['valueCommitment'])
def getLockRefundTxSwapOutputValue(self, bid, xmr_swap):
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [xmr_swap.a_lock_refund_tx.hex()])
lock_refund_tx_obj = self.rpc('decoderawtransaction', [xmr_swap.a_lock_refund_tx.hex()])
nonce = self.getScriptLockRefundTxNonce(xmr_swap.vkbv)
output_n, _ = self.findOutputByNonce(lock_refund_tx_obj, nonce)
ensure(output_n is not None, 'Output not found in tx')
return bytes.fromhex(lock_refund_tx_obj['vout'][output_n]['valueCommitment'])
def getLockRefundTxSwapOutput(self, xmr_swap):
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [xmr_swap.a_lock_refund_tx.hex()])
lock_refund_tx_obj = self.rpc('decoderawtransaction', [xmr_swap.a_lock_refund_tx.hex()])
nonce = self.getScriptLockRefundTxNonce(xmr_swap.vkbv)
output_n, _ = self.findOutputByNonce(lock_refund_tx_obj, nonce)
ensure(output_n is not None, 'Output not found in tx')
return output_n
def createSCLockSpendTx(self, tx_lock_bytes: bytes, script_lock: bytes, pk_dest: bytes, tx_fee_rate: int, vkbv: bytes, fee_info={}) -> bytes:
lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_bytes.hex()])
lock_tx_obj = self.rpc('decoderawtransaction', [tx_lock_bytes.hex()])
lock_txid_hex = lock_tx_obj['txid']
ensure(lock_tx_obj['version'] == self.txVersion(), 'Bad version')
@ -496,7 +496,7 @@ class PARTInterfaceBlind(PARTInterface):
inputs = [{'txid': lock_txid_hex, 'vout': spend_n, 'sequence': 0, 'blindingfactor': blinded_info['blind']}]
outputs = [{'type': 'blind', 'amount': blinded_info['amount'], 'address': addr_out, 'pubkey': pk_dest.hex()}]
params = [inputs, outputs]
rv = self.rpc_callback('createrawparttransaction', params)
rv = self.rpc_wallet('createrawparttransaction', params)
lock_spend_tx_hex = rv['hex']
# Set dummy witness data for fee estimation
@ -513,9 +513,9 @@ class PARTInterfaceBlind(PARTInterface):
'subtractFeeFromOutputs': [0, ]
}
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', lock_spend_tx_hex, inputs_info, outputs_info, options])
rv = self.rpc_wallet('fundrawtransactionfrom', ['blind', lock_spend_tx_hex, inputs_info, outputs_info, options])
lock_spend_tx_hex = rv['hex']
lock_spend_tx_obj = self.rpc_callback('decoderawtransaction', [lock_spend_tx_hex])
lock_spend_tx_obj = self.rpc('decoderawtransaction', [lock_spend_tx_hex])
pay_fee = make_int(lock_spend_tx_obj['vout'][0]['ct_fee'])
# lock_spend_tx_hex does not include the dummy witness stack
@ -535,7 +535,7 @@ class PARTInterfaceBlind(PARTInterface):
def verifySCLockSpendTx(self, tx_bytes,
lock_tx_bytes, lock_tx_script,
a_pk_f, feerate, vkbv):
lock_spend_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
lock_spend_tx_obj = self.rpc('decoderawtransaction', [tx_bytes.hex()])
lock_spend_txid_hex = lock_spend_tx_obj['txid']
self._log.info('Verifying lock spend tx: {}.'.format(lock_spend_txid_hex))
@ -543,7 +543,7 @@ class PARTInterfaceBlind(PARTInterface):
ensure(lock_spend_tx_obj['locktime'] == 0, 'Bad nLockTime')
ensure(len(lock_spend_tx_obj['vin']) == 1, 'tx doesn\'t have one input')
lock_tx_obj = self.rpc_callback('decoderawtransaction', [lock_tx_bytes.hex()])
lock_tx_obj = self.rpc('decoderawtransaction', [lock_tx_bytes.hex()])
lock_txid_hex = lock_tx_obj['txid']
# Find the output of the lock tx to verify
@ -559,7 +559,7 @@ class PARTInterfaceBlind(PARTInterface):
ensure(len(lock_spend_tx_obj['vout']) == 3, 'tx doesn\'t have three outputs')
addr_out = self.pubkey_to_address(a_pk_f)
privkey = self.rpc_callback('dumpprivkey', [addr_out])
privkey = self.rpc_wallet('dumpprivkey', [addr_out])
# Find output:
output_blinded_info = None
@ -568,7 +568,7 @@ class PARTInterfaceBlind(PARTInterface):
if txo['type'] != 'blind':
continue
try:
output_blinded_info = self.rpc_callback('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], privkey, txo['data_hex']])
output_blinded_info = self.rpc('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], privkey, txo['data_hex']])
output_n = txo['n']
break
except Exception as e:
@ -577,13 +577,13 @@ class PARTInterfaceBlind(PARTInterface):
ensure(output_n is not None, 'Output not found in tx')
# Commitment
v = self.rpc_callback('verifycommitment', [lock_spend_tx_obj['vout'][output_n]['valueCommitment'], output_blinded_info['blind'], output_blinded_info['amount']])
v = self.rpc('verifycommitment', [lock_spend_tx_obj['vout'][output_n]['valueCommitment'], output_blinded_info['blind'], output_blinded_info['amount']])
ensure(v['result'] is True, 'verifycommitment failed')
# Check rangeproofs and commitments sum
prevout = lock_tx_obj['vout'][spend_n]
prevtxns = [{'txid': lock_txid_hex, 'vout': spend_n, 'scriptPubKey': prevout['scriptPubKey']['hex'], 'amount_commitment': prevout['valueCommitment']}]
rv = self.rpc_callback('verifyrawtransaction', [tx_bytes.hex(), prevtxns])
rv = self.rpc('verifyrawtransaction', [tx_bytes.hex(), prevtxns])
ensure(rv['outputs_valid'] is True, 'Invalid outputs')
ensure(rv['inputs_valid'] is True, 'Invalid inputs')
@ -607,7 +607,7 @@ class PARTInterfaceBlind(PARTInterface):
def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv):
# lock refund swipe tx
# Sends the coinA locked coin to the follower
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_refund_bytes.hex()])
lock_refund_tx_obj = self.rpc('decoderawtransaction', [tx_lock_refund_bytes.hex()])
nonce = self.getScriptLockRefundTxNonce(vkbv)
# Find the output of the lock refund tx to spend
@ -616,7 +616,7 @@ class PARTInterfaceBlind(PARTInterface):
tx_lock_refund_id = lock_refund_tx_obj['txid']
addr_out = self.pkh_to_address(pkh_dest)
addr_info = self.rpc_callback('getaddressinfo', [addr_out])
addr_info = self.rpc_wallet('getaddressinfo', [addr_out])
output_pubkey_hex = addr_info['pubkey']
A, B, lock2_value, C = self.extractScriptLockRefundScriptValues(script_lock_refund)
@ -626,7 +626,7 @@ class PARTInterfaceBlind(PARTInterface):
inputs = [{'txid': tx_lock_refund_id, 'vout': spend_n, 'sequence': lock2_value, 'blindingfactor': input_blinded_info['blind']}]
outputs = [{'type': 'blind', 'amount': input_blinded_info['amount'], 'address': addr_out, 'pubkey': output_pubkey_hex}]
params = [inputs, outputs]
rv = self.rpc_callback('createrawparttransaction', params)
rv = self.rpc_wallet('createrawparttransaction', params)
lock_refund_swipe_tx_hex = rv['hex']
@ -645,13 +645,13 @@ class PARTInterfaceBlind(PARTInterface):
'subtractFeeFromOutputs': [0, ]
}
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', lock_refund_swipe_tx_hex, inputs_info, outputs_info, options])
rv = self.rpc_wallet('fundrawtransactionfrom', ['blind', lock_refund_swipe_tx_hex, inputs_info, outputs_info, options])
lock_refund_swipe_tx_hex = rv['hex']
return bytes.fromhex(lock_refund_swipe_tx_hex)
def getSpendableBalance(self) -> int:
return self.make_int(self.rpc_callback('getbalances')['mine']['blind_trusted'])
return self.make_int(self.rpc_wallet('getbalances')['mine']['blind_trusted'])
def publishBLockTx(self, vkbv: bytes, Kbs: bytes, output_amount: int, feerate: int, delay_for: int = 10, unlock_time: int = 0) -> bytes:
Kbv = self.getPubkey(vkbv)
@ -664,7 +664,7 @@ class PARTInterfaceBlind(PARTInterface):
'', '', self._anon_tx_ring_size, 1, False,
{'conf_target': self._conf_target, 'blind_watchonly_visible': True}]
txid = self.rpc_callback('sendtypeto', params)
txid = self.rpc_wallet('sendtypeto', params)
return bytes.fromhex(txid)
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height: int, bid_sender: bool):
@ -675,17 +675,17 @@ class PARTInterfaceBlind(PARTInterface):
if bid_sender:
cb_swap_value *= -1
else:
addr_info = self.rpc_callback('getaddressinfo', [sx_addr])
addr_info = self.rpc_wallet('getaddressinfo', [sx_addr])
if not addr_info['iswatchonly']:
wif_prefix = self.chainparams_network()['key_prefix']
wif_scan_key = toWIF(wif_prefix, kbv)
self.rpc_callback('importstealthaddress', [wif_scan_key, Kbs.hex()])
self.rpc_wallet('importstealthaddress', [wif_scan_key, Kbs.hex()])
self._log.info('Imported watch-only sx_addr: {}'.format(sx_addr))
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
self.rpc_callback('rescanblockchain', [restore_height])
self.rpc_wallet('rescanblockchain', [restore_height])
params = [{'include_watchonly': True, 'search': sx_addr}]
txns = self.rpc_callback('filtertransactions', params)
txns = self.rpc_wallet('filtertransactions', params)
if len(txns) == 1:
tx = txns[0]
@ -695,7 +695,7 @@ class PARTInterfaceBlind(PARTInterface):
if make_int(tx['outputs'][0]['amount']) == cb_swap_value:
height = 0
if tx['confirmations'] > 0:
chain_height = self.rpc_callback('getblockcount')
chain_height = self.rpc('getblockcount')
height = chain_height - (tx['confirmations'] - 1)
return {'txid': tx['txid'], 'amount': cb_swap_value, 'height': height}
else:
@ -707,20 +707,20 @@ class PARTInterfaceBlind(PARTInterface):
Kbv = self.getPubkey(kbv)
Kbs = self.getPubkey(kbs)
sx_addr = self.formatStealthAddress(Kbv, Kbs)
addr_info = self.rpc_callback('getaddressinfo', [sx_addr])
addr_info = self.rpc_wallet('getaddressinfo', [sx_addr])
if not addr_info['ismine']:
wif_prefix = self.chainparams_network()['key_prefix']
wif_scan_key = toWIF(wif_prefix, kbv)
wif_spend_key = toWIF(wif_prefix, kbs)
self.rpc_callback('importstealthaddress', [wif_scan_key, wif_spend_key])
self.rpc_wallet('importstealthaddress', [wif_scan_key, wif_spend_key])
self._log.info('Imported spend key for sx_addr: {}'.format(sx_addr))
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
self.rpc_callback('rescanblockchain', [restore_height])
self.rpc_wallet('rescanblockchain', [restore_height])
# TODO: Remove workaround
# utxos = self.rpc_callback('listunspentblind', [1, 9999999, [sx_addr]])
# utxos = self.rpc_wallet('listunspentblind', [1, 9999999, [sx_addr]])
utxos = []
all_utxos = self.rpc_callback('listunspentblind', [1, 9999999])
all_utxos = self.rpc_wallet('listunspentblind', [1, 9999999])
for utxo in all_utxos:
if utxo.get('stealth_address', '_') == sx_addr:
utxos.append(utxo)
@ -741,14 +741,14 @@ class PARTInterfaceBlind(PARTInterface):
[{'address': address_to, 'amount': self.format_amount(cb_swap_value), 'subfee': True}, ],
'', '', self._anon_tx_ring_size, 1, False,
{'conf_target': self._conf_target, 'inputs': inputs, 'show_fee': True}]
rv = self.rpc_callback('sendtypeto', params)
rv = self.rpc_wallet('sendtypeto', params)
return bytes.fromhex(rv['txid'])
def findTxnByHash(self, txid_hex):
# txindex is enabled for Particl
try:
rv = self.rpc_callback('getrawtransaction', [txid_hex, True])
rv = self.rpc('getrawtransaction', [txid_hex, True])
except Exception as ex:
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
return None
@ -759,7 +759,7 @@ class PARTInterfaceBlind(PARTInterface):
return None
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
txn = self.rpc_wallet('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
options = {
'lockUnspents': lock_unspents,
@ -767,7 +767,7 @@ class PARTInterfaceBlind(PARTInterface):
}
if sub_fee:
options['subtractFeeFromOutputs'] = [0,]
return self.rpc_callback('fundrawtransactionfrom', ['blind', txn, options])['hex']
return self.rpc_wallet('fundrawtransactionfrom', ['blind', txn, options])['hex']
class PARTInterfaceAnon(PARTInterface):
@ -801,7 +801,7 @@ class PARTInterfaceAnon(PARTInterface):
'', '', self._anon_tx_ring_size, 1, False,
{'conf_target': self._conf_target, 'blind_watchonly_visible': True}]
txid = self.rpc_callback('sendtypeto', params)
txid = self.rpc_wallet('sendtypeto', params)
return bytes.fromhex(txid)
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender):
@ -813,17 +813,17 @@ class PARTInterfaceAnon(PARTInterface):
if bid_sender:
cb_swap_value *= -1
else:
addr_info = self.rpc_callback('getaddressinfo', [sx_addr])
addr_info = self.rpc_wallet('getaddressinfo', [sx_addr])
if not addr_info['iswatchonly']:
wif_prefix = self.chainparams_network()['key_prefix']
wif_scan_key = toWIF(wif_prefix, kbv)
self.rpc_callback('importstealthaddress', [wif_scan_key, Kbs.hex()])
self.rpc_wallet('importstealthaddress', [wif_scan_key, Kbs.hex()])
self._log.info('Imported watch-only sx_addr: {}'.format(sx_addr))
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
self.rpc_callback('rescanblockchain', [restore_height])
self.rpc_wallet('rescanblockchain', [restore_height])
params = [{'include_watchonly': True, 'search': sx_addr}]
txns = self.rpc_callback('filtertransactions', params)
txns = self.rpc_wallet('filtertransactions', params)
if len(txns) == 1:
tx = txns[0]
@ -833,7 +833,7 @@ class PARTInterfaceAnon(PARTInterface):
if make_int(tx['outputs'][0]['amount']) == cb_swap_value:
height = 0
if tx['confirmations'] > 0:
chain_height = self.rpc_callback('getblockcount')
chain_height = self.rpc('getblockcount')
height = chain_height - (tx['confirmations'] - 1)
return {'txid': tx['txid'], 'amount': cb_swap_value, 'height': height}
else:
@ -845,17 +845,17 @@ class PARTInterfaceAnon(PARTInterface):
Kbv = self.getPubkey(kbv)
Kbs = self.getPubkey(kbs)
sx_addr = self.formatStealthAddress(Kbv, Kbs)
addr_info = self.rpc_callback('getaddressinfo', [sx_addr])
addr_info = self.rpc_wallet('getaddressinfo', [sx_addr])
if not addr_info['ismine']:
wif_prefix = self.chainparams_network()['key_prefix']
wif_scan_key = toWIF(wif_prefix, kbv)
wif_spend_key = toWIF(wif_prefix, kbs)
self.rpc_callback('importstealthaddress', [wif_scan_key, wif_spend_key])
self.rpc_wallet('importstealthaddress', [wif_scan_key, wif_spend_key])
self._log.info('Imported spend key for sx_addr: {}'.format(sx_addr))
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
self.rpc_callback('rescanblockchain', [restore_height])
self.rpc_wallet('rescanblockchain', [restore_height])
autxos = self.rpc_callback('listunspentanon', [1, 9999999, [sx_addr]])
autxos = self.rpc_wallet('listunspentanon', [1, 9999999, [sx_addr]])
if len(autxos) < 1:
raise TemporaryError('No spendable outputs')
@ -874,14 +874,14 @@ class PARTInterfaceAnon(PARTInterface):
[{'address': address_to, 'amount': self.format_amount(cb_swap_value), 'subfee': True}, ],
'', '', self._anon_tx_ring_size, 1, False,
{'conf_target': self._conf_target, 'inputs': inputs, 'show_fee': True}]
rv = self.rpc_callback('sendtypeto', params)
rv = self.rpc_wallet('sendtypeto', params)
return bytes.fromhex(rv['txid'])
def findTxnByHash(self, txid_hex: str):
# txindex is enabled for Particl
try:
rv = self.rpc_callback('getrawtransaction', [txid_hex, True])
rv = self.rpc('getrawtransaction', [txid_hex, True])
except Exception as ex:
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
return None
@ -892,4 +892,4 @@ class PARTInterfaceAnon(PARTInterface):
return None
def getSpendableBalance(self) -> int:
return self.make_int(self.rpc_callback('getbalances')['mine']['anon_trusted'])
return self.make_int(self.rpc_wallet('getbalances')['mine']['anon_trusted'])

31
basicswap/interface/pivx.py

@ -8,6 +8,7 @@
from io import BytesIO
from .btc import BTCInterface
from basicswap.rpc import make_rpc_func
from basicswap.chainparams import Coins
from basicswap.util.address import decodeAddress
from .contrib.pivx_test_framework.messages import (
@ -29,12 +30,20 @@ class PIVXInterface(BTCInterface):
def coin_type():
return Coins.PIVX
def __init__(self, coin_settings, network, swap_client=None):
super(PIVXInterface, self).__init__(coin_settings, network, swap_client)
# No multiwallet support
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
def checkWallets(self) -> int:
return 1
def signTxWithWallet(self, tx):
rv = self.rpc_callback('signrawtransaction', [tx.hex()])
rv = self.rpc('signrawtransaction', [tx.hex()])
return bytes.fromhex(rv['hex'])
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}')
options = {
@ -43,25 +52,25 @@ class PIVXInterface(BTCInterface):
}
if sub_fee:
options['subtractFeeFromOutputs'] = [0,]
return self.rpc_callback('fundrawtransaction', [txn, options])['hex']
return self.rpc('fundrawtransaction', [txn, options])['hex']
def createRawSignedTransaction(self, addr_to, amount) -> str:
txn_funded = self.createRawFundedTransaction(addr_to, amount)
return self.rpc_callback('signrawtransaction', [txn_funded])['hex']
return self.rpc('signrawtransaction', [txn_funded])['hex']
def decodeAddress(self, address):
return decodeAddress(address)[1:]
def getBlockWithTxns(self, block_hash):
# TODO: Bypass decoderawtransaction and getblockheader
block = self.rpc_callback('getblock', [block_hash, False])
block_header = self.rpc_callback('getblockheader', [block_hash])
block = self.rpc('getblock', [block_hash, False])
block_header = self.rpc('getblockheader', [block_hash])
decoded_block = CBlock()
decoded_block = FromHex(decoded_block, block)
tx_rv = []
for tx in decoded_block.vtx:
tx_dec = self.rpc_callback('decoderawtransaction', [ToHex(tx)])
tx_dec = self.rpc('decoderawtransaction', [ToHex(tx)])
tx_rv.append(tx_dec)
block_rv = {
@ -77,10 +86,10 @@ class PIVXInterface(BTCInterface):
def withdrawCoin(self, value, addr_to, subfee):
params = [addr_to, value, '', '', subfee]
return self.rpc_callback('sendtoaddress', params)
return self.rpc('sendtoaddress', params)
def getSpendableBalance(self) -> int:
return self.make_int(self.rpc_callback('getwalletinfo')['balance'])
return self.make_int(self.rpc('getwalletinfo')['balance'])
def loadTx(self, tx_bytes):
# Load tx from bytes to internal representation
@ -101,13 +110,13 @@ class PIVXInterface(BTCInterface):
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
key_wif = self.encodeKey(key)
rv = self.rpc_callback('signrawtransaction', [tx.hex(), [], [key_wif, ]])
rv = self.rpc('signrawtransaction', [tx.hex(), [], [key_wif, ]])
return bytes.fromhex(rv['hex'])
def findTxnByHash(self, txid_hex: str):
# Only works for wallet txns
try:
rv = self.rpc_callback('gettransaction', [txid_hex])
rv = self.rpc('gettransaction', [txid_hex])
except Exception as ex:
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
return None

91
basicswap/interface/xmr.py

@ -83,9 +83,9 @@ class XMRInterface(CoinInterface):
daemon_login = None
if coin_settings.get('rpcuser', '') != '':
daemon_login = (coin_settings.get('rpcuser', ''), coin_settings.get('rpcpassword', ''))
self.rpc_cb = make_xmr_rpc_func(coin_settings['rpcport'], daemon_login, host=coin_settings.get('rpchost', '127.0.0.1'))
self.rpc_cb2 = make_xmr_rpc2_func(coin_settings['rpcport'], daemon_login, host=coin_settings.get('rpchost', '127.0.0.1')) # non-json endpoint
self.rpc_wallet_cb = make_xmr_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'], host=coin_settings.get('walletrpchost', '127.0.0.1'))
self.rpc = make_xmr_rpc_func(coin_settings['rpcport'], daemon_login, host=coin_settings.get('rpchost', '127.0.0.1'))
self.rpc2 = make_xmr_rpc2_func(coin_settings['rpcport'], daemon_login, host=coin_settings.get('rpchost', '127.0.0.1')) # non-json endpoint
self.rpc_wallet = make_xmr_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'], host=coin_settings.get('walletrpchost', '127.0.0.1'))
self.blocks_confirmed = coin_settings['blocks_confirmed']
self._restore_height = coin_settings.get('restore_height', 0)
@ -95,6 +95,9 @@ class XMRInterface(CoinInterface):
self._wallet_password = None
self._have_checked_seed = False
def checkWallets(self) -> int:
return 1
def setFeePriority(self, new_priority):
ensure(new_priority >= 0 and new_priority < 4, 'Invalid fee_priority value')
self._fee_priority = new_priority
@ -105,7 +108,7 @@ class XMRInterface(CoinInterface):
def createWallet(self, params):
if self._wallet_password is not None:
params['password'] = self._wallet_password
rv = self.rpc_wallet_cb('generate_from_keys', params)
rv = self.rpc_wallet('generate_from_keys', params)
self._log.info('generate_from_keys %s', dumpj(rv))
def openWallet(self, filename):
@ -115,10 +118,10 @@ class XMRInterface(CoinInterface):
try:
# Can't reopen the same wallet in windows, !is_keys_file_locked()
self.rpc_wallet_cb('close_wallet')
self.rpc_wallet('close_wallet')
except Exception:
pass
self.rpc_wallet_cb('open_wallet', params)
self.rpc_wallet('open_wallet', params)
def initialiseWallet(self, key_view, key_spend, restore_height=None):
with self._mx_wallet:
@ -147,14 +150,14 @@ class XMRInterface(CoinInterface):
with self._mx_wallet:
self.openWallet(self._wallet_filename)
def testDaemonRPC(self, with_wallet=True):
self.rpc_wallet_cb('get_languages')
def testDaemonRPC(self, with_wallet=True) -> None:
self.rpc_wallet('get_languages')
def getDaemonVersion(self):
return self.rpc_wallet_cb('get_version')['version']
return self.rpc_wallet('get_version')['version']
def getBlockchainInfo(self):
get_height = self.rpc_cb2('get_height', timeout=30)
get_height = self.rpc2('get_height', timeout=30)
rv = {
'blocks': get_height['height'],
'verificationprogress': 0.0,
@ -165,7 +168,7 @@ class XMRInterface(CoinInterface):
# get_block_count returns "Internal error" if bootstrap-daemon is active
if get_height['untrusted'] is True:
rv['bootstrapping'] = True
get_info = self.rpc_cb2('get_info', timeout=30)
get_info = self.rpc2('get_info', timeout=30)
if 'height_without_bootstrap' in get_info:
rv['blocks'] = get_info['height_without_bootstrap']
@ -173,7 +176,7 @@ class XMRInterface(CoinInterface):
if rv['known_block_count'] > rv['blocks']:
rv['verificationprogress'] = rv['blocks'] / rv['known_block_count']
else:
rv['known_block_count'] = self.rpc_cb('get_block_count', timeout=30)['count']
rv['known_block_count'] = self.rpc('get_block_count', timeout=30)['count']
rv['verificationprogress'] = rv['blocks'] / rv['known_block_count']
except Exception as e:
self._log.warning('XMR get_block_count failed with: %s', str(e))
@ -182,7 +185,7 @@ class XMRInterface(CoinInterface):
return rv
def getChainHeight(self):
return self.rpc_cb2('get_height', timeout=30)['height']
return self.rpc2('get_height', timeout=30)['height']
def getWalletInfo(self):
with self._mx_wallet:
@ -195,8 +198,8 @@ class XMRInterface(CoinInterface):
raise e
rv = {}
self.rpc_wallet_cb('refresh')
balance_info = self.rpc_wallet_cb('get_balance')
self.rpc_wallet('refresh')
balance_info = self.rpc_wallet('get_balance')
rv['balance'] = self.format_amount(balance_info['unlocked_balance'])
rv['unconfirmed_balance'] = self.format_amount(balance_info['balance'] - balance_info['unlocked_balance'])
rv['encrypted'] = False if self._wallet_password is None else True
@ -209,13 +212,13 @@ class XMRInterface(CoinInterface):
def getMainWalletAddress(self) -> str:
with self._mx_wallet:
self.openWallet(self._wallet_filename)
return self.rpc_wallet_cb('get_address')['address']
return self.rpc_wallet('get_address')['address']
def getNewAddress(self, placeholder) -> str:
with self._mx_wallet:
self.openWallet(self._wallet_filename)
new_address = self.rpc_wallet_cb('create_address', {'account_index': 0})['address']
self.rpc_wallet_cb('store')
new_address = self.rpc_wallet('create_address', {'account_index': 0})['address']
self.rpc_wallet('store')
return new_address
def get_fee_rate(self, conf_target: int = 2):
@ -280,7 +283,7 @@ class XMRInterface(CoinInterface):
def publishBLockTx(self, kbv: bytes, Kbs: bytes, output_amount: int, feerate: int, delay_for: int = 10, unlock_time: int = 0) -> bytes:
with self._mx_wallet:
self.openWallet(self._wallet_filename)
self.rpc_wallet_cb('refresh')
self.rpc_wallet('refresh')
Kbv = self.getPubkey(kbv)
shared_addr = xmr_util.encode_address(Kbv, Kbs)
@ -288,7 +291,7 @@ class XMRInterface(CoinInterface):
params = {'destinations': [{'amount': output_amount, 'address': shared_addr}], 'unlock_time': unlock_time}
if self._fee_priority > 0:
params['priority'] = self._fee_priority
rv = self.rpc_wallet_cb('transfer', params)
rv = self.rpc_wallet('transfer', params)
self._log.info('publishBLockTx %s to address_b58 %s', rv['tx_hash'], shared_addr)
tx_hash = bytes.fromhex(rv['tx_hash'])
@ -296,7 +299,7 @@ class XMRInterface(CoinInterface):
i = 0
while not self._sc.delay_event.is_set():
gt_params = {'out': True, 'pending': True, 'failed': True, 'pool': True, }
rv = self.rpc_wallet_cb('get_transfers', gt_params)
rv = self.rpc_wallet('get_transfers', gt_params)
self._log.debug('get_transfers {}'.format(dumpj(rv)))
if 'pending' not in rv:
break
@ -325,26 +328,26 @@ class XMRInterface(CoinInterface):
self.createWallet(params)
self.openWallet(address_b58)
self.rpc_wallet_cb('refresh', timeout=600)
self.rpc_wallet('refresh', timeout=600)
'''
# Debug
try:
current_height = self.rpc_wallet_cb('get_height')['height']
current_height = self.rpc_wallet('get_height')['height']
self._log.info('findTxB XMR current_height %d\nAddress: %s', current_height, address_b58)
except Exception as e:
self._log.info('rpc_cb failed %s', str(e))
self._log.info('rpc failed %s', str(e))
current_height = None # If the transfer is available it will be deep enough
# and (current_height is None or current_height - transfer['block_height'] > cb_block_confirmed):
'''
params = {'transfer_type': 'available'}
transfers = self.rpc_wallet_cb('incoming_transfers', params)
transfers = self.rpc_wallet('incoming_transfers', params)
rv = None
if 'transfers' in transfers:
for transfer in transfers['transfers']:
# unlocked <- wallet->is_transfer_unlocked() checks unlock_time and CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE
if not transfer['unlocked']:
full_tx = self.rpc_wallet_cb('get_transfer_by_txid', {'txid': transfer['tx_hash']})
full_tx = self.rpc_wallet('get_transfer_by_txid', {'txid': transfer['tx_hash']})
unlock_time = full_tx['transfer']['unlock_time']
if unlock_time != 0:
self._log.warning('Coin b lock txn is locked: {}, unlock_time {}'.format(transfer['tx_hash'], unlock_time))
@ -360,17 +363,17 @@ class XMRInterface(CoinInterface):
def findTxnByHash(self, txid):
with self._mx_wallet:
self.openWallet(self._wallet_filename)
self.rpc_wallet_cb('refresh', timeout=600)
self.rpc_wallet('refresh', timeout=600)
try:
current_height = self.rpc_cb2('get_height', timeout=30)['height']
current_height = self.rpc2('get_height', timeout=30)['height']
self._log.info('findTxnByHash XMR current_height %d\nhash: %s', current_height, txid)
except Exception as e:
self._log.info('rpc_cb failed %s', str(e))
self._log.info('rpc failed %s', str(e))
current_height = None # If the transfer is available it will be deep enough
params = {'transfer_type': 'available'}
rv = self.rpc_wallet_cb('incoming_transfers', params)
rv = self.rpc_wallet('incoming_transfers', params)
if 'transfers' in rv:
for transfer in rv['transfers']:
if transfer['tx_hash'] == txid \
@ -405,11 +408,11 @@ class XMRInterface(CoinInterface):
self.createWallet(params)
self.openWallet(wallet_filename)
self.rpc_wallet_cb('refresh')
rv = self.rpc_wallet_cb('get_balance')
self.rpc_wallet('refresh')
rv = self.rpc_wallet('get_balance')
if rv['balance'] < cb_swap_value:
self._log.warning('Balance is too low, checking for existing spend.')
txns = self.rpc_wallet_cb('get_transfers', {'out': True})
txns = self.rpc_wallet('get_transfers', {'out': True})
if 'out' in txns:
txns = txns['out']
if len(txns) > 0:
@ -434,7 +437,7 @@ class XMRInterface(CoinInterface):
if self._fee_priority > 0:
params['priority'] = self._fee_priority
rv = self.rpc_wallet_cb('sweep_all', params)
rv = self.rpc_wallet('sweep_all', params)
self._log.debug('sweep_all {}'.format(json.dumps(rv)))
return bytes.fromhex(rv['tx_hash_list'][0])
@ -444,24 +447,24 @@ class XMRInterface(CoinInterface):
value_sats = make_int(value, self.exp())
self.openWallet(self._wallet_filename)
self.rpc_wallet_cb('refresh')
self.rpc_wallet('refresh')
if subfee:
balance = self.rpc_wallet_cb('get_balance')
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_cb('sweep_all', params)
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.')
params = {'destinations': [{'amount': value_sats, 'address': addr_to}]}
if self._fee_priority > 0:
params['priority'] = self._fee_priority
rv = self.rpc_wallet_cb('transfer', params)
rv = self.rpc_wallet('transfer', params)
return rv['tx_hash']
def showLockTransfers(self, kbv, Kbs, restore_height):
@ -488,9 +491,9 @@ class XMRInterface(CoinInterface):
self.createWallet(params)
self.openWallet(address_b58)
self.rpc_wallet_cb('refresh')
self.rpc_wallet('refresh')
rv = self.rpc_wallet_cb('get_transfers', {'in': True, 'out': True, 'pending': True, 'failed': True})
rv = self.rpc_wallet('get_transfers', {'in': True, 'out': True, 'pending': True, 'failed': True})
rv['filename'] = wallet_file
return rv
except Exception as e:
@ -500,8 +503,8 @@ class XMRInterface(CoinInterface):
with self._mx_wallet:
self.openWallet(self._wallet_filename)
self.rpc_wallet_cb('refresh')
balance_info = self.rpc_wallet_cb('get_balance')
self.rpc_wallet('refresh')
balance_info = self.rpc_wallet('get_balance')
return balance_info['unlocked_balance']
def changeWalletPassword(self, old_password, new_password):
@ -511,7 +514,7 @@ class XMRInterface(CoinInterface):
self._wallet_password = old_password
try:
self.openWallet(self._wallet_filename)
self.rpc_wallet_cb('change_wallet_password', {'old_password': old_password, 'new_password': new_password})
self.rpc_wallet('change_wallet_password', {'old_password': old_password, 'new_password': new_password})
except Exception as e:
self._wallet_password = orig_password
raise e
@ -536,4 +539,4 @@ class XMRInterface(CoinInterface):
raise ValueError('Balance too low')
def getTransaction(self, txid: bytes):
return self.rpc_cb2('get_transactions', {'txs_hashes': [txid.hex(), ]})
return self.rpc2('get_transactions', {'txs_hashes': [txid.hex(), ]})

24
basicswap/js_server.py

@ -63,6 +63,9 @@ def withdraw_coin(swap_client, coin_type, post_string, is_json):
type_from = get_data_entry_or(post_data, 'type_from', 'plain')
type_to = get_data_entry_or(post_data, 'type_to', 'plain')
txid_hex = swap_client.withdrawParticl(type_from, type_to, value, address, subfee)
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)
else:
txid_hex = swap_client.withdrawCoin(coin_type, value, address, subfee)
@ -92,6 +95,8 @@ def js_coins(self, url_split, post_string, is_json) -> bytes:
entry['variant'] = 'Anon'
elif coin == Coins.PART_BLIND:
entry['variant'] = 'Blind'
elif coin == Coins.LTC_MWEB:
entry['variant'] = 'MWEB'
coins.append(entry)
return bytes(json.dumps(coins), 'UTF-8')
@ -108,19 +113,30 @@ def js_wallets(self, url_split, post_string, is_json):
cmd = url_split[4]
if cmd == 'withdraw':
return bytes(json.dumps(withdraw_coin(swap_client, coin_type, post_string, is_json)), 'UTF-8')
if cmd == 'nextdepositaddr':
elif cmd == 'nextdepositaddr':
return bytes(json.dumps(swap_client.cacheNewAddressForCoin(coin_type)), 'UTF-8')
if cmd == 'createutxo':
elif cmd == 'createutxo':
post_data = getFormData(post_string, is_json)
ci = swap_client.ci(coin_type)
value = ci.make_int(get_data_entry(post_data, 'value'))
txid_hex, new_addr = ci.createUTXO(value)
return bytes(json.dumps({'txid': txid_hex, 'address': new_addr}), 'UTF-8')
if cmd == 'reseed':
elif cmd == 'reseed':
swap_client.reseedWallet(coin_type)
return bytes(json.dumps({'reseeded': True}), 'UTF-8')
elif cmd == 'newstealthaddress':
if coin_type != Coins.PART:
raise ValueError('Invalid coin for command')
return bytes(json.dumps(swap_client.ci(coin_type).getNewStealthAddress()), 'UTF-8')
elif cmd == 'newmwebaddress':
if coin_type not in (Coins.LTC, Coins.LTC_MWEB):
raise ValueError('Invalid coin for command')
return bytes(json.dumps(swap_client.ci(coin_type).getNewMwebAddress()), 'UTF-8')
raise ValueError('Unknown command')
if coin_type == Coins.LTC_MWEB:
coin_type = Coins.LTC
rv = swap_client.getWalletInfo(coin_type)
rv.update(swap_client.getBlockchainInfo(coin_type))
ci = swap_client.ci(coin_type)
@ -647,7 +663,7 @@ def js_setpassword(self, url_split, post_string, is_json) -> bytes:
if have_data_entry(post_data, 'coin'):
# Set password for one coin
coin = getCoinType(get_data_entry(post_data, 'coin'))
if coin in (Coins.PART_ANON, Coins.PART_BLIND):
if coin in (Coins.PART_ANON, Coins.PART_BLIND, Coins.LTC_MWEB):
raise ValueError('Invalid coin.')
swap_client.changeWalletPasswords(old_password, new_password, coin)
return bytes(json.dumps({'success': True}), 'UTF-8')

12
basicswap/templates/style.html

@ -0,0 +1,12 @@
{% set select_box_arrow_svg = '<svg class="absolute right-4 top-1/2 transform -translate-y-1/2" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.3333 6.1133C11.2084 5.98913 11.0395 5.91943 10.8633 5.91943C10.6872 5.91943 10.5182 5.98913 10.3933 6.1133L8.00001 8.47329L5.64001 6.1133C5.5151 5.98913 5.34613 5.91943 5.17001 5.91943C4.99388 5.91943 4.82491 5.98913 4.70001 6.1133C4.63752 6.17527 4.58792 6.249 4.55408 6.33024C4.52023 6.41148 4.50281 6.49862 4.50281 6.58663C4.50281 6.67464 4.52023 6.76177 4.55408 6.84301C4.58792 6.92425 4.63752 6.99799 4.70001 7.05996L7.52667 9.88663C7.58865 9.94911 7.66238 9.99871 7.74362 10.0326C7.82486 10.0664 7.912 10.0838 8.00001 10.0838C8.08801 10.0838 8.17515 10.0664 8.25639 10.0326C8.33763 9.99871 8.41136 9.94911 8.47334 9.88663L11.3333 7.05996C11.3958 6.99799 11.4454 6.92425 11.4793 6.84301C11.5131 6.76177 11.5305 6.67464 11.5305 6.58663C11.5305 6.49862 11.5131 6.41148 11.4793 6.33024C11.4454 6.249 11.3958 6.17527 11.3333 6.1133Z" fill="#8896AB"></path>
</svg>' %}
{% set select_box_class = 'hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0' %}
{% set circular_arrows_svg = '<svg class="text-gray-500 w-5 h-5 mr-2" xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24">
<g fill="#ffffff" class="nc-icon-wrapper">
<path fill="#ffffff" d="M12,3c1.989,0,3.873,0.65,5.43,1.833l-3.604,3.393l9.167,0.983L22.562,0l-3.655,3.442 C16.957,1.862,14.545,1,12,1C5.935,1,1,5.935,1,12h2C3,7.037,7.037,3,12,3z"></path>
<path data-color="color-2" d="M12,21c-1.989,0-3.873-0.65-5.43-1.833l3.604-3.393l-9.167-0.983L1.438,24l3.655-3.442 C7.043,22.138,9.455,23,12,23c6.065,0,11-4.935,11-11h-2C21,16.963,16.963,21,12,21z"></path>
</g>
</svg>' %}

96
basicswap/templates/wallet.html

@ -1,4 +1,6 @@
{% include 'header.html' %}
{% from 'style.html' import select_box_arrow_svg, select_box_class, circular_arrows_svg %}
<div class="container mx-auto">
<section class="p-5 mt-5">
<div class="flex flex-wrap items-center -m-2">
@ -47,12 +49,7 @@
<div class="w-full md:w-1/2 p-3 p-6 container flex flex-wrap items-center justify-end items-center mx-auto">
<a class="w-full md:w-1/2 p-3 p-6 container flex flex-wrap items-center justify-end items-center mx-auto">
<a class="mr-5 flex flex-wrap justify-center px-5 py-3 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border dark:bg-gray-500 dark:hover:bg-gray-700 border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" id="refresh" href="/wallet/{{ w.ticker }}">
<svg class="text-gray-500 w-5 h-5 mr-2" xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24">
<g fill="#ffffff" class="nc-icon-wrapper">
<path fill="#ffffff" d="M12,3c1.989,0,3.873,0.65,5.43,1.833l-3.604,3.393l9.167,0.983L22.562,0l-3.655,3.442 C16.957,1.862,14.545,1,12,1C5.935,1,1,5.935,1,12h2C3,7.037,7.037,3,12,3z"></path>
<path data-color="color-2" d="M12,21c-1.989,0-3.873-0.65-5.43-1.833l3.604-3.393l-9.167-0.983L1.438,24l3.655-3.442 C7.043,22.138,9.455,23,12,23c6.065,0,11-4.935,11-11h-2C21,16.963,16.963,21,12,21z"></path>
</g>
</svg>
{{ circular_arrows_svg }}
<span>Refresh</span>
</a>
</div>
@ -165,7 +162,7 @@
</td>
<td class="py-3 px-6 bold coinname-value" data-coinname="{{ w.name }}">{{ w.balance }} {{ w.ticker }} (<span class="usd-value"></span>){% if w.unconfirmed %} <span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Unconfirmed: +{{ w.unconfirmed }} {{ w.ticker }}</span>{% endif %}</td>
</tr>
{% if w.cid == '1' %}
{% if w.cid == '1' %} {# PART #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">
<span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded">
@ -180,9 +177,21 @@
<img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} Anon">
</span>Anon Balance:
</td>
<td class="py-3 px-6 bold coinname-value" data-coinname="{{ w.name }}">{{ w.anon_balance }} {{ w.ticker }} (<span class="usd-value"></span>) {% if w.anon_pending %} <span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.anon_pending }} {{ w.ticker }}</span>{% endif %}</td>
<td class="py-3 px-6 bold coinname-value" data-coinname="{{ w.name }}">{{ w.anon_balance }} {{ w.ticker }} (<span class="usd-value"></span>) {% if w.anon_pending %} <span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.anon_pending }} {{ w.ticker }}</span>{% endif %}</td>
<td class="usd-value"></td>
</tr> {% endif %} <tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
</tr>
{% endif %} {# / PART #}
{% if w.cid == '3' %} {# LTC #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">
<span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded">
<img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} MWEB">
</span>MWEB Balance:
</td>
<td class="py-3 px-6 bold coinname-value" data-coinname="{{ w.name }}">{{ w.mweb_balance }} {{ w.ticker }} (<span class="usd-value"></span>) {% if w.mweb_pending %} <span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.mweb_pending }} {{ w.ticker }}</span>{% endif %}</td>
</tr>
{% endif %} {# / LTC #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">Blocks:</td>
<td class="py-3 px-6">{{ w.blocks }} {% if w.known_block_count %} / {{ w.known_block_count }} {% endif %}</td>
</tr>
@ -192,13 +201,13 @@
</tr> {% if w.bootstrapping %} <tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">Bootstrapping:</td>
<td class="py-3 px-6">{{ w.bootstrapping }}</td>
</tr> {% endif %}
</tr> {% endif %} {# / bootstrapping #}
{% if w.encrypted %}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">Locked:</td>
<td class="py-3 px-6">{{ w.locked }}</td>
</tr>
{% endif %}
{% endif %} {# / encrypted #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">Expected Seed:</td>
<td class="py-3 px-6">{{ w.expected_seed }}</td> {% if block_unknown_seeds and w.expected_seed != true %} {# Only show addresses if wallet seed is correct #}
@ -235,13 +244,22 @@
{% else %}
</tr>
{% if w.cid == '1' %}
{% if w.cid == '1' %} {# PART #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">Stealth Address</td>
<td colspan=2 class="py-3 px-6 monospace bold select-all">{{ w.stealth_address }}</td>
</tr>
{% endif %}
{% if w.cid == '6' %}
{% endif %} {# / PART #}
{% if w.cid == '3' %} {# LTC #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">
<button type="submit" class="flex flex-wrap justify-center py-2 px-4 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" name="newmwebaddr_{{ w.cid }}" value="New MWEB Address">
{{ circular_arrows_svg }} New MWEB Address</button>
</td>
<td colspan=2 class="py-3 px-6 monospace bold select-all">{{ w.mweb_address }}</td>
</tr>
{% endif %} {# / LTC #}
{% if w.cid == '6' %} {# XMR #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">Main Address</td>
<td colspan=2 class="py-3 px-6 monospace bold select-all">{{ w.main_address }}</td>
@ -249,12 +267,7 @@
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">
<button type="submit" class="flex flex-wrap justify-center py-2 px-4 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" name="newaddr_{{ w.cid }}" value="New Subaddress">
<svg class="text-gray-500 w-5 h-5 mr-2" xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24">
<g fill="#ffffff" class="nc-icon-wrapper">
<path fill="#ffffff" d="M12,3c1.989,0,3.873,0.65,5.43,1.833l-3.604,3.393l9.167,0.983L22.562,0l-3.655,3.442 C16.957,1.862,14.545,1,12,1C5.935,1,1,5.935,1,12h2C3,7.037,7.037,3,12,3z"></path>
<path data-color="color-2" d="M12,21c-1.989,0-3.873-0.65-5.43-1.833l3.604-3.393l-9.167-0.983L1.438,24l3.655-3.442 C7.043,22.138,9.455,23,12,23c6.065,0,11-4.935,11-11h-2C21,16.963,16.963,21,12,21z"></path>
</g>
</svg> New {{ w.ticker }} Sub Address </button>
{{ circular_arrows_svg }} New {{ w.ticker }} Sub Address </button>
</td>
<td colspan=2 class="py-3 px-6 monospace select-all">{{ w.deposit_address }}</td>
</tr>
@ -262,12 +275,7 @@
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">
<button type="submit" class="flex flex-wrap justify-center py-2 px-4 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" name="newaddr_{{ w.cid }}" value="New Deposit Address">
<svg class="text-gray-500 w-5 h-5 mr-2" xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24">
<g fill="#ffffff" class="nc-icon-wrapper">
<path fill="#ffffff" d="M12,3c1.989,0,3.873,0.65,5.43,1.833l-3.604,3.393l9.167,0.983L22.562,0l-3.655,3.442 C16.957,1.862,14.545,1,12,1C5.935,1,1,5.935,1,12h2C3,7.037,7.037,3,12,3z"></path>
<path data-color="color-2" d="M12,21c-1.989,0-3.873-0.65-5.43-1.833l3.604-3.393l-9.167-0.983L1.438,24l3.655-3.442 C7.043,22.138,9.455,23,12,23c6.065,0,11-4.935,11-11h-2C21,16.963,16.963,21,12,21z"></path>
</g>
</svg>New {{ w.ticker }} Deposit Address</button>
{{ circular_arrows_svg }} New {{ w.ticker }} Deposit Address</button>
</td>
<td colspan=2 class="py-3 px-6 monospace bold select-all" id="deposit_address">{{ w.deposit_address }}</td>
</tr>
@ -342,16 +350,14 @@
</td>
<td></td>
</tr>
{% if w.cid == '1' %}
{% if w.cid == '1' %} {# PART #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-3 px-6 bold">Type From -> To:</td>
<td class="py-3 px-6">
<div class="w-full md:flex-1">
<div class="relative">
<svg class="absolute right-4 top-1/2 transform -translate-y-1/2" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.3333 6.1133C11.2084 5.98913 11.0395 5.91943 10.8633 5.91943C10.6872 5.91943 10.5182 5.98913 10.3933 6.1133L8.00001 8.47329L5.64001 6.1133C5.5151 5.98913 5.34613 5.91943 5.17001 5.91943C4.99388 5.91943 4.82491 5.98913 4.70001 6.1133C4.63752 6.17527 4.58792 6.249 4.55408 6.33024C4.52023 6.41148 4.50281 6.49862 4.50281 6.58663C4.50281 6.67464 4.52023 6.76177 4.55408 6.84301C4.58792 6.92425 4.63752 6.99799 4.70001 7.05996L7.52667 9.88663C7.58865 9.94911 7.66238 9.99871 7.74362 10.0326C7.82486 10.0664 7.912 10.0838 8.00001 10.0838C8.08801 10.0838 8.17515 10.0664 8.25639 10.0326C8.33763 9.99871 8.41136 9.94911 8.47334 9.88663L11.3333 7.05996C11.3958 6.99799 11.4454 6.92425 11.4793 6.84301C11.5131 6.76177 11.5305 6.67464 11.5305 6.58663C11.5305 6.49862 11.5131 6.41148 11.4793 6.33024C11.4454 6.249 11.3958 6.17527 11.3333 6.1133Z" fill="#8896AB"></path>
</svg>
<select class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" name="withdraw_type_from_{{ w.cid }}">
{{ select_box_arrow_svg }}
<select class="{{ select_box_class }}" name="withdraw_type_from_{{ w.cid }}">
<option value="plain" {% if w.wd_type_from=='plain' %} selected{% endif %}>Plain</option>
<option value="blind" {% if w.wd_type_from=='blind' %} selected{% endif %}>Blind</option>
<option value="anon" {% if w.wd_type_from=='anon' %} selected{% endif %}>Anon</option>
@ -362,10 +368,8 @@
<td class="py-3 px-6">
<div class="w-full md:flex-1">
<div class="relative">
<svg class="absolute right-4 top-1/2 transform -translate-y-1/2" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.3333 6.1133C11.2084 5.98913 11.0395 5.91943 10.8633 5.91943C10.6872 5.91943 10.5182 5.98913 10.3933 6.1133L8.00001 8.47329L5.64001 6.1133C5.5151 5.98913 5.34613 5.91943 5.17001 5.91943C4.99388 5.91943 4.82491 5.98913 4.70001 6.1133C4.63752 6.17527 4.58792 6.249 4.55408 6.33024C4.52023 6.41148 4.50281 6.49862 4.50281 6.58663C4.50281 6.67464 4.52023 6.76177 4.55408 6.84301C4.58792 6.92425 4.63752 6.99799 4.70001 7.05996L7.52667 9.88663C7.58865 9.94911 7.66238 9.99871 7.74362 10.0326C7.82486 10.0664 7.912 10.0838 8.00001 10.0838C8.08801 10.0838 8.17515 10.0664 8.25639 10.0326C8.33763 9.99871 8.41136 9.94911 8.47334 9.88663L11.3333 7.05996C11.3958 6.99799 11.4454 6.92425 11.4793 6.84301C11.5131 6.76177 11.5305 6.67464 11.5305 6.58663C11.5305 6.49862 11.5131 6.41148 11.4793 6.33024C11.4454 6.249 11.3958 6.17527 11.3333 6.1133Z" fill="#8896AB"></path>
</svg>
<select class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" name="withdraw_type_to_{{ w.cid }}">
{{ select_box_arrow_svg }}
<select class="{{ select_box_class }}" name="withdraw_type_to_{{ w.cid }}">
<option value="plain" {% if w.wd_type_to=='plain' %} selected{% endif %}>Plain</option>
<option value="blind" {% if w.wd_type_to=='blind' %} selected{% endif %}>Blind</option>
<option value="anon" {% if w.wd_type_to=='anon' %} selected{% endif %}>Anon</option>
@ -374,7 +378,23 @@
</div>
</td>
</tr>
{% endif %}
{% endif %} {# / PART #}
{% if w.cid == '3' %} {# LTC #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-3 px-6 bold">Type From:</td>
<td class="py-3 px-6">
<div class="w-full md:flex-1">
<div class="relative">
{{ select_box_arrow_svg }}
<select class="{{ select_box_class }}" name="withdraw_type_from_{{ w.cid }}">
<option value="plain" {% if w.wd_type_from=='plain' %} selected{% endif %}>Plain</option>
<option value="mweb" {% if w.wd_type_from=='mweb' %} selected{% endif %}>MWEB</option>
</select>
</div>
</div>
</td>
</tr>
{% endif %} {# / LTC #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">Fee Rate:</td>
<td class="py-3 px-6 bold">{{ w.fee_rate }}</td>
@ -392,7 +412,7 @@
</div>
</div>
</section>
{% if w.cid != '6' %}
{% if w.cid != '6' %} {# !XMR #}
{% if w.show_utxo_groups %}
<section class="p-6">
<div class="flex flex-wrap items-center">
@ -647,4 +667,4 @@ document.addEventListener('DOMContentLoaded', () => {
}
</script>
</body>
</html>
</html>

29
basicswap/templates/wallets.html

@ -113,7 +113,7 @@
<div class="bold inline-block py-1 px-2 rounded-full bg-green-100 text-xs text-green-500 dark:bg-gray-500 dark:text-green-500 usd-value"></div>
</div>
{% endif %}
{% if w.cid == '1' %}
{% if w.cid == '1' %} {# PART #}
<div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-medium dark:text-white">Blind Balance:</h4>
<span class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}">{{ w.blind_balance }} {{ w.ticker }}</span>
@ -130,7 +130,7 @@
<div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-bold text-green-500 dark:text-green-500">Blind Unconfirmed USD value:</h4>
<div class="bold inline-block py-1 px-2 rounded-full bg-green-100 text-xs text-green-500 dark:bg-gray-500 dark:text-green-500 usd-value"></div>
</div>
</div>
{% endif %}
<div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-medium dark:text-white">Anon Balance:</h4>
@ -149,9 +149,30 @@
<div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-bold text-green-500 dark:text-green-500">Anon Pending USD value:</h4>
<div class="bold inline-block py-1 px-2 rounded-full bg-green-100 text-xs text-green-500 dark:bg-gray-500 dark:text-green-500 usd-value"></div>
</div>
</div>
{% endif %}
{% endif %} {# / PART #}
{% if w.cid == '3' %} {# LTC #}
<div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-medium dark:text-white">MWEB Balance:</h4>
<span class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}">{{ w.mweb_balance }} {{ w.ticker }}</span>
</div>
<div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-medium dark:text-white">MWEB USD value:</h4>
<div class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 usd-value"></div>
</div>
{% if w.mweb_pending %}
<div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-bold text-green-500 dark:text-green-500">MWEB Pending:</h4>
<span class="bold inline-block py-1 px-2 rounded-full bg-green-100 text-xs text-green-500 dark:bg-gray-500 dark:text-green-500 coinname-value" data-coinname="{{ w.name }}">
+{{ w.mweb_pending }} {{ w.ticker }}</span>
</div>
<div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-bold text-green-500 dark:text-green-500">MWEB Pending USD value:</h4>
<div class="bold inline-block py-1 px-2 rounded-full bg-green-100 text-xs text-green-500 dark:bg-gray-500 dark:text-green-500 usd-value"></div>
</div>
{% endif %}
{% endif %} {# / LTC #}
<div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-medium dark:text-white">Blocks:</h4>
<span class="inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200">{{ w.blocks }}{% if w.known_block_count %} / {{ w.known_block_count }}{% endif %}</span>
@ -409,4 +430,4 @@ window.onload = async () => {
};
</script>
</body>
</html>
</html>

32
basicswap/ui/page_wallet.py

@ -51,12 +51,16 @@ def format_wallet_data(swap_client, ci, w):
if ci.coin_type() == Coins.PART:
wf['stealth_address'] = w.get('stealth_address', '?')
wf['blind_balance'] = "{:.8f}".format(float(w['blind_balance']))
wf['blind_balance'] = w.get('blind_balance', '?')
if 'blind_unconfirmed' in w and float(w['blind_unconfirmed']) > 0.0:
wf['blind_unconfirmed'] = w['blind_unconfirmed']
wf['anon_balance'] = w.get('anon_balance', '?')
if 'anon_pending' in w and float(w['anon_pending']) > 0.0:
wf['anon_pending'] = w['anon_pending']
elif ci.coin_type() == Coins.LTC:
wf['mweb_address'] = w.get('mweb_address', '?')
wf['mweb_balance'] = w.get('mweb_balance', '?')
wf['mweb_pending'] = w.get('mweb_pending', '?')
checkAddressesOwned(swap_client, ci, wf)
return wf
@ -128,6 +132,8 @@ def page_wallet(self, url_split, post_string):
if bytes('newaddr_' + cid, 'utf-8') in form_data:
swap_client.cacheNewAddressForCoin(coin_id)
elif bytes('newmwebaddr_' + cid, 'utf-8') in form_data:
swap_client.cacheNewStealthAddressForCoin(coin_id)
elif bytes('reseed_' + cid, 'utf-8') in form_data:
try:
swap_client.reseedWallet(coin_id)
@ -158,22 +164,28 @@ def page_wallet(self, url_split, post_string):
page_data['wd_type_to_' + cid] = type_to
except Exception as e:
err_messages.append('Missing type')
elif coin_id == Coins.LTC:
try:
type_from = form_data[bytes('withdraw_type_from_' + cid, 'utf-8')][0].decode('utf-8')
page_data['wd_type_from_' + cid] = type_from
except Exception as e:
err_messages.append('Missing type')
if len(messages) == 0:
ci = swap_client.ci(coin_id)
ticker = ci.ticker()
if coin_id == Coins.PART:
try:
try:
if coin_id == Coins.PART:
txid = swap_client.withdrawParticl(type_from, type_to, value, address, subfee)
messages.append('Withdrew {} {} ({} to {}) to address {}<br/>In txid: {}'.format(value, ticker, type_from, type_to, address, txid))
except Exception as e:
err_messages.append(str(e))
else:
try:
elif coin_id == Coins.LTC:
txid = swap_client.withdrawLTC(type_from, value, address, subfee)
messages.append('Withdrew {} {} (from {}) to address {}<br/>In txid: {}'.format(value, ticker, type_from, address, txid))
else:
txid = swap_client.withdrawCoin(coin_id, value, address, subfee)
messages.append('Withdrew {} {} to address {}<br/>In txid: {}'.format(value, ticker, address, txid))
except Exception as e:
err_messages.append(str(e))
except Exception as e:
err_messages.append(str(e))
swap_client.updateWalletsInfo(True, coin_id)
elif have_data_entry(form_data, 'showutxogroups'):
show_utxo_groups = True
@ -227,6 +239,8 @@ def page_wallet(self, url_split, post_string):
if k == Coins.XMR:
wallet_data['main_address'] = w.get('main_address', 'Refresh necessary')
elif k == Coins.LTC:
wallet_data['mweb_address'] = w.get('mweb_address', 'Refresh necessary')
if 'wd_type_from_' + cid in page_data:
wallet_data['wd_type_from'] = page_data['wd_type_from_' + cid]

7
basicswap/ui/util.py

@ -419,6 +419,8 @@ def getCoinName(c):
return chainparams[Coins.PART]['name'].capitalize() + ' Anon'
if c == Coins.PART_BLIND:
return chainparams[Coins.PART]['name'].capitalize() + ' Blind'
if c == Coins.LTC_MWEB:
return chainparams[Coins.LTC]['name'].capitalize() + ' MWEB'
coin_chainparams = chainparams[c]
if coin_chainparams.get('use_ticker_as_name', False):
@ -441,6 +443,11 @@ def listAvailableCoins(swap_client, with_variants=True, split_from=False):
coins.append((int(v), getCoinName(v)))
if split_from and v not in invalid_coins_from:
coins_from.append(coins[-1])
if with_variants and k == Coins.LTC:
for v in (Coins.LTC_MWEB, ):
coins.append((int(v), getCoinName(v)))
if split_from and v not in invalid_coins_from:
coins_from.append(coins[-1])
if split_from:
return coins_from, coins
return coins

24
bin/basicswap_prepare.py

@ -42,7 +42,7 @@ PARTICL_VERSION = os.getenv('PARTICL_VERSION', '23.2.7.0')
PARTICL_VERSION_TAG = os.getenv('PARTICL_VERSION_TAG', '')
PARTICL_LINUX_EXTRA = os.getenv('PARTICL_LINUX_EXTRA', 'nousb')
LITECOIN_VERSION = os.getenv('LITECOIN_VERSION', '0.21.2')
LITECOIN_VERSION = os.getenv('LITECOIN_VERSION', '0.21.2.2')
LITECOIN_VERSION_TAG = os.getenv('LITECOIN_VERSION_TAG', '')
BITCOIN_VERSION = os.getenv('BITCOIN_VERSION', '23.0')
@ -602,7 +602,7 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
assert_url = 'https://raw.githubusercontent.com/particl/gitian.sigs/master/%s-%s/%s/%s' % (version + version_tag, os_dir_name, signing_key_name, assert_filename)
elif coin == 'litecoin':
release_url = 'https://download.litecoin.org/litecoin-{}/{}/{}'.format(version, os_name, release_filename)
assert_filename = '{}-core-{}-{}-build.assert'.format(coin, os_name, version.rsplit('.', 1)[0])
assert_filename = '{}-core-{}-{}-build.assert'.format(coin, os_name, '.'.join(version.split('.')[:2]))
assert_url = 'https://raw.githubusercontent.com/litecoin-project/gitian.sigs.ltc/master/%s-%s/%s/%s' % (version, os_dir_name, signing_key_name, assert_filename)
elif coin == 'bitcoin':
release_url = 'https://bitcoincore.org/bin/bitcoin-core-{}/{}'.format(version, release_filename)
@ -1141,6 +1141,12 @@ def test_particl_encryption(data_dir, settings, chain, use_tor_proxy):
finalise_daemon(d)
def encrypt_wallet(swap_client, coin_type) -> None:
ci = swap_client.ci(coin_type)
ci.changeWalletPassword('', WALLET_ENCRYPTION_PWD)
ci.unlockWallet(WALLET_ENCRYPTION_PWD)
def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings, chain, use_tor_proxy):
swap_client = None
daemons = []
@ -1185,16 +1191,18 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
if len(wallets) < 1:
logger.info('Creating wallet.dat for {}.'.format(getCoinName(c)))
if c == Coins.BTC:
if c in (Coins.BTC, Coins.LTC):
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
swap_client.callcoinrpc(c, 'createwallet', ['wallet.dat', False, True, '', False, False])
swap_client.callcoinrpc(c, 'createwallet', ['wallet.dat', False, True, WALLET_ENCRYPTION_PWD, False, False])
swap_client.ci(c).unlockWallet(WALLET_ENCRYPTION_PWD)
else:
swap_client.callcoinrpc(c, 'createwallet', ['wallet.dat'])
if WALLET_ENCRYPTION_PWD != '':
encrypt_wallet(swap_client, c)
if WALLET_ENCRYPTION_PWD != '':
ci = swap_client.ci(c)
ci.changeWalletPassword('', WALLET_ENCRYPTION_PWD)
ci.unlockWallet(WALLET_ENCRYPTION_PWD)
if c == Coins.LTC:
password = WALLET_ENCRYPTION_PWD if WALLET_ENCRYPTION_PWD != '' else None
swap_client.ci(Coins.LTC_MWEB).init_wallet(password)
if c == Coins.PART:
if 'particl' in with_coins:

8
doc/release-notes.md

@ -1,3 +1,11 @@
0.12.4
==============
- LTC creates a new wallet to hold MWEB balance.
- MWEB wallet should be be automatically created at startup or when unlocked if system is encrypted.
0.12.3
==============

2
tests/basicswap/extended/test_dash.py

@ -672,7 +672,7 @@ class Test(unittest.TestCase):
# Verify expected inputs were used
bid, offer = swap_clients[2].getBidAndOffer(bid_id)
assert (bid.initiate_tx)
wtx = ci_from.rpc_callback('gettransaction', [bid.initiate_tx.txid.hex(),])
wtx = ci_from.rpc_wallet('gettransaction', [bid.initiate_tx.txid.hex(),])
itx_after = ci_from.describeTx(wtx['hex'])
assert (len(itx_after['vin']) == len(itx_decoded['vin']))
for i, txin in enumerate(itx_decoded['vin']):

2
tests/basicswap/extended/test_nav.py

@ -902,7 +902,7 @@ class Test(TestFunctions):
# Verify expected inputs were used
bid, offer = swap_clients[2].getBidAndOffer(bid_id)
assert (bid.initiate_tx)
wtx = ci_from.rpc_callback('gettransaction', [bid.initiate_tx.txid.hex(),])
wtx = ci_from.rpc('gettransaction', [bid.initiate_tx.txid.hex(),])
itx_after = ci_from.describeTx(wtx['hex'])
assert (len(itx_after['vin']) == len(itx_decoded['vin']))
for i, txin in enumerate(itx_decoded['vin']):

2
tests/basicswap/extended/test_pivx.py

@ -677,7 +677,7 @@ class Test(unittest.TestCase):
# Verify expected inputs were used
bid, offer = swap_clients[2].getBidAndOffer(bid_id)
assert (bid.initiate_tx)
wtx = ci_from.rpc_callback('gettransaction', [bid.initiate_tx.txid.hex(),])
wtx = ci_from.rpc('gettransaction', [bid.initiate_tx.txid.hex(),])
itx_after = ci_from.describeTx(wtx['hex'])
assert (len(itx_after['vin']) == len(itx_decoded['vin']))
for i, txin in enumerate(itx_decoded['vin']):

213
tests/basicswap/test_btc_xmr.py

@ -60,6 +60,16 @@ class TestFunctions(BaseTest):
node_a_id = 0
node_b_id = 1
def callnoderpc(self, method, params=[], wallet=None, node_id=0):
return callnoderpc(node_id, method, params, wallet, self.base_rpc_port)
def mineBlock(self, num_blocks=1):
self.callnoderpc('generatetoaddress', [num_blocks, self.btc_addr])
def check_softfork_active(self, feature_name):
deploymentinfo = self.callnoderpc('getdeploymentinfo')
assert (deploymentinfo['deployments'][feature_name]['active'] is True)
def getBalance(self, js_wallets, coin) -> float:
if coin == Coins.PART_BLIND:
coin_ticker: str = 'PART'
@ -79,12 +89,6 @@ class TestFunctions(BaseTest):
return float(js_wallets[coin_ticker][balance_type]) + float(js_wallets[coin_ticker][unconfirmed_name])
def callnoderpc(self, method, params=[], wallet=None, node_id=0):
return callnoderpc(node_id, method, params, wallet, self.base_rpc_port)
def mineBlock(self, num_blocks=1):
self.callnoderpc('generatetoaddress', [num_blocks, self.btc_addr])
def prepare_balance(self, coin, amount: float, port_target_node: int, port_take_from_node: int, test_balance: bool = True) -> None:
delay_iterations = 100 if coin == Coins.NAV else 20
delay_time = 5 if coin == Coins.NAV else 3
@ -149,8 +153,8 @@ class TestFunctions(BaseTest):
js_1 = read_json_api(1800 + id_bidder, 'wallets')
node1_from_before: float = self.getBalance(js_1, coin_from)
node0_sent_messages_before: int = ci_part0.rpc_callback('smsgoutbox', ['count',])['num_messages']
node1_sent_messages_before: int = ci_part1.rpc_callback('smsgoutbox', ['count',])['num_messages']
node0_sent_messages_before: int = ci_part0.rpc('smsgoutbox', ['count',])['num_messages']
node1_sent_messages_before: int = ci_part1.rpc('smsgoutbox', ['count',])['num_messages']
amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1)
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
@ -224,8 +228,8 @@ class TestFunctions(BaseTest):
if False: # TODO: set stakeaddress and xmr rewards to non wallet addresses
assert (node1_to_after < node1_to_before - amount_to_float)
node0_sent_messages_after: int = ci_part0.rpc_callback('smsgoutbox', ['count',])['num_messages']
node1_sent_messages_after: int = ci_part1.rpc_callback('smsgoutbox', ['count',])['num_messages']
node0_sent_messages_after: int = ci_part0.rpc('smsgoutbox', ['count',])['num_messages']
node1_sent_messages_after: int = ci_part1.rpc('smsgoutbox', ['count',])['num_messages']
node0_sent_messages: int = node0_sent_messages_after - node0_sent_messages_before
node1_sent_messages: int = node1_sent_messages_after - node1_sent_messages_before
split_msgs: int = 2 if (ci_from.curve_type() != Curves.secp256k1 or ci_to.curve_type() != Curves.secp256k1) else 0
@ -434,21 +438,22 @@ class BasicSwapTest(TestFunctions):
def test_001_nested_segwit(self):
# p2sh-p2wpkh
logging.info('---------- Test {} p2sh nested segwit'.format(self.test_coin_from.name))
ci = self.swap_clients[0].ci(self.test_coin_from)
addr_p2sh_segwit = self.callnoderpc('getnewaddress', ['segwit test', 'p2sh-segwit'])
addr_info = self.callnoderpc('getaddressinfo', [addr_p2sh_segwit, ])
addr_p2sh_segwit = ci.rpc_wallet('getnewaddress', ['segwit test', 'p2sh-segwit'])
addr_info = ci.rpc_wallet('getaddressinfo', [addr_p2sh_segwit, ])
assert addr_info['script'] == 'witness_v0_keyhash'
txid = self.callnoderpc('sendtoaddress', [addr_p2sh_segwit, 1.0])
txid = ci.rpc_wallet('sendtoaddress', [addr_p2sh_segwit, 1.0])
assert len(txid) == 64
self.mineBlock()
ro = self.callnoderpc('scantxoutset', ['start', ['addr({})'.format(addr_p2sh_segwit)]])
ro = ci.rpc('scantxoutset', ['start', ['addr({})'.format(addr_p2sh_segwit)]])
assert (len(ro['unspents']) == 1)
assert (ro['unspents'][0]['txid'] == txid)
tx_wallet = self.callnoderpc('gettransaction', [txid, ])['hex']
tx = self.callnoderpc('decoderawtransaction', [tx_wallet, ])
tx_wallet = ci.rpc_wallet('gettransaction', [txid, ])['hex']
tx = ci.rpc('decoderawtransaction', [tx_wallet, ])
prevout_n = -1
for txo in tx['vout']:
@ -457,14 +462,14 @@ class BasicSwapTest(TestFunctions):
break
assert prevout_n > -1
tx_funded = self.callnoderpc('createrawtransaction', [[{'txid': txid, 'vout': prevout_n}], {addr_p2sh_segwit: 0.99}])
tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded, ])['hex']
tx_funded_decoded = self.callnoderpc('decoderawtransaction', [tx_funded, ])
tx_signed_decoded = self.callnoderpc('decoderawtransaction', [tx_signed, ])
tx_funded = ci.rpc('createrawtransaction', [[{'txid': txid, 'vout': prevout_n}], {addr_p2sh_segwit: 0.99}])
tx_signed = ci.rpc_wallet('signrawtransactionwithwallet', [tx_funded, ])['hex']
tx_funded_decoded = ci.rpc('decoderawtransaction', [tx_funded, ])
tx_signed_decoded = ci.rpc('decoderawtransaction', [tx_signed, ])
assert tx_funded_decoded['txid'] != tx_signed_decoded['txid']
# Add scriptsig for txids to match
addr_p2sh_segwit_info = self.callnoderpc('getaddressinfo', [addr_p2sh_segwit, ])
addr_p2sh_segwit_info = ci.rpc_wallet('getaddressinfo', [addr_p2sh_segwit, ])
decoded_tx = FromHex(CTransaction(), tx_funded)
decoded_tx.vin[0].scriptSig = bytes.fromhex('16' + addr_p2sh_segwit_info['hex'])
txid_with_scriptsig = decoded_tx.rehash()
@ -473,18 +478,19 @@ class BasicSwapTest(TestFunctions):
def test_002_native_segwit(self):
# p2wpkh
logging.info('---------- Test {} p2sh native segwit'.format(self.test_coin_from.name))
ci = self.swap_clients[0].ci(self.test_coin_from)
addr_segwit = self.callnoderpc('getnewaddress', ['segwit test', 'bech32'])
addr_info = self.callnoderpc('getaddressinfo', [addr_segwit, ])
addr_segwit = ci.rpc_wallet('getnewaddress', ['segwit test', 'bech32'])
addr_info = ci.rpc_wallet('getaddressinfo', [addr_segwit, ])
assert addr_info['iswitness'] is True
txid = self.callnoderpc('sendtoaddress', [addr_segwit, 1.0])
txid = ci.rpc_wallet('sendtoaddress', [addr_segwit, 1.0])
assert len(txid) == 64
tx_wallet = self.callnoderpc('gettransaction', [txid, ])['hex']
tx = self.callnoderpc('decoderawtransaction', [tx_wallet, ])
tx_wallet = ci.rpc_wallet('gettransaction', [txid, ])['hex']
tx = ci.rpc('decoderawtransaction', [tx_wallet, ])
self.mineBlock()
ro = self.callnoderpc('scantxoutset', ['start', ['addr({})'.format(addr_segwit)]])
ro = ci.rpc('scantxoutset', ['start', ['addr({})'.format(addr_segwit)]])
assert (len(ro['unspents']) == 1)
assert (ro['unspents'][0]['txid'] == txid)
@ -495,19 +501,17 @@ class BasicSwapTest(TestFunctions):
break
assert prevout_n > -1
tx_funded = self.callnoderpc('createrawtransaction', [[{'txid': txid, 'vout': prevout_n}], {addr_segwit: 0.99}])
tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded, ])['hex']
tx_funded_decoded = self.callnoderpc('decoderawtransaction', [tx_funded, ])
tx_signed_decoded = self.callnoderpc('decoderawtransaction', [tx_signed, ])
tx_funded = ci.rpc('createrawtransaction', [[{'txid': txid, 'vout': prevout_n}], {addr_segwit: 0.99}])
tx_signed = ci.rpc_wallet('signrawtransactionwithwallet', [tx_funded, ])['hex']
tx_funded_decoded = ci.rpc('decoderawtransaction', [tx_funded, ])
tx_signed_decoded = ci.rpc('decoderawtransaction', [tx_signed, ])
assert tx_funded_decoded['txid'] == tx_signed_decoded['txid']
def test_003_cltv(self):
logging.info('---------- Test {} cltv'.format(self.test_coin_from.name))
ci = self.swap_clients[0].ci(self.test_coin_from)
deploymentinfo = self.callnoderpc('getdeploymentinfo')
bip65_active = deploymentinfo['deployments']['bip65']['active']
assert (bip65_active)
self.check_softfork_active('bip65')
chain_height = self.callnoderpc('getblockcount')
script = CScript([chain_height + 3, OP_CHECKLOCKTIMEVERIFY, ])
@ -517,12 +521,12 @@ class BasicSwapTest(TestFunctions):
tx.nVersion = ci.txVersion()
tx.vout.append(ci.txoType()(ci.make_int(1.1), script_dest))
tx_hex = ToHex(tx)
tx_funded = self.callnoderpc('fundrawtransaction', [tx_hex])
tx_funded = ci.rpc_wallet('fundrawtransaction', [tx_hex])
utxo_pos = 0 if tx_funded['changepos'] == 1 else 1
tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded['hex'], ])['hex']
txid = self.callnoderpc('sendrawtransaction', [tx_signed, ])
tx_signed = ci.rpc_wallet('signrawtransactionwithwallet', [tx_funded['hex'], ])['hex']
txid = ci.rpc('sendrawtransaction', [tx_signed, ])
addr_out = self.callnoderpc('getnewaddress', ['cltv test', 'bech32'])
addr_out = ci.rpc_wallet('getnewaddress', ['cltv test', 'bech32'])
pkh = ci.decodeSegwitAddress(addr_out)
script_out = ci.getScriptForPubkeyHash(pkh)
@ -548,15 +552,15 @@ class BasicSwapTest(TestFunctions):
self.mineBlock(5)
try:
txid = self.callnoderpc('sendrawtransaction', [tx_spend_invalid_hex, ])
txid = ci.rpc('sendrawtransaction', [tx_spend_invalid_hex, ])
except Exception as e:
assert ('Locktime requirement not satisfied' in str(e))
else:
assert False, 'Should fail'
txid = self.callnoderpc('sendrawtransaction', [tx_spend_hex, ])
txid = ci.rpc('sendrawtransaction', [tx_spend_hex, ])
self.mineBlock()
ro = self.callnoderpc('listreceivedbyaddress', [0, ])
ro = ci.rpc_wallet('listreceivedbyaddress', [0, ])
sum_addr = 0
for entry in ro:
if entry['address'] == addr_out:
@ -564,7 +568,7 @@ class BasicSwapTest(TestFunctions):
assert (sum_addr == 1.0999)
# Ensure tx was mined
tx_wallet = self.callnoderpc('gettransaction', [txid, ])
tx_wallet = ci.rpc_wallet('gettransaction', [txid, ])
assert (len(tx_wallet['blockhash']) == 64)
def test_004_csv(self):
@ -572,6 +576,8 @@ class BasicSwapTest(TestFunctions):
swap_clients = self.swap_clients
ci = self.swap_clients[0].ci(self.test_coin_from)
self.check_softfork_active('bip66')
script = CScript([3, OP_CHECKSEQUENCEVERIFY, ])
script_dest = ci.getScriptDest(script)
@ -579,17 +585,17 @@ class BasicSwapTest(TestFunctions):
tx.nVersion = ci.txVersion()
tx.vout.append(ci.txoType()(ci.make_int(1.1), script_dest))
tx_hex = ToHex(tx)
tx_funded = self.callnoderpc('fundrawtransaction', [tx_hex])
tx_funded = ci.rpc_wallet('fundrawtransaction', [tx_hex])
utxo_pos = 0 if tx_funded['changepos'] == 1 else 1
tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded['hex'], ])['hex']
txid = self.callnoderpc('sendrawtransaction', [tx_signed, ])
tx_signed = ci.rpc_wallet('signrawtransactionwithwallet', [tx_funded['hex'], ])['hex']
txid = ci.rpc('sendrawtransaction', [tx_signed, ])
addr_out = self.callnoderpc('getnewaddress', ['csv test', 'bech32'])
addr_out = ci.rpc_wallet('getnewaddress', ['csv test', 'bech32'])
pkh = ci.decodeSegwitAddress(addr_out)
script_out = ci.getScriptForPubkeyHash(pkh)
# Double check output type
prev_tx = self.callnoderpc('decoderawtransaction', [tx_signed, ])
prev_tx = ci.rpc('decoderawtransaction', [tx_signed, ])
assert (prev_tx['vout'][utxo_pos]['scriptPubKey']['type'] == 'witness_v0_scripthash')
tx_spend = CTransaction()
@ -601,16 +607,16 @@ class BasicSwapTest(TestFunctions):
tx_spend.wit.vtxinwit[0].scriptWitness.stack = [script, ]
tx_spend_hex = ToHex(tx_spend)
try:
txid = self.callnoderpc('sendrawtransaction', [tx_spend_hex, ])
txid = ci.rpc('sendrawtransaction', [tx_spend_hex, ])
except Exception as e:
assert ('non-BIP68-final' in str(e))
else:
assert False, 'Should fail'
self.mineBlock(3)
txid = self.callnoderpc('sendrawtransaction', [tx_spend_hex, ])
txid = ci.rpc('sendrawtransaction', [tx_spend_hex, ])
self.mineBlock(1)
ro = self.callnoderpc('listreceivedbyaddress', [0, ])
ro = ci.rpc_wallet('listreceivedbyaddress', [0, ])
sum_addr = 0
for entry in ro:
if entry['address'] == addr_out:
@ -618,20 +624,22 @@ class BasicSwapTest(TestFunctions):
assert (sum_addr == 1.0999)
# Ensure tx was mined
tx_wallet = self.callnoderpc('gettransaction', [txid, ])
tx_wallet = ci.rpc_wallet('gettransaction', [txid, ])
assert (len(tx_wallet['blockhash']) == 64)
def test_005_watchonly(self):
logging.info('---------- Test {} watchonly'.format(self.test_coin_from.name))
addr = self.callnoderpc('getnewaddress', ['watchonly test', 'bech32'])
ro = self.callnoderpc('importaddress', [addr, '', False], node_id=1)
txid = self.callnoderpc('sendtoaddress', [addr, 1.0])
tx_hex = self.callnoderpc('getrawtransaction', [txid, ])
self.callnoderpc('sendrawtransaction', [tx_hex, ], node_id=1)
ro = self.callnoderpc('gettransaction', [txid, ], node_id=1)
ci = self.swap_clients[0].ci(self.test_coin_from)
ci1 = self.swap_clients[1].ci(self.test_coin_from)
addr = ci.rpc_wallet('getnewaddress', ['watchonly test', 'bech32'])
ro = ci1.rpc_wallet('importaddress', [addr, '', False])
txid = ci.rpc_wallet('sendtoaddress', [addr, 1.0])
tx_hex = ci.rpc('getrawtransaction', [txid, ])
ci1.rpc_wallet('sendrawtransaction', [tx_hex, ])
ro = ci1.rpc_wallet('gettransaction', [txid, ])
assert (ro['txid'] == txid)
balances = self.callnoderpc('getbalances', node_id=1)
balances = ci1.rpc_wallet('getbalances')
assert (balances['watchonly']['trusted'] + balances['watchonly']['untrusted_pending'] >= 1.0)
def test_006_getblock_verbosity(self):
@ -643,6 +651,7 @@ class BasicSwapTest(TestFunctions):
def test_007_hdwallet(self):
logging.info('---------- Test {} hdwallet'.format(self.test_coin_from.name))
ci = self.swap_clients[0].ci(self.test_coin_from)
test_seed = '8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b'
test_wif = self.swap_clients[0].ci(self.test_coin_from).encodeKey(bytes.fromhex(test_seed))
@ -657,7 +666,7 @@ class BasicSwapTest(TestFunctions):
self.swap_clients[0].initialiseWallet(Coins.BTC, raise_errors=True)
assert self.swap_clients[0].checkWalletSeed(Coins.BTC) is True
for i in range(1500):
self.callnoderpc('getnewaddress')
ci.rpc_wallet('getnewaddress')
assert self.swap_clients[0].checkWalletSeed(Coins.BTC) is True
rv = read_json_api(1800, 'getcoinseed', {'coin': 'XMR'})
@ -667,38 +676,44 @@ class BasicSwapTest(TestFunctions):
logging.info('---------- Test {} gettxout'.format(self.test_coin_from.name))
swap_client = self.swap_clients[0]
ci = swap_client.ci(self.test_coin_from)
addr_1 = self.callnoderpc('getnewaddress', ['gettxout test 1',])
txid = self.callnoderpc('sendtoaddress', [addr_1, 1.0])
addr_1 = ci.rpc_wallet('getnewaddress', ['gettxout test 1',])
txid = ci.rpc_wallet('sendtoaddress', [addr_1, 1.0])
assert len(txid) == 64
self.mineBlock()
unspents = self.callnoderpc('listunspent', [0, 999999999, [addr_1,]])
unspents = ci.rpc_wallet('listunspent', [0, 999999999, [addr_1,]])
assert (len(unspents) == 1)
utxo = unspents[0]
txout = self.callnoderpc('gettxout', [utxo['txid'], utxo['vout']])
assert (addr_1 == txout['scriptPubKey']['address'])
txout = ci.rpc('gettxout', [utxo['txid'], utxo['vout']])
if 'address' in txout:
assert (addr_1 == txout['scriptPubKey']['address'])
else:
assert (addr_1 in txout['scriptPubKey']['addresses'])
# Spend
addr_2 = self.callnoderpc('getnewaddress', ['gettxout test 2',])
tx_funded = self.callnoderpc('createrawtransaction', [[{'txid': utxo['txid'], 'vout': utxo['vout']}], {addr_2: 0.99}])
tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded,])['hex']
self.callnoderpc('sendrawtransaction', [tx_signed,])
addr_2 = ci.rpc_wallet('getnewaddress', ['gettxout test 2',])
tx_funded = ci.rpc('createrawtransaction', [[{'txid': utxo['txid'], 'vout': utxo['vout']}], {addr_2: 0.99}])
tx_signed = ci.rpc_wallet('signrawtransactionwithwallet', [tx_funded,])['hex']
ci.rpc('sendrawtransaction', [tx_signed,])
# utxo should be unavailable when spent in the mempool
txout = self.callnoderpc('gettxout', [utxo['txid'], utxo['vout']])
txout = ci.rpc('gettxout', [utxo['txid'], utxo['vout']])
assert (txout is None)
def test_009_scantxoutset(self):
logging.info('---------- Test {} scantxoutset'.format(self.test_coin_from.name))
addr_1 = self.callnoderpc('getnewaddress', ['scantxoutset test', ])
txid = self.callnoderpc('sendtoaddress', [addr_1, 1.0])
ci = self.swap_clients[0].ci(self.test_coin_from)
addr_1 = ci.rpc_wallet('getnewaddress', ['scantxoutset test', ])
txid = ci.rpc_wallet('sendtoaddress', [addr_1, 1.0])
assert len(txid) == 64
self.mineBlock()
ro = self.callnoderpc('scantxoutset', ['start', ['addr({})'.format(addr_1)]])
ro = ci.rpc('scantxoutset', ['start', ['addr({})'.format(addr_1)]])
assert (len(ro['unspents']) == 1)
assert (ro['unspents'][0]['txid'] == txid)
@ -712,7 +727,7 @@ class BasicSwapTest(TestFunctions):
amount: int = ci.make_int(random.uniform(0.1, 2.0), r=1)
# Record unspents before createSCLockTx as the used ones will be locked
unspents = self.callnoderpc('listunspent')
unspents = ci.rpc_wallet('listunspent')
# fee_rate is in sats/kvB
fee_rate: int = 1000
@ -728,10 +743,10 @@ class BasicSwapTest(TestFunctions):
lock_tx = ci.fundSCLockTx(lock_tx, fee_rate)
lock_tx = ci.signTxWithWallet(lock_tx)
unspents_after = self.callnoderpc('listunspent')
unspents_after = ci.rpc_wallet('listunspent')
assert (len(unspents) > len(unspents_after))
tx_decoded = self.callnoderpc('decoderawtransaction', [lock_tx.hex()])
tx_decoded = ci.rpc('decoderawtransaction', [lock_tx.hex()])
txid = tx_decoded['txid']
vsize = tx_decoded['vsize']
@ -752,8 +767,8 @@ class BasicSwapTest(TestFunctions):
break
fee_value = in_value - out_value
self.callnoderpc('sendrawtransaction', [lock_tx.hex()])
rv = self.callnoderpc('gettransaction', [txid])
ci.rpc('sendrawtransaction', [lock_tx.hex()])
rv = ci.rpc_wallet('gettransaction', [txid])
wallet_tx_fee = -ci.make_int(rv['fee'])
assert (wallet_tx_fee == fee_value)
@ -765,7 +780,7 @@ class BasicSwapTest(TestFunctions):
lock_spend_tx = ci.createSCLockSpendTx(lock_tx, lock_tx_script, pkh_out, fee_rate, fee_info=fee_info)
vsize_estimated: int = fee_info['vsize']
tx_decoded = self.callnoderpc('decoderawtransaction', [lock_spend_tx.hex()])
tx_decoded = ci.rpc('decoderawtransaction', [lock_spend_tx.hex()])
txid = tx_decoded['txid']
witness_stack = [
@ -775,11 +790,11 @@ class BasicSwapTest(TestFunctions):
lock_tx_script,
]
lock_spend_tx = ci.setTxSignature(lock_spend_tx, witness_stack)
tx_decoded = self.callnoderpc('decoderawtransaction', [lock_spend_tx.hex()])
tx_decoded = ci.rpc('decoderawtransaction', [lock_spend_tx.hex()])
vsize_actual: int = tx_decoded['vsize']
assert (vsize_actual <= vsize_estimated and vsize_estimated - vsize_actual < 4)
assert (self.callnoderpc('sendrawtransaction', [lock_spend_tx.hex()]) == txid)
assert (ci.rpc('sendrawtransaction', [lock_spend_tx.hex()]) == txid)
expect_vsize: int = ci.xmr_swap_a_lock_spend_tx_vsize()
assert (expect_vsize >= vsize_actual)
@ -796,7 +811,7 @@ class BasicSwapTest(TestFunctions):
lock_tx_b_spend = ci.getTransaction(lock_tx_b_spend_txid)
if lock_tx_b_spend is None:
lock_tx_b_spend = ci.getWalletTransaction(lock_tx_b_spend_txid)
lock_tx_b_spend_decoded = self.callnoderpc('decoderawtransaction', [lock_tx_b_spend.hex()])
lock_tx_b_spend_decoded = ci.rpc('decoderawtransaction', [lock_tx_b_spend.hex()])
expect_vsize: int = ci.xmr_swap_b_lock_spend_tx_vsize()
assert (expect_vsize >= lock_tx_b_spend_decoded['vsize'])
@ -816,17 +831,17 @@ class BasicSwapTest(TestFunctions):
tx.nVersion = ci.txVersion()
tx.vout.append(ci.txoType()(ci.make_int(1.1), script_dest))
tx_hex = ToHex(tx)
tx_funded = self.callnoderpc('fundrawtransaction', [tx_hex])
tx_funded = ci.rpc_wallet('fundrawtransaction', [tx_hex])
utxo_pos = 0 if tx_funded['changepos'] == 1 else 1
tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded['hex'], ])['hex']
txid = self.callnoderpc('sendrawtransaction', [tx_signed, ])
tx_signed = ci.rpc_wallet('signrawtransactionwithwallet', [tx_funded['hex'], ])['hex']
txid = ci.rpc('sendrawtransaction', [tx_signed, ])
addr_out = self.callnoderpc('getnewaddress', ['csv test', 'bech32'])
addr_out = ci.rpc_wallet('getnewaddress', ['csv test', 'bech32'])
pkh = ci.decodeSegwitAddress(addr_out)
script_out = ci.getScriptForPubkeyHash(pkh)
# Double check output type
prev_tx = self.callnoderpc('decoderawtransaction', [tx_signed, ])
prev_tx = ci.rpc('decoderawtransaction', [tx_signed, ])
assert (prev_tx['vout'][utxo_pos]['scriptPubKey']['type'] == 'scripthash')
tx_spend = CTransaction()
@ -836,9 +851,9 @@ class BasicSwapTest(TestFunctions):
tx_spend.vout.append(ci.txoType()(ci.make_int(1.0999), script_out))
tx_spend_hex = ToHex(tx_spend)
txid = self.callnoderpc('sendrawtransaction', [tx_spend_hex, ])
txid = ci.rpc('sendrawtransaction', [tx_spend_hex, ])
self.mineBlock(1)
ro = self.callnoderpc('listreceivedbyaddress', [0, ])
ro = ci.rpc_wallet('listreceivedbyaddress', [0, ])
sum_addr = 0
for entry in ro:
if entry['address'] == addr_out:
@ -846,7 +861,7 @@ class BasicSwapTest(TestFunctions):
assert (sum_addr == 1.0999)
# Ensure tx was mined
tx_wallet = self.callnoderpc('gettransaction', [txid, ])
tx_wallet = ci.rpc_wallet('gettransaction', [txid, ])
assert (len(tx_wallet['blockhash']) == 64)
def test_012_p2sh_p2wsh(self):
@ -863,17 +878,17 @@ class BasicSwapTest(TestFunctions):
tx.nVersion = ci.txVersion()
tx.vout.append(ci.txoType()(ci.make_int(1.1), script_dest))
tx_hex = ToHex(tx)
tx_funded = self.callnoderpc('fundrawtransaction', [tx_hex])
tx_funded = ci.rpc_wallet('fundrawtransaction', [tx_hex])
utxo_pos = 0 if tx_funded['changepos'] == 1 else 1
tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded['hex'], ])['hex']
txid = self.callnoderpc('sendrawtransaction', [tx_signed, ])
tx_signed = ci.rpc_wallet('signrawtransactionwithwallet', [tx_funded['hex'], ])['hex']
txid = ci.rpc('sendrawtransaction', [tx_signed, ])
addr_out = self.callnoderpc('getnewaddress', ['csv test', 'bech32'])
addr_out = ci.rpc_wallet('getnewaddress', ['csv test', 'bech32'])
pkh = ci.decodeSegwitAddress(addr_out)
script_out = ci.getScriptForPubkeyHash(pkh)
# Double check output type
prev_tx = self.callnoderpc('decoderawtransaction', [tx_signed, ])
prev_tx = ci.rpc('decoderawtransaction', [tx_signed, ])
assert (prev_tx['vout'][utxo_pos]['scriptPubKey']['type'] == 'scripthash')
tx_spend = CTransaction()
@ -885,9 +900,9 @@ class BasicSwapTest(TestFunctions):
tx_spend.wit.vtxinwit[0].scriptWitness.stack = [script, ]
tx_spend_hex = ToHex(tx_spend)
txid = self.callnoderpc('sendrawtransaction', [tx_spend_hex, ])
txid = ci.rpc('sendrawtransaction', [tx_spend_hex, ])
self.mineBlock(1)
ro = self.callnoderpc('listreceivedbyaddress', [0, ])
ro = ci.rpc_wallet('listreceivedbyaddress', [0, ])
sum_addr = 0
for entry in ro:
if entry['address'] == addr_out:
@ -895,7 +910,7 @@ class BasicSwapTest(TestFunctions):
assert (sum_addr == 1.0999)
# Ensure tx was mined
tx_wallet = self.callnoderpc('gettransaction', [txid, ])
tx_wallet = ci.rpc_wallet('gettransaction', [txid, ])
assert (len(tx_wallet['blockhash']) == 64)
def test_01_a_full_swap(self):
@ -1045,7 +1060,7 @@ class BasicSwapTest(TestFunctions):
# Verify expected inputs were used
bid, _, _, _, _ = swap_clients[2].getXmrBidAndOffer(bid_id)
assert (bid.xmr_a_lock_tx)
wtx = ci.rpc_callback('gettransaction', [bid.xmr_a_lock_tx.txid.hex(),])
wtx = ci.rpc_wallet('gettransaction', [bid.xmr_a_lock_tx.txid.hex(),])
itx_after = ci.describeTx(wtx['hex'])
assert (len(itx_after['vin']) == len(itx_decoded['vin']))
for i, txin in enumerate(itx_decoded['vin']):

146
tests/basicswap/test_ltc_xmr.py

@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (c) 2021-2022 tecnovert
# Copyright (c) 2021-2023 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@ -24,9 +24,10 @@ from tests.basicswap.common import (
wait_for_bid,
wait_for_offer,
wait_for_in_progress,
TEST_HTTP_PORT,
LTC_BASE_RPC_PORT,
)
from .test_btc_xmr import BasicSwapTest, test_delay_event
from .test_btc_xmr import BasicSwapTest, test_delay_event, callnoderpc
logger = logging.getLogger()
@ -37,9 +38,20 @@ class TestLTC(BasicSwapTest):
start_ltc_nodes = True
base_rpc_port = LTC_BASE_RPC_PORT
@classmethod
def prepareExtraCoins(cls):
logging.info('Mining {} chain to height 1352 to activate CVS (BIP66)'.format(cls.test_coin_from.name))
chain_height = callnoderpc(0, 'getblockcount', base_rpc_port=LTC_BASE_RPC_PORT)
num_blocks: int = 1352 - chain_height
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT)
def mineBlock(self, num_blocks=1):
self.callnoderpc('generatetoaddress', [num_blocks, self.ltc_addr])
def check_softfork_active(self, feature_name):
deploymentinfo = self.callnoderpc('getblockchaininfo')
assert (deploymentinfo['softforks'][feature_name]['active'] is True)
def test_001_nested_segwit(self):
logging.info('---------- Test {} p2sh nested segwit'.format(self.test_coin_from.name))
logging.info('Skipped')
@ -47,17 +59,18 @@ class TestLTC(BasicSwapTest):
def test_002_native_segwit(self):
logging.info('---------- Test {} p2sh native segwit'.format(self.test_coin_from.name))
addr_segwit = self.callnoderpc('getnewaddress', ['segwit test', 'bech32'])
addr_info = self.callnoderpc('getaddressinfo', [addr_segwit, ])
ci = self.swap_clients[0].ci(self.test_coin_from)
addr_segwit = ci.rpc_wallet('getnewaddress', ['segwit test', 'bech32'])
addr_info = ci.rpc_wallet('getaddressinfo', [addr_segwit, ])
assert addr_info['iswitness'] is True
txid = self.callnoderpc('sendtoaddress', [addr_segwit, 1.0])
txid = ci.rpc_wallet('sendtoaddress', [addr_segwit, 1.0])
assert len(txid) == 64
tx_wallet = self.callnoderpc('gettransaction', [txid, ])['hex']
tx = self.callnoderpc('decoderawtransaction', [tx_wallet, ])
tx_wallet = ci.rpc_wallet('gettransaction', [txid, ])['hex']
tx = ci.rpc('decoderawtransaction', [tx_wallet, ])
self.mineBlock()
ro = self.callnoderpc('scantxoutset', ['start', ['addr({})'.format(addr_segwit)]])
ro = ci.rpc('scantxoutset', ['start', ['addr({})'.format(addr_segwit)]])
assert (len(ro['unspents']) == 1)
assert (ro['unspents'][0]['txid'] == txid)
@ -68,10 +81,10 @@ class TestLTC(BasicSwapTest):
break
assert prevout_n > -1
tx_funded = self.callnoderpc('createrawtransaction', [[{'txid': txid, 'vout': prevout_n}], {addr_segwit: 0.99}])
tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded, ])['hex']
tx_funded_decoded = self.callnoderpc('decoderawtransaction', [tx_funded, ])
tx_signed_decoded = self.callnoderpc('decoderawtransaction', [tx_signed, ])
tx_funded = ci.rpc('createrawtransaction', [[{'txid': txid, 'vout': prevout_n}], {addr_segwit: 0.99}])
tx_signed = ci.rpc_wallet('signrawtransactionwithwallet', [tx_funded, ])['hex']
tx_funded_decoded = ci.rpc('decoderawtransaction', [tx_funded, ])
tx_signed_decoded = ci.rpc('decoderawtransaction', [tx_signed, ])
assert tx_funded_decoded['txid'] == tx_signed_decoded['txid']
def test_007_hdwallet(self):
@ -108,6 +121,115 @@ class TestLTC(BasicSwapTest):
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
def test_21_mweb(self):
logging.info('---------- Test MWEB {}'.format(self.test_coin_from.name))
swap_clients = self.swap_clients
ci0 = swap_clients[0].ci(self.test_coin_from)
ci1 = swap_clients[1].ci(self.test_coin_from)
mweb_addr_0 = ci0.rpc_wallet('getnewaddress', ['mweb addr test 0', 'mweb'])
mweb_addr_1 = ci1.rpc_wallet('getnewaddress', ['mweb addr test 1', 'mweb'])
addr_info0 = ci0.rpc_wallet('getaddressinfo', [mweb_addr_0,])
assert (addr_info0['ismweb'] is True)
addr_info1 = ci1.rpc_wallet('getaddressinfo', [mweb_addr_1,])
assert (addr_info1['ismweb'] is True)
txid = ci0.rpc_wallet('sendtoaddress', [mweb_addr_0, 10.0])
self.mineBlock()
txns = ci0.rpc_wallet('listtransactions')
utxos = ci0.rpc_wallet('listunspent')
balances = ci0.rpc_wallet('getbalances')
wi = ci0.rpc_wallet('getwalletinfo')
txid = ci0.rpc_wallet('sendtoaddress', [mweb_addr_1, 10.0])
self.mineBlock()
txns = ci1.rpc_wallet('listtransactions')
utxos = ci1.rpc_wallet('listunspent')
balances = ci1.rpc_wallet('getbalances')
wi = ci1.rpc_wallet('getwalletinfo')
mweb_tx = None
for utxo in utxos:
if utxo.get('address', '') == mweb_addr_1:
mweb_tx = utxo
assert (mweb_tx is not None)
tx = ci1.rpc_wallet('gettransaction', [mweb_tx['txid'],])
blockhash = tx['blockhash']
block = ci1.rpc('getblock', [blockhash, 3])
block = ci1.rpc('getblock', [blockhash, 0])
# TODO
def test_22_mweb_balance(self):
logging.info('---------- Test MWEB balance {}'.format(self.test_coin_from.name))
swap_clients = self.swap_clients
ci_mweb = swap_clients[0].ci(Coins.LTC_MWEB)
mweb_addr_0 = ci_mweb.getNewAddress()
addr_info0 = ci_mweb.rpc_wallet('getaddressinfo', [mweb_addr_0,])
assert (addr_info0['ismweb'] is True)
ltc_addr = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc/nextdepositaddr')
ltc_mweb_addr = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc_mweb/nextdepositaddr')
ltc_mweb_addr2 = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc/newmwebaddress')
assert (ci_mweb.rpc_wallet('getaddressinfo', [ltc_addr,])['ismweb'] is False)
assert (ci_mweb.rpc_wallet('getaddressinfo', [ltc_mweb_addr,])['ismweb'] is True)
assert (ci_mweb.rpc_wallet('getaddressinfo', [ltc_mweb_addr2,])['ismweb'] is True)
post_json = {
'value': 10,
'address': ltc_mweb_addr,
'subfee': False,
}
json_rv = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc/withdraw', post_json)
assert (len(json_rv['txid']) == 64)
self.mineBlock()
json_rv = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc', post_json)
assert (json_rv['mweb_balance'] == 10.0)
mweb_address = json_rv['mweb_address']
post_json = {
'value': 11,
'address': mweb_address,
'subfee': False,
}
json_rv = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc/withdraw', post_json)
assert (len(json_rv['txid']) == 64)
self.mineBlock()
json_rv = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc_mweb', post_json)
assert (json_rv['mweb_balance'] == 21.0)
assert (json_rv['mweb_address'] == mweb_address)
ltc_address = json_rv['deposit_address']
# Check that spending the mweb balance takes from the correct wallet
post_json = {
'value': 1,
'address': ltc_address,
'subfee': False,
'type_from': 'mweb',
}
json_rv = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc/withdraw', post_json)
assert (len(json_rv['txid']) == 64)
json_rv = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc', post_json)
assert (json_rv['mweb_balance'] <= 20.0)
if __name__ == '__main__':
unittest.main()

28
tests/basicswap/test_partblind_xmr.py

@ -101,7 +101,7 @@ class Test(BaseTest):
nonlocal ci
i = 0
while not delay_event.is_set():
unspents = ci.rpc_callback('listunspentblind')
unspents = ci.rpc_wallet('listunspentblind')
if len(unspents) >= 1:
return
delay_event.wait(delay_time)
@ -113,8 +113,8 @@ class Test(BaseTest):
amount: int = ci.make_int(random.uniform(0.1, 2.0), r=1)
# Record unspents before createSCLockTx as the used ones will be locked
unspents = ci.rpc_callback('listunspentblind')
locked_utxos_before = ci.rpc_callback('listlockunspent')
unspents = ci.rpc_wallet('listunspentblind')
locked_utxos_before = ci.rpc_wallet('listlockunspent')
# fee_rate is in sats/kvB
fee_rate: int = 1000
@ -131,33 +131,33 @@ class Test(BaseTest):
lock_tx = ci.fundSCLockTx(lock_tx, fee_rate, vkbv)
lock_tx = ci.signTxWithWallet(lock_tx)
unspents_after = ci.rpc_callback('listunspentblind')
locked_utxos_after = ci.rpc_callback('listlockunspent')
unspents_after = ci.rpc_wallet('listunspentblind')
locked_utxos_after = ci.rpc_wallet('listlockunspent')
assert (len(unspents) > len(unspents_after))
assert (len(locked_utxos_after) > len(locked_utxos_before))
lock_tx_decoded = ci.rpc_callback('decoderawtransaction', [lock_tx.hex()])
lock_tx_decoded = ci.rpc_wallet('decoderawtransaction', [lock_tx.hex()])
txid = lock_tx_decoded['txid']
vsize = lock_tx_decoded['vsize']
expect_fee_int = round(fee_rate * vsize / 1000)
expect_fee = ci.format_amount(expect_fee_int)
ci.rpc_callback('sendrawtransaction', [lock_tx.hex()])
rv = ci.rpc_callback('gettransaction', [txid])
ci.rpc_wallet('sendrawtransaction', [lock_tx.hex()])
rv = ci.rpc_wallet('gettransaction', [txid])
wallet_tx_fee = -ci.make_int(rv['details'][0]['fee'])
assert (wallet_tx_fee >= expect_fee_int)
assert (wallet_tx_fee - expect_fee_int < 20)
addr_out = ci.getNewAddress(True)
addrinfo = ci.rpc_callback('getaddressinfo', [addr_out,])
addrinfo = ci.rpc_wallet('getaddressinfo', [addr_out,])
pk_out = bytes.fromhex(addrinfo['pubkey'])
fee_info = {}
lock_spend_tx = ci.createSCLockSpendTx(lock_tx, lock_tx_script, pk_out, fee_rate, vkbv, fee_info=fee_info)
vsize_estimated: int = fee_info['vsize']
spend_tx_decoded = ci.rpc_callback('decoderawtransaction', [lock_spend_tx.hex()])
spend_tx_decoded = ci.rpc('decoderawtransaction', [lock_spend_tx.hex()])
txid = spend_tx_decoded['txid']
nonce = ci.getScriptLockTxNonce(vkbv)
@ -172,12 +172,12 @@ class Test(BaseTest):
lock_tx_script,
]
lock_spend_tx = ci.setTxSignature(lock_spend_tx, witness_stack)
tx_decoded = ci.rpc_callback('decoderawtransaction', [lock_spend_tx.hex()])
tx_decoded = ci.rpc('decoderawtransaction', [lock_spend_tx.hex()])
vsize_actual: int = tx_decoded['vsize']
# Note: The fee is set allowing 9 bytes for the encoded fee amount, causing a small overestimate
assert (vsize_actual <= vsize_estimated and vsize_estimated - vsize_actual < 10)
assert (ci.rpc_callback('sendrawtransaction', [lock_spend_tx.hex()]) == txid)
assert (ci.rpc('sendrawtransaction', [lock_spend_tx.hex()]) == txid)
# Test chain b (no-script) lock tx size
v = ci.getNewSecretKey()
@ -198,7 +198,7 @@ class Test(BaseTest):
lock_tx_b_spend = ci.getTransaction(lock_tx_b_spend_txid)
if lock_tx_b_spend is None:
lock_tx_b_spend = ci.getWalletTransaction(lock_tx_b_spend_txid)
lock_tx_b_spend_decoded = ci.rpc_callback('decoderawtransaction', [lock_tx_b_spend.hex()])
lock_tx_b_spend_decoded = ci.rpc('decoderawtransaction', [lock_tx_b_spend.hex()])
expect_vsize: int = ci.xmr_swap_b_lock_spend_tx_vsize()
assert (expect_vsize >= lock_tx_b_spend_decoded['vsize'])
@ -472,7 +472,7 @@ class Test(BaseTest):
# Verify expected inputs were used
bid, _, _, _, _ = swap_clients[2].getXmrBidAndOffer(bid_id)
assert (bid.xmr_a_lock_tx)
wtx = ci.rpc_callback('gettransaction', [bid.xmr_a_lock_tx.txid.hex(),])
wtx = ci.rpc_wallet('gettransaction', [bid.xmr_a_lock_tx.txid.hex(),])
itx_after = ci.describeTx(wtx['hex'])
assert (len(itx_after['vin']) == len(itx_decoded['vin']))
for i, txin in enumerate(itx_decoded['vin']):

11
tests/basicswap/test_run.py

@ -80,10 +80,10 @@ class Test(BaseTest):
super(Test, cls).setUpClass()
btc_addr1 = callnoderpc(1, 'getnewaddress', ['initial funds', 'bech32'], base_rpc_port=BTC_BASE_RPC_PORT)
ltc_addr1 = callnoderpc(1, 'getnewaddress', ['initial funds', 'bech32'], base_rpc_port=LTC_BASE_RPC_PORT)
ltc_addr1 = callnoderpc(1, 'getnewaddress', ['initial funds', 'bech32'], base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat')
callnoderpc(0, 'sendtoaddress', [btc_addr1, 1000], base_rpc_port=BTC_BASE_RPC_PORT)
callnoderpc(0, 'sendtoaddress', [ltc_addr1, 1000], base_rpc_port=LTC_BASE_RPC_PORT)
callnoderpc(0, 'sendtoaddress', [ltc_addr1, 1000], base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat')
wait_for_balance(test_delay_event, 'http://127.0.0.1:1801/json/wallets/btc', 'balance', 1000.0)
wait_for_balance(test_delay_event, 'http://127.0.0.1:1801/json/wallets/ltc', 'balance', 1000.0)
@ -182,6 +182,9 @@ class Test(BaseTest):
rv = read_json_api(1800, 'automationstrategies/1')
assert (rv['label'] == 'Accept All')
sx_addr = read_json_api(1800, 'wallets/part/newstealthaddress')
assert (callnoderpc(0, 'getaddressinfo', [sx_addr, ])['isstealthaddress'] is True)
def test_004_validateSwapType(self):
logging.info('---------- Test validateSwapType')
@ -570,7 +573,7 @@ class Test(BaseTest):
def test_12_withdrawal(self):
logging.info('---------- Test LTC withdrawals')
ltc_addr = callnoderpc(0, 'getnewaddress', ['Withdrawal test', 'legacy'], base_rpc_port=LTC_BASE_RPC_PORT)
ltc_addr = callnoderpc(0, 'getnewaddress', ['Withdrawal test', 'legacy'], base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat')
wallets0 = read_json_api(TEST_HTTP_PORT + 0, 'wallets')
assert (float(wallets0['LTC']['balance']) > 100)
@ -712,7 +715,7 @@ class Test(BaseTest):
# Verify expected inputs were used
bid, offer = swap_clients[2].getBidAndOffer(bid_id)
assert (bid.initiate_tx)
wtx = ci.rpc_callback('gettransaction', [bid.initiate_tx.txid.hex(),])
wtx = ci.rpc('gettransaction', [bid.initiate_tx.txid.hex(),])
itx_after = ci.describeTx(wtx['hex'])
assert (len(itx_after['vin']) == len(itx_decoded['vin']))
for i, txin in enumerate(itx_decoded['vin']):

40
tests/basicswap/test_xmr.py

@ -530,29 +530,29 @@ class BaseTest(unittest.TestCase):
if cls.start_ltc_nodes:
num_blocks = 400
cls.ltc_addr = callnoderpc(0, 'getnewaddress', ['mining_addr', 'bech32'], base_rpc_port=LTC_BASE_RPC_PORT)
cls.ltc_addr = callnoderpc(0, 'getnewaddress', ['mining_addr', 'bech32'], base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat')
logging.info('Mining %d Litecoin blocks to %s', num_blocks, cls.ltc_addr)
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT)
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat')
num_blocks = 31
cls.ltc_addr = cls.swap_clients[0].ci(Coins.LTC).pubkey_to_address(void_block_rewards_pubkey)
logging.info('Mining %d Litecoin blocks to %s', num_blocks, cls.ltc_addr)
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT)
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat')
# https://github.com/litecoin-project/litecoin/issues/807
# Block 432 is when MWEB activates. It requires a peg-in. You'll need to generate an mweb address and send some coins to it. Then it will allow you to mine the next block.
mweb_addr = callnoderpc(2, 'getnewaddress', ['mweb_addr', 'mweb'], base_rpc_port=LTC_BASE_RPC_PORT)
callnoderpc(0, 'sendtoaddress', [mweb_addr, 1], base_rpc_port=LTC_BASE_RPC_PORT)
mweb_addr = callnoderpc(2, 'getnewaddress', ['mweb_addr', 'mweb'], base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat')
callnoderpc(0, 'sendtoaddress', [mweb_addr, 1], base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat')
ltc_addr1 = callnoderpc(1, 'getnewaddress', ['initial addr'], base_rpc_port=LTC_BASE_RPC_PORT)
ltc_addr1 = callnoderpc(1, 'getnewaddress', ['initial addr'], base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat')
for i in range(5):
callnoderpc(0, 'sendtoaddress', [ltc_addr1, 100], base_rpc_port=LTC_BASE_RPC_PORT)
callnoderpc(0, 'sendtoaddress', [ltc_addr1, 100], base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat')
num_blocks = 69
cls.ltc_addr = cls.swap_clients[0].ci(Coins.LTC).pubkey_to_address(void_block_rewards_pubkey)
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT)
callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat')
checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=LTC_BASE_RPC_PORT))
checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat'))
num_blocks = 100
if cls.start_xmr_nodes:
@ -682,7 +682,7 @@ class Test(BaseTest):
amount: int = ci.make_int(random.uniform(0.1, 2.0), r=1)
# Record unspents before createSCLockTx as the used ones will be locked
unspents = ci.rpc_callback('listunspent')
unspents = ci.rpc('listunspent')
# fee_rate is in sats/kvB
fee_rate: int = 1000
@ -698,10 +698,10 @@ class Test(BaseTest):
lock_tx = ci.fundSCLockTx(lock_tx, fee_rate)
lock_tx = ci.signTxWithWallet(lock_tx)
unspents_after = ci.rpc_callback('listunspent')
unspents_after = ci.rpc('listunspent')
assert (len(unspents) > len(unspents_after))
tx_decoded = ci.rpc_callback('decoderawtransaction', [lock_tx.hex()])
tx_decoded = ci.rpc('decoderawtransaction', [lock_tx.hex()])
txid = tx_decoded['txid']
vsize = tx_decoded['vsize']
@ -722,8 +722,8 @@ class Test(BaseTest):
break
fee_value = in_value - out_value
ci.rpc_callback('sendrawtransaction', [lock_tx.hex()])
rv = ci.rpc_callback('gettransaction', [txid])
ci.rpc('sendrawtransaction', [lock_tx.hex()])
rv = ci.rpc('gettransaction', [txid])
wallet_tx_fee = -ci.make_int(rv['fee'])
assert (wallet_tx_fee == fee_value)
@ -735,7 +735,7 @@ class Test(BaseTest):
lock_spend_tx = ci.createSCLockSpendTx(lock_tx, lock_tx_script, pkh_out, fee_rate, fee_info=fee_info)
vsize_estimated: int = fee_info['vsize']
tx_decoded = ci.rpc_callback('decoderawtransaction', [lock_spend_tx.hex()])
tx_decoded = ci.rpc('decoderawtransaction', [lock_spend_tx.hex()])
txid = tx_decoded['txid']
witness_stack = [
@ -745,11 +745,11 @@ class Test(BaseTest):
lock_tx_script,
]
lock_spend_tx = ci.setTxSignature(lock_spend_tx, witness_stack)
tx_decoded = ci.rpc_callback('decoderawtransaction', [lock_spend_tx.hex()])
tx_decoded = ci.rpc('decoderawtransaction', [lock_spend_tx.hex()])
vsize_actual: int = tx_decoded['vsize']
assert (vsize_actual <= vsize_estimated and vsize_estimated - vsize_actual < 4)
assert (ci.rpc_callback('sendrawtransaction', [lock_spend_tx.hex()]) == txid)
assert (ci.rpc('sendrawtransaction', [lock_spend_tx.hex()]) == txid)
expect_vsize: int = ci.xmr_swap_a_lock_spend_tx_vsize()
assert (expect_vsize >= vsize_actual)
@ -766,7 +766,7 @@ class Test(BaseTest):
lock_tx_b_spend = ci.getTransaction(lock_tx_b_spend_txid)
if lock_tx_b_spend is None:
lock_tx_b_spend = ci.getWalletTransaction(lock_tx_b_spend_txid)
lock_tx_b_spend_decoded = ci.rpc_callback('decoderawtransaction', [lock_tx_b_spend.hex()])
lock_tx_b_spend_decoded = ci.rpc('decoderawtransaction', [lock_tx_b_spend.hex()])
expect_vsize: int = ci.xmr_swap_b_lock_spend_tx_vsize()
assert (expect_vsize >= lock_tx_b_spend_decoded['vsize'])
@ -1354,7 +1354,7 @@ class Test(BaseTest):
lock_tx_b_spend = ci.getTransaction(lock_tx_b_spend_txid)
if lock_tx_b_spend is None:
lock_tx_b_spend = ci.getWalletTransaction(lock_tx_b_spend_txid)
lock_tx_b_spend_decoded = ci.rpc_callback('decoderawtransaction', [lock_tx_b_spend.hex()])
lock_tx_b_spend_decoded = ci.rpc('decoderawtransaction', [lock_tx_b_spend.hex()])
expect_vsize: int = ci.xmr_swap_b_lock_spend_tx_vsize()
assert (expect_vsize >= lock_tx_b_spend_decoded['vsize'])
@ -1501,7 +1501,7 @@ class Test(BaseTest):
# Verify expected inputs were used
bid, _, _, _, _ = swap_clients[2].getXmrBidAndOffer(bid_id)
assert (bid.xmr_a_lock_tx)
wtx = ci.rpc_callback('gettransaction', [bid.xmr_a_lock_tx.txid.hex(),])
wtx = ci.rpc('gettransaction', [bid.xmr_a_lock_tx.txid.hex(),])
itx_after = ci.describeTx(wtx['hex'])
assert (len(itx_after['vin']) == len(itx_decoded['vin']))
for i, txin in enumerate(itx_decoded['vin']):

Loading…
Cancel
Save