coins: Add LTC MWEB wallet

This commit is contained in:
tecnovert 2023-12-29 15:36:00 +02:00
parent 7547587d4e
commit 38fa498b0b
No known key found for this signature in database
GPG Key ID: 8ED6D8750C4E3F93
29 changed files with 987 additions and 517 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,15 +1,117 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2020 tecnovert # Copyright (c) 2020-2023 tecnovert
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
from .btc import BTCInterface 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): class LTCInterface(BTCInterface):
@staticmethod @staticmethod
def coin_type(): def coin_type():
return Coins.LTC 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())

View File

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

View File

@ -19,7 +19,7 @@ class NMCInterface(BTCInterface):
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index=False): def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index=False):
self._log.debug('[rm] scantxoutset start') # scantxoutset is slow 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') self._log.debug('[rm] scantxoutset end')
return_txid = True if txid is None else False return_txid = True if txid is None else False
for o in ro['unspents']: for o in ro['unspents']:

View File

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

View File

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

View File

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

View File

@ -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_from = get_data_entry_or(post_data, 'type_from', 'plain')
type_to = get_data_entry_or(post_data, 'type_to', 'plain') type_to = get_data_entry_or(post_data, 'type_to', 'plain')
txid_hex = swap_client.withdrawParticl(type_from, type_to, value, address, subfee) 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: else:
txid_hex = swap_client.withdrawCoin(coin_type, value, address, subfee) 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' entry['variant'] = 'Anon'
elif coin == Coins.PART_BLIND: elif coin == Coins.PART_BLIND:
entry['variant'] = 'Blind' entry['variant'] = 'Blind'
elif coin == Coins.LTC_MWEB:
entry['variant'] = 'MWEB'
coins.append(entry) coins.append(entry)
return bytes(json.dumps(coins), 'UTF-8') 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] cmd = url_split[4]
if cmd == 'withdraw': if cmd == 'withdraw':
return bytes(json.dumps(withdraw_coin(swap_client, coin_type, post_string, is_json)), 'UTF-8') 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') return bytes(json.dumps(swap_client.cacheNewAddressForCoin(coin_type)), 'UTF-8')
if cmd == 'createutxo': elif cmd == 'createutxo':
post_data = getFormData(post_string, is_json) post_data = getFormData(post_string, is_json)
ci = swap_client.ci(coin_type) ci = swap_client.ci(coin_type)
value = ci.make_int(get_data_entry(post_data, 'value')) value = ci.make_int(get_data_entry(post_data, 'value'))
txid_hex, new_addr = ci.createUTXO(value) txid_hex, new_addr = ci.createUTXO(value)
return bytes(json.dumps({'txid': txid_hex, 'address': new_addr}), 'UTF-8') return bytes(json.dumps({'txid': txid_hex, 'address': new_addr}), 'UTF-8')
if cmd == 'reseed': elif cmd == 'reseed':
swap_client.reseedWallet(coin_type) swap_client.reseedWallet(coin_type)
return bytes(json.dumps({'reseeded': True}), 'UTF-8') 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') raise ValueError('Unknown command')
if coin_type == Coins.LTC_MWEB:
coin_type = Coins.LTC
rv = swap_client.getWalletInfo(coin_type) rv = swap_client.getWalletInfo(coin_type)
rv.update(swap_client.getBlockchainInfo(coin_type)) rv.update(swap_client.getBlockchainInfo(coin_type))
ci = swap_client.ci(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'): if have_data_entry(post_data, 'coin'):
# Set password for one coin # Set password for one coin
coin = getCoinType(get_data_entry(post_data, '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.') raise ValueError('Invalid coin.')
swap_client.changeWalletPasswords(old_password, new_password, coin) swap_client.changeWalletPasswords(old_password, new_password, coin)
return bytes(json.dumps({'success': True}), 'UTF-8') return bytes(json.dumps({'success': True}), 'UTF-8')

View File

@ -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>' %}

View File

@ -1,4 +1,6 @@
{% include 'header.html' %} {% include 'header.html' %}
{% from 'style.html' import select_box_arrow_svg, select_box_class, circular_arrows_svg %}
<div class="container mx-auto"> <div class="container mx-auto">
<section class="p-5 mt-5"> <section class="p-5 mt-5">
<div class="flex flex-wrap items-center -m-2"> <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"> <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="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 }}"> <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"> {{ circular_arrows_svg }}
<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>
<span>Refresh</span> <span>Refresh</span>
</a> </a>
</div> </div>
@ -165,7 +162,7 @@
</td> </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> <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> </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"> <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"> <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"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded">
@ -182,7 +179,19 @@
</td> </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> <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 bold">Blocks:</td>
<td class="py-3 px-6">{{ w.blocks }} {% if w.known_block_count %} / {{ w.known_block_count }} {% endif %}</td> <td class="py-3 px-6">{{ w.blocks }} {% if w.known_block_count %} / {{ w.known_block_count }} {% endif %}</td>
</tr> </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"> </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 bold">Bootstrapping:</td>
<td class="py-3 px-6">{{ w.bootstrapping }}</td> <td class="py-3 px-6">{{ w.bootstrapping }}</td>
</tr> {% endif %} </tr> {% endif %} {# / bootstrapping #}
{% if w.encrypted %} {% if w.encrypted %}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600"> <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 bold">Locked:</td>
<td class="py-3 px-6">{{ w.locked }}</td> <td class="py-3 px-6">{{ w.locked }}</td>
</tr> </tr>
{% endif %} {% endif %} {# / encrypted #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600"> <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 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 #} <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 %} {% else %}
</tr> </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"> <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 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> <td colspan=2 class="py-3 px-6 monospace bold select-all">{{ w.stealth_address }}</td>
</tr> </tr>
{% endif %} {% endif %} {# / PART #}
{% if w.cid == '6' %} {% 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"> <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 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> <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"> <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"> <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"> <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"> {{ circular_arrows_svg }} New {{ w.ticker }} Sub Address </button>
<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>
</td> </td>
<td colspan=2 class="py-3 px-6 monospace select-all">{{ w.deposit_address }}</td> <td colspan=2 class="py-3 px-6 monospace select-all">{{ w.deposit_address }}</td>
</tr> </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"> <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"> <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"> <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"> {{ circular_arrows_svg }} New {{ w.ticker }} Deposit Address</button>
<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>
</td> </td>
<td colspan=2 class="py-3 px-6 monospace bold select-all" id="deposit_address">{{ w.deposit_address }}</td> <td colspan=2 class="py-3 px-6 monospace bold select-all" id="deposit_address">{{ w.deposit_address }}</td>
</tr> </tr>
@ -342,16 +350,14 @@
</td> </td>
<td></td> <td></td>
</tr> </tr>
{% if w.cid == '1' %} {% if w.cid == '1' %} {# PART #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100"> <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 bold">Type From -> To:</td>
<td class="py-3 px-6"> <td class="py-3 px-6">
<div class="w-full md:flex-1"> <div class="w-full md:flex-1">
<div class="relative"> <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"> {{ select_box_arrow_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> <select class="{{ select_box_class }}" name="withdraw_type_from_{{ w.cid }}">
</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 }}">
<option value="plain" {% if w.wd_type_from=='plain' %} selected{% endif %}>Plain</option> <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="blind" {% if w.wd_type_from=='blind' %} selected{% endif %}>Blind</option>
<option value="anon" {% if w.wd_type_from=='anon' %} selected{% endif %}>Anon</option> <option value="anon" {% if w.wd_type_from=='anon' %} selected{% endif %}>Anon</option>
@ -362,10 +368,8 @@
<td class="py-3 px-6"> <td class="py-3 px-6">
<div class="w-full md:flex-1"> <div class="w-full md:flex-1">
<div class="relative"> <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"> {{ select_box_arrow_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> <select class="{{ select_box_class }}" name="withdraw_type_to_{{ w.cid }}">
</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 }}">
<option value="plain" {% if w.wd_type_to=='plain' %} selected{% endif %}>Plain</option> <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="blind" {% if w.wd_type_to=='blind' %} selected{% endif %}>Blind</option>
<option value="anon" {% if w.wd_type_to=='anon' %} selected{% endif %}>Anon</option> <option value="anon" {% if w.wd_type_to=='anon' %} selected{% endif %}>Anon</option>
@ -374,7 +378,23 @@
</div> </div>
</td> </td>
</tr> </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"> <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">Fee Rate:</td>
<td class="py-3 px-6 bold">{{ w.fee_rate }}</td> <td class="py-3 px-6 bold">{{ w.fee_rate }}</td>
@ -392,7 +412,7 @@
</div> </div>
</div> </div>
</section> </section>
{% if w.cid != '6' %} {% if w.cid != '6' %} {# !XMR #}
{% if w.show_utxo_groups %} {% if w.show_utxo_groups %}
<section class="p-6"> <section class="p-6">
<div class="flex flex-wrap items-center"> <div class="flex flex-wrap items-center">

View File

@ -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 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 %}
{% if w.cid == '1' %} {% if w.cid == '1' %} {# PART #}
<div class="flex mb-2 justify-between items-center"> <div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-medium dark:text-white">Blind Balance:</h4> <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> <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>
@ -151,7 +151,28 @@
<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 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 %}
{% 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 %}
{% endif %} {# / LTC #}
<div class="flex mb-2 justify-between items-center"> <div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-medium dark:text-white">Blocks:</h4> <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> <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>

View File

@ -51,12 +51,16 @@ def format_wallet_data(swap_client, ci, w):
if ci.coin_type() == Coins.PART: if ci.coin_type() == Coins.PART:
wf['stealth_address'] = w.get('stealth_address', '?') 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: if 'blind_unconfirmed' in w and float(w['blind_unconfirmed']) > 0.0:
wf['blind_unconfirmed'] = w['blind_unconfirmed'] wf['blind_unconfirmed'] = w['blind_unconfirmed']
wf['anon_balance'] = w.get('anon_balance', '?') wf['anon_balance'] = w.get('anon_balance', '?')
if 'anon_pending' in w and float(w['anon_pending']) > 0.0: if 'anon_pending' in w and float(w['anon_pending']) > 0.0:
wf['anon_pending'] = w['anon_pending'] 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) checkAddressesOwned(swap_client, ci, wf)
return wf return wf
@ -128,6 +132,8 @@ def page_wallet(self, url_split, post_string):
if bytes('newaddr_' + cid, 'utf-8') in form_data: if bytes('newaddr_' + cid, 'utf-8') in form_data:
swap_client.cacheNewAddressForCoin(coin_id) 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: elif bytes('reseed_' + cid, 'utf-8') in form_data:
try: try:
swap_client.reseedWallet(coin_id) swap_client.reseedWallet(coin_id)
@ -158,18 +164,24 @@ def page_wallet(self, url_split, post_string):
page_data['wd_type_to_' + cid] = type_to page_data['wd_type_to_' + cid] = type_to
except Exception as e: except Exception as e:
err_messages.append('Missing type') 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: if len(messages) == 0:
ci = swap_client.ci(coin_id) ci = swap_client.ci(coin_id)
ticker = ci.ticker() 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) 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)) messages.append('Withdrew {} {} ({} to {}) to address {}<br/>In txid: {}'.format(value, ticker, type_from, type_to, address, txid))
except Exception as e: elif coin_id == Coins.LTC:
err_messages.append(str(e)) 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: else:
try:
txid = swap_client.withdrawCoin(coin_id, value, address, subfee) txid = swap_client.withdrawCoin(coin_id, value, address, subfee)
messages.append('Withdrew {} {} to address {}<br/>In txid: {}'.format(value, ticker, address, txid)) messages.append('Withdrew {} {} to address {}<br/>In txid: {}'.format(value, ticker, address, txid))
except Exception as e: except Exception as e:
@ -227,6 +239,8 @@ def page_wallet(self, url_split, post_string):
if k == Coins.XMR: if k == Coins.XMR:
wallet_data['main_address'] = w.get('main_address', 'Refresh necessary') 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: if 'wd_type_from_' + cid in page_data:
wallet_data['wd_type_from'] = page_data['wd_type_from_' + cid] wallet_data['wd_type_from'] = page_data['wd_type_from_' + cid]

View File

@ -419,6 +419,8 @@ def getCoinName(c):
return chainparams[Coins.PART]['name'].capitalize() + ' Anon' return chainparams[Coins.PART]['name'].capitalize() + ' Anon'
if c == Coins.PART_BLIND: if c == Coins.PART_BLIND:
return chainparams[Coins.PART]['name'].capitalize() + ' Blind' return chainparams[Coins.PART]['name'].capitalize() + ' Blind'
if c == Coins.LTC_MWEB:
return chainparams[Coins.LTC]['name'].capitalize() + ' MWEB'
coin_chainparams = chainparams[c] coin_chainparams = chainparams[c]
if coin_chainparams.get('use_ticker_as_name', False): 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))) coins.append((int(v), getCoinName(v)))
if split_from and v not in invalid_coins_from: if split_from and v not in invalid_coins_from:
coins_from.append(coins[-1]) 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: if split_from:
return coins_from, coins return coins_from, coins
return coins return coins

View File

@ -42,7 +42,7 @@ PARTICL_VERSION = os.getenv('PARTICL_VERSION', '23.2.7.0')
PARTICL_VERSION_TAG = os.getenv('PARTICL_VERSION_TAG', '') PARTICL_VERSION_TAG = os.getenv('PARTICL_VERSION_TAG', '')
PARTICL_LINUX_EXTRA = os.getenv('PARTICL_LINUX_EXTRA', 'nousb') 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', '') LITECOIN_VERSION_TAG = os.getenv('LITECOIN_VERSION_TAG', '')
BITCOIN_VERSION = os.getenv('BITCOIN_VERSION', '23.0') 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) 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': elif coin == 'litecoin':
release_url = 'https://download.litecoin.org/litecoin-{}/{}/{}'.format(version, os_name, release_filename) 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) 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': elif coin == 'bitcoin':
release_url = 'https://bitcoincore.org/bin/bitcoin-core-{}/{}'.format(version, release_filename) 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) 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): def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings, chain, use_tor_proxy):
swap_client = None swap_client = None
daemons = [] daemons = []
@ -1185,16 +1191,18 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
if len(wallets) < 1: if len(wallets) < 1:
logger.info('Creating wallet.dat for {}.'.format(getCoinName(c))) 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 # 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: else:
swap_client.callcoinrpc(c, 'createwallet', ['wallet.dat']) swap_client.callcoinrpc(c, 'createwallet', ['wallet.dat'])
if WALLET_ENCRYPTION_PWD != '': if WALLET_ENCRYPTION_PWD != '':
ci = swap_client.ci(c) encrypt_wallet(swap_client, 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 c == Coins.PART:
if 'particl' in with_coins: if 'particl' in with_coins:

View File

@ -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 0.12.3
============== ==============

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2021-2022 tecnovert # Copyright (c) 2021-2023 tecnovert
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # 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_bid,
wait_for_offer, wait_for_offer,
wait_for_in_progress, wait_for_in_progress,
TEST_HTTP_PORT,
LTC_BASE_RPC_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() logger = logging.getLogger()
@ -37,9 +38,20 @@ class TestLTC(BasicSwapTest):
start_ltc_nodes = True start_ltc_nodes = True
base_rpc_port = LTC_BASE_RPC_PORT 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): def mineBlock(self, num_blocks=1):
self.callnoderpc('generatetoaddress', [num_blocks, self.ltc_addr]) 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): def test_001_nested_segwit(self):
logging.info('---------- Test {} p2sh nested segwit'.format(self.test_coin_from.name)) logging.info('---------- Test {} p2sh nested segwit'.format(self.test_coin_from.name))
logging.info('Skipped') logging.info('Skipped')
@ -47,17 +59,18 @@ class TestLTC(BasicSwapTest):
def test_002_native_segwit(self): def test_002_native_segwit(self):
logging.info('---------- Test {} p2sh native segwit'.format(self.test_coin_from.name)) logging.info('---------- Test {} p2sh native segwit'.format(self.test_coin_from.name))
addr_segwit = self.callnoderpc('getnewaddress', ['segwit test', 'bech32']) ci = self.swap_clients[0].ci(self.test_coin_from)
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 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 assert len(txid) == 64
tx_wallet = self.callnoderpc('gettransaction', [txid, ])['hex'] tx_wallet = ci.rpc_wallet('gettransaction', [txid, ])['hex']
tx = self.callnoderpc('decoderawtransaction', [tx_wallet, ]) tx = ci.rpc('decoderawtransaction', [tx_wallet, ])
self.mineBlock() 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 (len(ro['unspents']) == 1)
assert (ro['unspents'][0]['txid'] == txid) assert (ro['unspents'][0]['txid'] == txid)
@ -68,10 +81,10 @@ class TestLTC(BasicSwapTest):
break break
assert prevout_n > -1 assert prevout_n > -1
tx_funded = self.callnoderpc('createrawtransaction', [[{'txid': txid, 'vout': prevout_n}], {addr_segwit: 0.99}]) tx_funded = ci.rpc('createrawtransaction', [[{'txid': txid, 'vout': prevout_n}], {addr_segwit: 0.99}])
tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded, ])['hex'] tx_signed = ci.rpc_wallet('signrawtransactionwithwallet', [tx_funded, ])['hex']
tx_funded_decoded = self.callnoderpc('decoderawtransaction', [tx_funded, ]) tx_funded_decoded = ci.rpc('decoderawtransaction', [tx_funded, ])
tx_signed_decoded = self.callnoderpc('decoderawtransaction', [tx_signed, ]) tx_signed_decoded = ci.rpc('decoderawtransaction', [tx_signed, ])
assert tx_funded_decoded['txid'] == tx_signed_decoded['txid'] assert tx_funded_decoded['txid'] == tx_signed_decoded['txid']
def test_007_hdwallet(self): 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_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
assert (js_1['num_swapping'] == 0 and js_1['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__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

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

View File

@ -80,10 +80,10 @@ class Test(BaseTest):
super(Test, cls).setUpClass() super(Test, cls).setUpClass()
btc_addr1 = callnoderpc(1, 'getnewaddress', ['initial funds', 'bech32'], base_rpc_port=BTC_BASE_RPC_PORT) 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', [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/btc', 'balance', 1000.0)
wait_for_balance(test_delay_event, 'http://127.0.0.1:1801/json/wallets/ltc', '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') rv = read_json_api(1800, 'automationstrategies/1')
assert (rv['label'] == 'Accept All') 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): def test_004_validateSwapType(self):
logging.info('---------- Test validateSwapType') logging.info('---------- Test validateSwapType')
@ -570,7 +573,7 @@ class Test(BaseTest):
def test_12_withdrawal(self): def test_12_withdrawal(self):
logging.info('---------- Test LTC withdrawals') 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') wallets0 = read_json_api(TEST_HTTP_PORT + 0, 'wallets')
assert (float(wallets0['LTC']['balance']) > 100) assert (float(wallets0['LTC']['balance']) > 100)
@ -712,7 +715,7 @@ class Test(BaseTest):
# Verify expected inputs were used # Verify expected inputs were used
bid, offer = swap_clients[2].getBidAndOffer(bid_id) bid, offer = swap_clients[2].getBidAndOffer(bid_id)
assert (bid.initiate_tx) 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']) itx_after = ci.describeTx(wtx['hex'])
assert (len(itx_after['vin']) == len(itx_decoded['vin'])) assert (len(itx_after['vin']) == len(itx_decoded['vin']))
for i, txin in enumerate(itx_decoded['vin']): for i, txin in enumerate(itx_decoded['vin']):

View File

@ -530,29 +530,29 @@ class BaseTest(unittest.TestCase):
if cls.start_ltc_nodes: if cls.start_ltc_nodes:
num_blocks = 400 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) 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 num_blocks = 31
cls.ltc_addr = cls.swap_clients[0].ci(Coins.LTC).pubkey_to_address(void_block_rewards_pubkey) 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) 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 # 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. # 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) 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) 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): 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 num_blocks = 69
cls.ltc_addr = cls.swap_clients[0].ci(Coins.LTC).pubkey_to_address(void_block_rewards_pubkey) 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 num_blocks = 100
if cls.start_xmr_nodes: 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) amount: int = ci.make_int(random.uniform(0.1, 2.0), r=1)
# Record unspents before createSCLockTx as the used ones will be locked # 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 is in sats/kvB
fee_rate: int = 1000 fee_rate: int = 1000
@ -698,10 +698,10 @@ class Test(BaseTest):
lock_tx = ci.fundSCLockTx(lock_tx, fee_rate) lock_tx = ci.fundSCLockTx(lock_tx, fee_rate)
lock_tx = ci.signTxWithWallet(lock_tx) lock_tx = ci.signTxWithWallet(lock_tx)
unspents_after = ci.rpc_callback('listunspent') unspents_after = ci.rpc('listunspent')
assert (len(unspents) > len(unspents_after)) 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'] txid = tx_decoded['txid']
vsize = tx_decoded['vsize'] vsize = tx_decoded['vsize']
@ -722,8 +722,8 @@ class Test(BaseTest):
break break
fee_value = in_value - out_value fee_value = in_value - out_value
ci.rpc_callback('sendrawtransaction', [lock_tx.hex()]) ci.rpc('sendrawtransaction', [lock_tx.hex()])
rv = ci.rpc_callback('gettransaction', [txid]) rv = ci.rpc('gettransaction', [txid])
wallet_tx_fee = -ci.make_int(rv['fee']) wallet_tx_fee = -ci.make_int(rv['fee'])
assert (wallet_tx_fee == fee_value) 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) lock_spend_tx = ci.createSCLockSpendTx(lock_tx, lock_tx_script, pkh_out, fee_rate, fee_info=fee_info)
vsize_estimated: int = fee_info['vsize'] 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'] txid = tx_decoded['txid']
witness_stack = [ witness_stack = [
@ -745,11 +745,11 @@ class Test(BaseTest):
lock_tx_script, lock_tx_script,
] ]
lock_spend_tx = ci.setTxSignature(lock_spend_tx, witness_stack) 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'] vsize_actual: int = tx_decoded['vsize']
assert (vsize_actual <= vsize_estimated and vsize_estimated - vsize_actual < 4) 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() expect_vsize: int = ci.xmr_swap_a_lock_spend_tx_vsize()
assert (expect_vsize >= vsize_actual) assert (expect_vsize >= vsize_actual)
@ -766,7 +766,7 @@ class Test(BaseTest):
lock_tx_b_spend = ci.getTransaction(lock_tx_b_spend_txid) lock_tx_b_spend = ci.getTransaction(lock_tx_b_spend_txid)
if lock_tx_b_spend is None: if lock_tx_b_spend is None:
lock_tx_b_spend = ci.getWalletTransaction(lock_tx_b_spend_txid) 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() expect_vsize: int = ci.xmr_swap_b_lock_spend_tx_vsize()
assert (expect_vsize >= lock_tx_b_spend_decoded['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) lock_tx_b_spend = ci.getTransaction(lock_tx_b_spend_txid)
if lock_tx_b_spend is None: if lock_tx_b_spend is None:
lock_tx_b_spend = ci.getWalletTransaction(lock_tx_b_spend_txid) 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() expect_vsize: int = ci.xmr_swap_b_lock_spend_tx_vsize()
assert (expect_vsize >= lock_tx_b_spend_decoded['vsize']) assert (expect_vsize >= lock_tx_b_spend_decoded['vsize'])
@ -1501,7 +1501,7 @@ class Test(BaseTest):
# Verify expected inputs were used # Verify expected inputs were used
bid, _, _, _, _ = swap_clients[2].getXmrBidAndOffer(bid_id) bid, _, _, _, _ = swap_clients[2].getXmrBidAndOffer(bid_id)
assert (bid.xmr_a_lock_tx) 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']) itx_after = ci.describeTx(wtx['hex'])
assert (len(itx_after['vin']) == len(itx_decoded['vin'])) assert (len(itx_after['vin']) == len(itx_decoded['vin']))
for i, txin in enumerate(itx_decoded['vin']): for i, txin in enumerate(itx_decoded['vin']):