mirror of
https://github.com/basicswap/basicswap.git
synced 2025-11-06 10:48:11 +01:00
coins: Add LTC MWEB wallet
This commit is contained in:
@@ -195,7 +195,9 @@ class BTCInterface(CoinInterface):
|
||||
self._rpc_host = coin_settings.get('rpchost', '127.0.0.1')
|
||||
self._rpcport = coin_settings['rpcport']
|
||||
self._rpcauth = coin_settings['rpcauth']
|
||||
self.rpc_callback = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
|
||||
self.rpc = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
|
||||
self._rpc_wallet = 'wallet.dat'
|
||||
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet)
|
||||
self.blocks_confirmed = coin_settings['blocks_confirmed']
|
||||
self.setConfTarget(coin_settings['conf_target'])
|
||||
self._use_segwit = coin_settings['use_segwit']
|
||||
@@ -204,6 +206,23 @@ class BTCInterface(CoinInterface):
|
||||
self._log = self._sc.log if self._sc and self._sc.log else logging
|
||||
self._expect_seedid_hex = None
|
||||
|
||||
def checkWallets(self) -> int:
|
||||
wallets = self.rpc('listwallets')
|
||||
|
||||
# Wallet name is "" for some LTC and PART installs on older cores
|
||||
if self._rpc_wallet not in wallets and len(wallets) > 0:
|
||||
self._log.debug('Changing {} wallet name.'.format(self.ticker()))
|
||||
for wallet_name in wallets:
|
||||
# Skip over other expected wallets
|
||||
if wallet_name in ('mweb', ):
|
||||
continue
|
||||
self._rpc_wallet = wallet_name
|
||||
self._log.info('Switched {} wallet name to {}.'.format(self.ticker(), self._rpc_wallet))
|
||||
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet)
|
||||
break
|
||||
|
||||
return len(wallets)
|
||||
|
||||
def using_segwit(self) -> bool:
|
||||
# Using btc native segwit
|
||||
return self._use_segwit
|
||||
@@ -239,34 +258,34 @@ class BTCInterface(CoinInterface):
|
||||
self._conf_target = new_conf_target
|
||||
|
||||
def testDaemonRPC(self, with_wallet=True) -> None:
|
||||
self.rpc_callback('getwalletinfo' if with_wallet else 'getblockchaininfo')
|
||||
self.rpc_wallet('getwalletinfo' if with_wallet else 'getblockchaininfo')
|
||||
|
||||
def getDaemonVersion(self):
|
||||
return self.rpc_callback('getnetworkinfo')['version']
|
||||
return self.rpc('getnetworkinfo')['version']
|
||||
|
||||
def getBlockchainInfo(self):
|
||||
return self.rpc_callback('getblockchaininfo')
|
||||
return self.rpc('getblockchaininfo')
|
||||
|
||||
def getChainHeight(self) -> int:
|
||||
return self.rpc_callback('getblockcount')
|
||||
return self.rpc('getblockcount')
|
||||
|
||||
def getMempoolTx(self, txid):
|
||||
return self.rpc_callback('getrawtransaction', [txid.hex()])
|
||||
return self.rpc('getrawtransaction', [txid.hex()])
|
||||
|
||||
def getBlockHeaderFromHeight(self, height):
|
||||
block_hash = self.rpc_callback('getblockhash', [height])
|
||||
return self.rpc_callback('getblockheader', [block_hash])
|
||||
block_hash = self.rpc('getblockhash', [height])
|
||||
return self.rpc('getblockheader', [block_hash])
|
||||
|
||||
def getBlockHeader(self, block_hash):
|
||||
return self.rpc_callback('getblockheader', [block_hash])
|
||||
return self.rpc('getblockheader', [block_hash])
|
||||
|
||||
def getBlockHeaderAt(self, time: int, block_after=False):
|
||||
blockchaininfo = self.rpc_callback('getblockchaininfo')
|
||||
last_block_header = self.rpc_callback('getblockheader', [blockchaininfo['bestblockhash']])
|
||||
blockchaininfo = self.rpc('getblockchaininfo')
|
||||
last_block_header = self.rpc('getblockheader', [blockchaininfo['bestblockhash']])
|
||||
|
||||
max_tries = 5000
|
||||
for i in range(max_tries):
|
||||
prev_block_header = self.rpc_callback('getblock', [last_block_header['previousblockhash']])
|
||||
prev_block_header = self.rpc('getblock', [last_block_header['previousblockhash']])
|
||||
if prev_block_header['time'] <= time:
|
||||
return last_block_header if block_after else prev_block_header
|
||||
|
||||
@@ -275,11 +294,10 @@ class BTCInterface(CoinInterface):
|
||||
|
||||
def initialiseWallet(self, key_bytes: bytes) -> None:
|
||||
key_wif = self.encodeKey(key_bytes)
|
||||
|
||||
self.rpc_callback('sethdseed', [True, key_wif])
|
||||
self.rpc_wallet('sethdseed', [True, key_wif])
|
||||
|
||||
def getWalletInfo(self):
|
||||
rv = self.rpc_callback('getwalletinfo')
|
||||
rv = self.rpc_wallet('getwalletinfo')
|
||||
rv['encrypted'] = 'unlocked_until' in rv
|
||||
rv['locked'] = rv.get('unlocked_until', 1) <= 0
|
||||
return rv
|
||||
@@ -288,7 +306,7 @@ class BTCInterface(CoinInterface):
|
||||
return self._restore_height
|
||||
|
||||
def getWalletRestoreHeight(self) -> int:
|
||||
start_time = self.rpc_callback('getwalletinfo')['keypoololdest']
|
||||
start_time = self.rpc_wallet('getwalletinfo')['keypoololdest']
|
||||
|
||||
blockchaininfo = self.getBlockchainInfo()
|
||||
best_block = blockchaininfo['bestblockhash']
|
||||
@@ -312,7 +330,7 @@ class BTCInterface(CoinInterface):
|
||||
raise ValueError('{} wallet restore height not found.'.format(self.coin_name()))
|
||||
|
||||
def getWalletSeedID(self) -> str:
|
||||
wi = self.rpc_callback('getwalletinfo')
|
||||
wi = self.rpc_wallet('getwalletinfo')
|
||||
return 'Not found' if 'hdseedid' not in wi else wi['hdseedid']
|
||||
|
||||
def checkExpectedSeed(self, expect_seedid) -> bool:
|
||||
@@ -323,11 +341,11 @@ class BTCInterface(CoinInterface):
|
||||
args = [label]
|
||||
if use_segwit:
|
||||
args.append('bech32')
|
||||
return self.rpc_callback('getnewaddress', args)
|
||||
return self.rpc_wallet('getnewaddress', args)
|
||||
|
||||
def isValidAddress(self, address: str) -> bool:
|
||||
try:
|
||||
rv = self.rpc_callback('validateaddress', [address])
|
||||
rv = self.rpc_wallet('validateaddress', [address])
|
||||
if rv['isvalid'] is True:
|
||||
return True
|
||||
except Exception as ex:
|
||||
@@ -347,13 +365,13 @@ class BTCInterface(CoinInterface):
|
||||
return False
|
||||
|
||||
def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
|
||||
addr_info = self.rpc_callback('getaddressinfo', [address])
|
||||
addr_info = self.rpc_wallet('getaddressinfo', [address])
|
||||
if not or_watch_only:
|
||||
return addr_info['ismine']
|
||||
return addr_info['ismine'] or addr_info['iswatchonly']
|
||||
|
||||
def checkAddressMine(self, address: str) -> None:
|
||||
addr_info = self.rpc_callback('getaddressinfo', [address])
|
||||
addr_info = self.rpc_wallet('getaddressinfo', [address])
|
||||
ensure(addr_info['ismine'], 'ismine is false')
|
||||
if self.sc._restrict_unknown_seed_wallets:
|
||||
ensure(addr_info['hdseedid'] == self._expect_seedid_hex, 'unexpected seedid')
|
||||
@@ -369,16 +387,16 @@ class BTCInterface(CoinInterface):
|
||||
|
||||
def try_get_fee_rate(self, conf_target):
|
||||
try:
|
||||
fee_rate = self.rpc_callback('estimatesmartfee', [conf_target])['feerate']
|
||||
fee_rate = self.rpc_wallet('estimatesmartfee', [conf_target])['feerate']
|
||||
assert (fee_rate > 0.0), 'Non positive feerate'
|
||||
return fee_rate, 'estimatesmartfee'
|
||||
except Exception:
|
||||
try:
|
||||
fee_rate = self.rpc_callback('getwalletinfo')['paytxfee']
|
||||
fee_rate = self.rpc_wallet('getwalletinfo')['paytxfee']
|
||||
assert (fee_rate > 0.0), 'Non positive feerate'
|
||||
return fee_rate, 'paytxfee'
|
||||
except Exception:
|
||||
return self.rpc_callback('getnetworkinfo')['relayfee'], 'relayfee'
|
||||
return self.rpc('getnetworkinfo')['relayfee'], 'relayfee'
|
||||
|
||||
fee_rate, rate_src = try_get_fee_rate(self, conf_target)
|
||||
if min_relay_fee and min_relay_fee > fee_rate:
|
||||
@@ -734,7 +752,7 @@ class BTCInterface(CoinInterface):
|
||||
add_bytes = 0
|
||||
add_witness_bytes = getCompactSizeLen(len(tx.vin))
|
||||
for pi in tx.vin:
|
||||
ptx = self.rpc_callback('getrawtransaction', [i2h(pi.prevout.hash), True])
|
||||
ptx = self.rpc('getrawtransaction', [i2h(pi.prevout.hash), True])
|
||||
prevout = ptx['vout'][pi.prevout.n]
|
||||
inputs_value += make_int(prevout['value'])
|
||||
|
||||
@@ -942,13 +960,13 @@ class BTCInterface(CoinInterface):
|
||||
'lockUnspents': True,
|
||||
'feeRate': feerate_str,
|
||||
}
|
||||
rv = self.rpc_callback('fundrawtransaction', [tx.hex(), options])
|
||||
rv = self.rpc_wallet('fundrawtransaction', [tx.hex(), options])
|
||||
return bytes.fromhex(rv['hex'])
|
||||
|
||||
def listInputs(self, tx_bytes):
|
||||
tx = self.loadTx(tx_bytes)
|
||||
|
||||
all_locked = self.rpc_callback('listlockunspent')
|
||||
all_locked = self.rpc_wallet('listlockunspent')
|
||||
inputs = []
|
||||
for pi in tx.vin:
|
||||
txid_hex = i2h(pi.prevout.hash)
|
||||
@@ -962,19 +980,19 @@ class BTCInterface(CoinInterface):
|
||||
inputs = []
|
||||
for pi in tx.vin:
|
||||
inputs.append({'txid': i2h(pi.prevout.hash), 'vout': pi.prevout.n})
|
||||
self.rpc_callback('lockunspent', [True, inputs])
|
||||
self.rpc('lockunspent', [True, inputs])
|
||||
|
||||
def signTxWithWallet(self, tx: bytes) -> bytes:
|
||||
rv = self.rpc_callback('signrawtransactionwithwallet', [tx.hex()])
|
||||
rv = self.rpc_wallet('signrawtransactionwithwallet', [tx.hex()])
|
||||
return bytes.fromhex(rv['hex'])
|
||||
|
||||
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
|
||||
key_wif = self.encodeKey(key)
|
||||
rv = self.rpc_callback('signrawtransactionwithkey', [tx.hex(), [key_wif, ]])
|
||||
rv = self.rpc('signrawtransactionwithkey', [tx.hex(), [key_wif, ]])
|
||||
return bytes.fromhex(rv['hex'])
|
||||
|
||||
def publishTx(self, tx: bytes):
|
||||
return self.rpc_callback('sendrawtransaction', [tx.hex()])
|
||||
return self.rpc('sendrawtransaction', [tx.hex()])
|
||||
|
||||
def encodeTx(self, tx) -> bytes:
|
||||
return tx.serialize()
|
||||
@@ -1018,18 +1036,18 @@ class BTCInterface(CoinInterface):
|
||||
return self.getScriptForPubkeyHash(self.getPubkeyHash(K))
|
||||
|
||||
def scanTxOutset(self, dest):
|
||||
return self.rpc_callback('scantxoutset', ['start', ['raw({})'.format(dest.hex())]])
|
||||
return self.rpc('scantxoutset', ['start', ['raw({})'.format(dest.hex())]])
|
||||
|
||||
def getTransaction(self, txid: bytes):
|
||||
try:
|
||||
return bytes.fromhex(self.rpc_callback('getrawtransaction', [txid.hex()]))
|
||||
return bytes.fromhex(self.rpc('getrawtransaction', [txid.hex()]))
|
||||
except Exception as ex:
|
||||
# TODO: filter errors
|
||||
return None
|
||||
|
||||
def getWalletTransaction(self, txid: bytes):
|
||||
try:
|
||||
return bytes.fromhex(self.rpc_callback('gettransaction', [txid.hex()]))
|
||||
return bytes.fromhex(self.rpc('gettransaction', [txid.hex()]))
|
||||
except Exception as ex:
|
||||
# TODO: filter errors
|
||||
return None
|
||||
@@ -1115,7 +1133,7 @@ class BTCInterface(CoinInterface):
|
||||
|
||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int) -> bytes:
|
||||
self._log.info('spendBLockTx %s:\n', chain_b_lock_txid.hex())
|
||||
wtx = self.rpc_callback('gettransaction', [chain_b_lock_txid.hex(), ])
|
||||
wtx = self.rpc_wallet('gettransaction', [chain_b_lock_txid.hex(), ])
|
||||
lock_tx = self.loadTx(bytes.fromhex(wtx['hex']))
|
||||
|
||||
Kbs = self.getPubkey(kbs)
|
||||
@@ -1144,10 +1162,10 @@ class BTCInterface(CoinInterface):
|
||||
return bytes.fromhex(self.publishTx(b_lock_spend_tx))
|
||||
|
||||
def importWatchOnlyAddress(self, address: str, label: str):
|
||||
self.rpc_callback('importaddress', [address, label, False])
|
||||
self.rpc_wallet('importaddress', [address, label, False])
|
||||
|
||||
def isWatchOnlyAddress(self, address: str):
|
||||
addr_info = self.rpc_callback('getaddressinfo', [address])
|
||||
addr_info = self.rpc_wallet('getaddressinfo', [address])
|
||||
return addr_info['iswatchonly']
|
||||
|
||||
def getSCLockScriptAddress(self, lock_script):
|
||||
@@ -1161,11 +1179,11 @@ class BTCInterface(CoinInterface):
|
||||
self.importWatchOnlyAddress(dest_address, 'bid')
|
||||
self._log.info('Imported watch-only addr: {}'.format(dest_address))
|
||||
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), rescan_from))
|
||||
self.rpc_callback('rescanblockchain', [rescan_from])
|
||||
self.rpc_wallet('rescanblockchain', [rescan_from])
|
||||
|
||||
return_txid = True if txid is None else False
|
||||
if txid is None:
|
||||
txns = self.rpc_callback('listunspent', [0, 9999999, [dest_address, ]])
|
||||
txns = self.rpc_wallet('listunspent', [0, 9999999, [dest_address, ]])
|
||||
|
||||
for tx in txns:
|
||||
if self.make_int(tx['amount']) == bid_amount:
|
||||
@@ -1176,11 +1194,11 @@ class BTCInterface(CoinInterface):
|
||||
return None
|
||||
|
||||
try:
|
||||
tx = self.rpc_callback('gettransaction', [txid.hex()])
|
||||
tx = self.rpc_wallet('gettransaction', [txid.hex()])
|
||||
|
||||
block_height = 0
|
||||
if 'blockhash' in tx:
|
||||
block_header = self.rpc_callback('getblockheader', [tx['blockhash']])
|
||||
block_header = self.rpc('getblockheader', [tx['blockhash']])
|
||||
block_height = block_header['height']
|
||||
|
||||
rv = {
|
||||
@@ -1192,7 +1210,7 @@ class BTCInterface(CoinInterface):
|
||||
return None
|
||||
|
||||
if find_index:
|
||||
tx_obj = self.rpc_callback('decoderawtransaction', [tx['hex']])
|
||||
tx_obj = self.rpc('decoderawtransaction', [tx['hex']])
|
||||
rv['index'] = find_vout_for_address_from_txobj(tx_obj, dest_address)
|
||||
|
||||
if return_txid:
|
||||
@@ -1202,7 +1220,7 @@ class BTCInterface(CoinInterface):
|
||||
|
||||
def getOutput(self, txid, dest_script, expect_value, xmr_swap=None):
|
||||
# TODO: Use getrawtransaction if txindex is active
|
||||
utxos = self.rpc_callback('scantxoutset', ['start', ['raw({})'.format(dest_script.hex())]])
|
||||
utxos = self.rpc('scantxoutset', ['start', ['raw({})'.format(dest_script.hex())]])
|
||||
if 'height' in utxos: # chain_height not returned by v18 codebase
|
||||
chain_height = utxos['height']
|
||||
else:
|
||||
@@ -1225,7 +1243,7 @@ class BTCInterface(CoinInterface):
|
||||
|
||||
def withdrawCoin(self, value, addr_to, subfee):
|
||||
params = [addr_to, value, '', '', subfee, True, self._conf_target]
|
||||
return self.rpc_callback('sendtoaddress', params)
|
||||
return self.rpc_wallet('sendtoaddress', params)
|
||||
|
||||
def signCompact(self, k, message):
|
||||
message_hash = hashlib.sha256(bytes(message, 'utf-8')).digest()
|
||||
@@ -1318,10 +1336,10 @@ class BTCInterface(CoinInterface):
|
||||
return length
|
||||
|
||||
def describeTx(self, tx_hex: str):
|
||||
return self.rpc_callback('decoderawtransaction', [tx_hex])
|
||||
return self.rpc('decoderawtransaction', [tx_hex])
|
||||
|
||||
def getSpendableBalance(self) -> int:
|
||||
return self.make_int(self.rpc_callback('getbalances')['mine']['trusted'])
|
||||
return self.make_int(self.rpc_wallet('getbalances')['mine']['trusted'])
|
||||
|
||||
def createUTXO(self, value_sats: int):
|
||||
# Create a new address and send value_sats to it
|
||||
@@ -1334,7 +1352,7 @@ class BTCInterface(CoinInterface):
|
||||
return self.withdrawCoin(self.format_amount(value_sats), address, False), address
|
||||
|
||||
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
|
||||
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
||||
txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
||||
|
||||
options = {
|
||||
'lockUnspents': lock_unspents,
|
||||
@@ -1342,18 +1360,18 @@ class BTCInterface(CoinInterface):
|
||||
}
|
||||
if sub_fee:
|
||||
options['subtractFeeFromOutputs'] = [0,]
|
||||
return self.rpc_callback('fundrawtransaction', [txn, options])['hex']
|
||||
return self.rpc_wallet('fundrawtransaction', [txn, options])['hex']
|
||||
|
||||
def createRawSignedTransaction(self, addr_to, amount) -> str:
|
||||
txn_funded = self.createRawFundedTransaction(addr_to, amount)
|
||||
return self.rpc_callback('signrawtransactionwithwallet', [txn_funded])['hex']
|
||||
return self.rpc_wallet('signrawtransactionwithwallet', [txn_funded])['hex']
|
||||
|
||||
def getBlockWithTxns(self, block_hash: str):
|
||||
return self.rpc_callback('getblock', [block_hash, 2])
|
||||
return self.rpc('getblock', [block_hash, 2])
|
||||
|
||||
def getUnspentsByAddr(self):
|
||||
unspent_addr = dict()
|
||||
unspent = self.rpc_callback('listunspent')
|
||||
unspent = self.rpc_wallet('listunspent')
|
||||
for u in unspent:
|
||||
if u['spendable'] is not True:
|
||||
continue
|
||||
@@ -1361,11 +1379,11 @@ class BTCInterface(CoinInterface):
|
||||
return unspent_addr
|
||||
|
||||
def getUTXOBalance(self, address: str):
|
||||
num_blocks = self.rpc_callback('getblockcount')
|
||||
num_blocks = self.rpc('getblockcount')
|
||||
|
||||
sum_unspent = 0
|
||||
self._log.debug('[rm] scantxoutset start') # scantxoutset is slow
|
||||
ro = self.rpc_callback('scantxoutset', ['start', ['addr({})'.format(address)]]) # TODO: Use combo(address) where possible
|
||||
ro = self.rpc('scantxoutset', ['start', ['addr({})'.format(address)]]) # TODO: Use combo(address) where possible
|
||||
self._log.debug('[rm] scantxoutset end')
|
||||
for o in ro['unspents']:
|
||||
sum_unspent += self.make_int(o['amount'])
|
||||
@@ -1391,7 +1409,7 @@ class BTCInterface(CoinInterface):
|
||||
sign_for_addr = self.pkh_to_address(pkh)
|
||||
self._log.debug('sign_for_addr converted %s', sign_for_addr)
|
||||
|
||||
signature = self.rpc_callback('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + extra_commit_bytes.hex()])
|
||||
signature = self.rpc_wallet('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + extra_commit_bytes.hex()])
|
||||
|
||||
prove_utxos = [] # TODO: Send specific utxos
|
||||
return (sign_for_addr, signature, prove_utxos)
|
||||
@@ -1416,17 +1434,17 @@ class BTCInterface(CoinInterface):
|
||||
return self.getUTXOBalance(address)
|
||||
|
||||
def isWalletEncrypted(self) -> bool:
|
||||
wallet_info = self.rpc_callback('getwalletinfo')
|
||||
wallet_info = self.rpc_wallet('getwalletinfo')
|
||||
return 'unlocked_until' in wallet_info
|
||||
|
||||
def isWalletLocked(self) -> bool:
|
||||
wallet_info = self.rpc_callback('getwalletinfo')
|
||||
wallet_info = self.rpc_wallet('getwalletinfo')
|
||||
if 'unlocked_until' in wallet_info and wallet_info['unlocked_until'] <= 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
def isWalletEncryptedLocked(self):
|
||||
wallet_info = self.rpc_callback('getwalletinfo')
|
||||
wallet_info = self.rpc_wallet('getwalletinfo')
|
||||
encrypted = 'unlocked_until' in wallet_info
|
||||
locked = encrypted and wallet_info['unlocked_until'] <= 0
|
||||
return encrypted, locked
|
||||
@@ -1436,8 +1454,8 @@ class BTCInterface(CoinInterface):
|
||||
if old_password == '':
|
||||
if self.isWalletEncrypted():
|
||||
raise ValueError('Old password must be set')
|
||||
return self.rpc_callback('encryptwallet', [new_password])
|
||||
self.rpc_callback('walletpassphrasechange', [old_password, new_password])
|
||||
return self.rpc_wallet('encryptwallet', [new_password])
|
||||
self.rpc_wallet('walletpassphrasechange', [old_password, new_password])
|
||||
|
||||
def unlockWallet(self, password: str):
|
||||
if password == '':
|
||||
@@ -1447,21 +1465,20 @@ class BTCInterface(CoinInterface):
|
||||
if self.coin_type() == Coins.BTC:
|
||||
# Recreate wallet if none found
|
||||
# Required when encrypting an existing btc wallet, workaround is to delete the btc wallet and recreate
|
||||
wallets = self.rpc_callback('listwallets')
|
||||
wallets = self.rpc('listwallets')
|
||||
if len(wallets) < 1:
|
||||
self._log.info('Creating wallet.dat for {}.'.format(self.coin_name()))
|
||||
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
|
||||
self.rpc_callback('createwallet', ['wallet.dat', False, True, '', False, False])
|
||||
self.rpc_callback('encryptwallet', [password])
|
||||
self.rpc('createwallet', ['wallet.dat', False, True, '', False, False])
|
||||
self.rpc_wallet('encryptwallet', [password])
|
||||
|
||||
# Max timeout value, ~3 years
|
||||
self.rpc_callback('walletpassphrase', [password, 100000000])
|
||||
|
||||
self.rpc_wallet('walletpassphrase', [password, 100000000])
|
||||
self._sc.checkWalletSeed(self.coin_type())
|
||||
|
||||
def lockWallet(self):
|
||||
self._log.info('lockWallet - {}'.format(self.ticker()))
|
||||
self.rpc_callback('walletlock')
|
||||
self.rpc_wallet('walletlock')
|
||||
|
||||
def get_p2sh_script_pubkey(self, script: bytearray) -> bytearray:
|
||||
script_hash = hash160(script)
|
||||
@@ -1474,7 +1491,7 @@ class BTCInterface(CoinInterface):
|
||||
def findTxnByHash(self, txid_hex: str):
|
||||
# Only works for wallet txns
|
||||
try:
|
||||
rv = self.rpc_callback('gettransaction', [txid_hex])
|
||||
rv = self.rpc_wallet('gettransaction', [txid_hex])
|
||||
except Exception as ex:
|
||||
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
||||
return None
|
||||
|
||||
@@ -32,7 +32,7 @@ class DASHInterface(BTCInterface):
|
||||
words = self.seedToMnemonic(key)
|
||||
|
||||
mnemonic_passphrase = ''
|
||||
self.rpc_callback('upgradetohd', [words, mnemonic_passphrase, self._wallet_passphrase])
|
||||
self.rpc_wallet('upgradetohd', [words, mnemonic_passphrase, self._wallet_passphrase])
|
||||
self._have_checked_seed = False
|
||||
if self._wallet_passphrase != '':
|
||||
self.unlockWallet(self._wallet_passphrase)
|
||||
@@ -42,7 +42,7 @@ class DASHInterface(BTCInterface):
|
||||
|
||||
def checkExpectedSeed(self, key_hash: str):
|
||||
try:
|
||||
rv = self.rpc_callback('dumphdinfo')
|
||||
rv = self.rpc_wallet('dumphdinfo')
|
||||
entropy = Mnemonic('english').to_entropy(rv['mnemonic'].split(' '))
|
||||
entropy_hash = self.getAddressHashFromKey(entropy)[::-1].hex()
|
||||
self._have_checked_seed = True
|
||||
@@ -53,10 +53,10 @@ class DASHInterface(BTCInterface):
|
||||
|
||||
def withdrawCoin(self, value, addr_to, subfee):
|
||||
params = [addr_to, value, '', '', subfee, False, False, self._conf_target]
|
||||
return self.rpc_callback('sendtoaddress', params)
|
||||
return self.rpc_wallet('sendtoaddress', params)
|
||||
|
||||
def getSpendableBalance(self) -> int:
|
||||
return self.make_int(self.rpc_callback('getwalletinfo')['balance'])
|
||||
return self.make_int(self.rpc_wallet('getwalletinfo')['balance'])
|
||||
|
||||
def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray:
|
||||
# Return P2PKH
|
||||
@@ -72,7 +72,7 @@ class DASHInterface(BTCInterface):
|
||||
def findTxnByHash(self, txid_hex: str):
|
||||
# Only works for wallet txns
|
||||
try:
|
||||
rv = self.rpc_callback('gettransaction', [txid_hex])
|
||||
rv = self.rpc_wallet('gettransaction', [txid_hex])
|
||||
except Exception as ex:
|
||||
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
||||
return None
|
||||
|
||||
@@ -13,6 +13,7 @@ from basicswap.util import (
|
||||
i2b,
|
||||
ensure,
|
||||
)
|
||||
from basicswap.rpc import make_rpc_func
|
||||
from basicswap.util.crypto import hash160
|
||||
from basicswap.util.address import decodeAddress
|
||||
from basicswap.chainparams import Coins
|
||||
@@ -36,6 +37,14 @@ class FIROInterface(BTCInterface):
|
||||
def coin_type():
|
||||
return Coins.FIRO
|
||||
|
||||
def __init__(self, coin_settings, network, swap_client=None):
|
||||
super(FIROInterface, self).__init__(coin_settings, network, swap_client)
|
||||
# No multiwallet support
|
||||
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
|
||||
|
||||
def checkWallets(self) -> int:
|
||||
return 1
|
||||
|
||||
def getExchangeName(self, exchange_name):
|
||||
return 'zcoin'
|
||||
|
||||
@@ -44,9 +53,9 @@ class FIROInterface(BTCInterface):
|
||||
pass
|
||||
|
||||
def getNewAddress(self, use_segwit, label='swap_receive'):
|
||||
return self.rpc_callback('getnewaddress', [label])
|
||||
# addr_plain = self.rpc_callback('getnewaddress', [label])
|
||||
# return self.rpc_callback('addwitnessaddress', [addr_plain])
|
||||
return self.rpc('getnewaddress', [label])
|
||||
# addr_plain = self.rpc('getnewaddress', [label])
|
||||
# return self.rpc('addwitnessaddress', [addr_plain])
|
||||
|
||||
def decodeAddress(self, address):
|
||||
return decodeAddress(address)[1:]
|
||||
@@ -58,11 +67,11 @@ class FIROInterface(BTCInterface):
|
||||
raise ValueError('TODO')
|
||||
|
||||
def isWatchOnlyAddress(self, address):
|
||||
addr_info = self.rpc_callback('validateaddress', [address])
|
||||
addr_info = self.rpc('validateaddress', [address])
|
||||
return addr_info['iswatchonly']
|
||||
|
||||
def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
|
||||
addr_info = self.rpc_callback('validateaddress', [address])
|
||||
addr_info = self.rpc('validateaddress', [address])
|
||||
if not or_watch_only:
|
||||
return addr_info['ismine']
|
||||
return addr_info['ismine'] or addr_info['iswatchonly']
|
||||
@@ -73,8 +82,8 @@ class FIROInterface(BTCInterface):
|
||||
|
||||
if not self.isAddressMine(address, or_watch_only=True):
|
||||
# Expects P2WSH nested in BIP16_P2SH
|
||||
ro = self.rpc_callback('importaddress', [lock_tx_dest.hex(), 'bid lock', False, True])
|
||||
addr_info = self.rpc_callback('validateaddress', [address])
|
||||
ro = self.rpc('importaddress', [lock_tx_dest.hex(), 'bid lock', False, True])
|
||||
addr_info = self.rpc('validateaddress', [address])
|
||||
|
||||
return address
|
||||
|
||||
@@ -89,7 +98,7 @@ class FIROInterface(BTCInterface):
|
||||
|
||||
return_txid = True if txid is None else False
|
||||
if txid is None:
|
||||
txns = self.rpc_callback('listunspent', [0, 9999999, [dest_address, ]])
|
||||
txns = self.rpc('listunspent', [0, 9999999, [dest_address, ]])
|
||||
|
||||
for tx in txns:
|
||||
if self.make_int(tx['amount']) == bid_amount:
|
||||
@@ -100,11 +109,11 @@ class FIROInterface(BTCInterface):
|
||||
return None
|
||||
|
||||
try:
|
||||
tx = self.rpc_callback('gettransaction', [txid.hex()])
|
||||
tx = self.rpc('gettransaction', [txid.hex()])
|
||||
|
||||
block_height = 0
|
||||
if 'blockhash' in tx:
|
||||
block_header = self.rpc_callback('getblockheader', [tx['blockhash']])
|
||||
block_header = self.rpc('getblockheader', [tx['blockhash']])
|
||||
block_height = block_header['height']
|
||||
|
||||
rv = {
|
||||
@@ -116,7 +125,7 @@ class FIROInterface(BTCInterface):
|
||||
return None
|
||||
|
||||
if find_index:
|
||||
tx_obj = self.rpc_callback('decoderawtransaction', [tx['hex']])
|
||||
tx_obj = self.rpc('decoderawtransaction', [tx['hex']])
|
||||
rv['index'] = find_vout_for_address_from_txobj(tx_obj, dest_address)
|
||||
|
||||
if return_txid:
|
||||
@@ -135,11 +144,11 @@ class FIROInterface(BTCInterface):
|
||||
return self.fundTx(tx_bytes, feerate)
|
||||
|
||||
def signTxWithWallet(self, tx):
|
||||
rv = self.rpc_callback('signrawtransaction', [tx.hex()])
|
||||
rv = self.rpc('signrawtransaction', [tx.hex()])
|
||||
return bytes.fromhex(rv['hex'])
|
||||
|
||||
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
|
||||
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
||||
txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
||||
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
||||
self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}')
|
||||
options = {
|
||||
@@ -148,11 +157,11 @@ class FIROInterface(BTCInterface):
|
||||
}
|
||||
if sub_fee:
|
||||
options['subtractFeeFromOutputs'] = [0,]
|
||||
return self.rpc_callback('fundrawtransaction', [txn, options])['hex']
|
||||
return self.rpc('fundrawtransaction', [txn, options])['hex']
|
||||
|
||||
def createRawSignedTransaction(self, addr_to, amount) -> str:
|
||||
txn_funded = self.createRawFundedTransaction(addr_to, amount)
|
||||
return self.rpc_callback('signrawtransaction', [txn_funded])['hex']
|
||||
return self.rpc('signrawtransaction', [txn_funded])['hex']
|
||||
|
||||
def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray:
|
||||
# Return P2PKH
|
||||
@@ -180,13 +189,13 @@ class FIROInterface(BTCInterface):
|
||||
|
||||
def withdrawCoin(self, value, addr_to, subfee):
|
||||
params = [addr_to, value, '', '', subfee]
|
||||
return self.rpc_callback('sendtoaddress', params)
|
||||
return self.rpc('sendtoaddress', params)
|
||||
|
||||
def getWalletSeedID(self):
|
||||
return self.rpc_callback('getwalletinfo')['hdmasterkeyid']
|
||||
return self.rpc('getwalletinfo')['hdmasterkeyid']
|
||||
|
||||
def getSpendableBalance(self) -> int:
|
||||
return self.make_int(self.rpc_callback('getwalletinfo')['balance'])
|
||||
return self.make_int(self.rpc('getwalletinfo')['balance'])
|
||||
|
||||
def getBLockSpendTxFee(self, tx, fee_rate: int) -> int:
|
||||
add_bytes = 107
|
||||
@@ -197,13 +206,13 @@ class FIROInterface(BTCInterface):
|
||||
|
||||
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
|
||||
key_wif = self.encodeKey(key)
|
||||
rv = self.rpc_callback('signrawtransaction', [tx.hex(), [], [key_wif, ]])
|
||||
rv = self.rpc('signrawtransaction', [tx.hex(), [], [key_wif, ]])
|
||||
return bytes.fromhex(rv['hex'])
|
||||
|
||||
def findTxnByHash(self, txid_hex: str):
|
||||
# Only works for wallet txns
|
||||
try:
|
||||
rv = self.rpc_callback('gettransaction', [txid_hex])
|
||||
rv = self.rpc('gettransaction', [txid_hex])
|
||||
except Exception as ex:
|
||||
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
||||
return None
|
||||
@@ -216,7 +225,7 @@ class FIROInterface(BTCInterface):
|
||||
# TODO: Lock unspent and use same output/s to fund bid
|
||||
|
||||
unspents_by_addr = dict()
|
||||
unspents = self.rpc_callback('listunspent')
|
||||
unspents = self.rpc('listunspent')
|
||||
for u in unspents:
|
||||
if u['spendable'] is not True:
|
||||
continue
|
||||
@@ -276,7 +285,7 @@ class FIROInterface(BTCInterface):
|
||||
sign_for_addr = self.pkh_to_address(pkh)
|
||||
self._log.debug('sign_for_addr converted %s', sign_for_addr)
|
||||
|
||||
signature = self.rpc_callback('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex()])
|
||||
signature = self.rpc('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex()])
|
||||
|
||||
return (sign_for_addr, signature, prove_utxos)
|
||||
|
||||
@@ -296,7 +305,7 @@ class FIROInterface(BTCInterface):
|
||||
|
||||
sum_value: int = 0
|
||||
for outpoint in utxos:
|
||||
txout = self.rpc_callback('gettxout', [outpoint[0].hex(), outpoint[1]])
|
||||
txout = self.rpc('gettxout', [outpoint[0].hex(), outpoint[1]])
|
||||
sum_value += self.make_int(txout['value'])
|
||||
|
||||
return sum_value
|
||||
@@ -307,15 +316,15 @@ class FIROInterface(BTCInterface):
|
||||
chain_blocks: int = self.getChainHeight()
|
||||
|
||||
current_height: int = chain_blocks
|
||||
block_hash = self.rpc_callback('getblockhash', [current_height])
|
||||
block_hash = self.rpc('getblockhash', [current_height])
|
||||
|
||||
script_hash: bytes = self.decodeAddress(addr_find)
|
||||
find_scriptPubKey = self.getDestForScriptHash(script_hash)
|
||||
|
||||
while current_height > height_start:
|
||||
block_hash = self.rpc_callback('getblockhash', [current_height])
|
||||
block_hash = self.rpc('getblockhash', [current_height])
|
||||
|
||||
block = self.rpc_callback('getblock', [block_hash, False])
|
||||
block = self.rpc('getblock', [block_hash, False])
|
||||
decoded_block = CBlock()
|
||||
decoded_block = FromHex(decoded_block, block)
|
||||
for tx in decoded_block.vtx:
|
||||
@@ -325,22 +334,22 @@ class FIROInterface(BTCInterface):
|
||||
txid = i2b(tx.sha256)
|
||||
self._log.info('Found output to addr: {} in tx {} in block {}'.format(addr_find, txid.hex(), block_hash))
|
||||
self._log.info('rescanblockchain hack invalidateblock {}'.format(block_hash))
|
||||
self.rpc_callback('invalidateblock', [block_hash])
|
||||
self.rpc_callback('reconsiderblock', [block_hash])
|
||||
self.rpc('invalidateblock', [block_hash])
|
||||
self.rpc('reconsiderblock', [block_hash])
|
||||
return
|
||||
current_height -= 1
|
||||
|
||||
def getBlockWithTxns(self, block_hash):
|
||||
# TODO: Bypass decoderawtransaction and getblockheader
|
||||
block = self.rpc_callback('getblock', [block_hash, False])
|
||||
block_header = self.rpc_callback('getblockheader', [block_hash])
|
||||
block = self.rpc('getblock', [block_hash, False])
|
||||
block_header = self.rpc('getblockheader', [block_hash])
|
||||
decoded_block = CBlock()
|
||||
decoded_block = FromHex(decoded_block, block)
|
||||
|
||||
tx_rv = []
|
||||
for tx in decoded_block.vtx:
|
||||
tx_hex = tx.serialize_with_witness().hex()
|
||||
tx_dec = self.rpc_callback('decoderawtransaction', [tx_hex])
|
||||
tx_dec = self.rpc('decoderawtransaction', [tx_hex])
|
||||
if 'hex' not in tx_dec:
|
||||
tx_dec['hex'] = tx_hex
|
||||
|
||||
|
||||
@@ -1,15 +1,117 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2020 tecnovert
|
||||
# Copyright (c) 2020-2023 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
from .btc import BTCInterface
|
||||
from basicswap.chainparams import Coins
|
||||
from basicswap.rpc import make_rpc_func
|
||||
from basicswap.chainparams import Coins, chainparams
|
||||
|
||||
|
||||
class LTCInterface(BTCInterface):
|
||||
@staticmethod
|
||||
def coin_type():
|
||||
return Coins.LTC
|
||||
|
||||
def __init__(self, coin_settings, network, swap_client=None):
|
||||
super(LTCInterface, self).__init__(coin_settings, network, swap_client)
|
||||
self._rpc_wallet_mweb = 'mweb'
|
||||
self.rpc_wallet_mweb = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet_mweb)
|
||||
|
||||
def getNewMwebAddress(self, use_segwit=False, label='swap_receive') -> str:
|
||||
return self.rpc_wallet_mweb('getnewaddress', [label, 'mweb'])
|
||||
|
||||
def getNewStealthAddress(self, label=''):
|
||||
return self.getNewMwebAddress(False, label)
|
||||
|
||||
def withdrawCoin(self, value, type_from: str, addr_to: str, subfee: bool) -> str:
|
||||
params = [addr_to, value, '', '', subfee, True, self._conf_target]
|
||||
if type_from == 'mweb':
|
||||
return self.rpc_wallet_mweb('sendtoaddress', params)
|
||||
return self.rpc_wallet('sendtoaddress', params)
|
||||
|
||||
def getWalletInfo(self):
|
||||
rv = self.rpc_wallet('getwalletinfo')
|
||||
rv['encrypted'] = 'unlocked_until' in rv
|
||||
rv['locked'] = rv.get('unlocked_until', 1) <= 0
|
||||
|
||||
mweb_info = self.rpc_wallet_mweb('getwalletinfo')
|
||||
rv['mweb_balance'] = mweb_info['balance']
|
||||
rv['mweb_unconfirmed'] = mweb_info['unconfirmed_balance']
|
||||
rv['mweb_immature'] = mweb_info['immature_balance']
|
||||
return rv
|
||||
|
||||
|
||||
class LTCInterfaceMWEB(LTCInterface):
|
||||
@staticmethod
|
||||
def coin_type():
|
||||
return Coins.LTC_MWEB
|
||||
|
||||
def __init__(self, coin_settings, network, swap_client=None):
|
||||
super(LTCInterfaceMWEB, self).__init__(coin_settings, network, swap_client)
|
||||
self._rpc_wallet = 'mweb'
|
||||
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet)
|
||||
|
||||
def chainparams(self):
|
||||
return chainparams[Coins.LTC]
|
||||
|
||||
def chainparams_network(self):
|
||||
return chainparams[Coins.LTC][self._network]
|
||||
|
||||
def coin_name(self) -> str:
|
||||
coin_chainparams = chainparams[Coins.LTC]
|
||||
if coin_chainparams.get('use_ticker_as_name', False):
|
||||
return coin_chainparams['ticker'] + ' MWEB'
|
||||
return coin_chainparams['name'].capitalize() + ' MWEB'
|
||||
|
||||
def ticker(self) -> str:
|
||||
ticker = chainparams[Coins.LTC]['ticker']
|
||||
if self._network == 'testnet':
|
||||
ticker = 't' + ticker
|
||||
elif self._network == 'regtest':
|
||||
ticker = 'rt' + ticker
|
||||
return ticker + '_MWEB'
|
||||
|
||||
def getNewAddress(self, use_segwit=False, label='swap_receive') -> str:
|
||||
return self.getNewMwebAddress()
|
||||
|
||||
def has_mweb_wallet(self) -> bool:
|
||||
return 'mweb' in self.rpc('listwallets')
|
||||
|
||||
def init_wallet(self, password=None):
|
||||
# If system is encrypted mweb wallet will be created at first unlock
|
||||
|
||||
self._log.info('init_wallet - {}'.format(self.ticker()))
|
||||
|
||||
self._log.info('Creating mweb wallet for {}.'.format(self.coin_name()))
|
||||
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors, load_on_startup
|
||||
self.rpc('createwallet', ['mweb', False, True, password, False, False, True])
|
||||
|
||||
if password is not None:
|
||||
# Max timeout value, ~3 years
|
||||
self.rpc_wallet('walletpassphrase', [password, 100000000])
|
||||
|
||||
if self.getWalletSeedID() == 'Not found':
|
||||
self._sc.initialiseWallet(self.coin_type())
|
||||
|
||||
# Workaround to trigger mweb_spk_man->LoadMWEBKeychain()
|
||||
self.rpc('unloadwallet', ['mweb'])
|
||||
self.rpc('loadwallet', ['mweb'])
|
||||
if password is not None:
|
||||
self.rpc_wallet('walletpassphrase', [password, 100000000])
|
||||
self.rpc_wallet('keypoolrefill')
|
||||
|
||||
def unlockWallet(self, password: str):
|
||||
if password == '':
|
||||
return
|
||||
self._log.info('unlockWallet - {}'.format(self.ticker()))
|
||||
|
||||
if not self.has_mweb_wallet():
|
||||
self.init_wallet(password)
|
||||
else:
|
||||
# Max timeout value, ~3 years
|
||||
self.rpc_wallet('walletpassphrase', [password, 100000000])
|
||||
|
||||
self._sc.checkWalletSeed(self.coin_type())
|
||||
|
||||
@@ -14,6 +14,7 @@ from coincurve.keys import (
|
||||
PrivateKey,
|
||||
)
|
||||
from .btc import BTCInterface, find_vout_for_address_from_txobj, findOutput
|
||||
from basicswap.rpc import make_rpc_func
|
||||
from basicswap.chainparams import Coins
|
||||
from basicswap.interface.contrib.nav_test_framework.mininode import (
|
||||
CTxIn,
|
||||
@@ -63,6 +64,14 @@ class NAVInterface(BTCInterface):
|
||||
def txoType():
|
||||
return CTxOut
|
||||
|
||||
def __init__(self, coin_settings, network, swap_client=None):
|
||||
super(NAVInterface, self).__init__(coin_settings, network, swap_client)
|
||||
# No multiwallet support
|
||||
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
|
||||
|
||||
def checkWallets(self) -> int:
|
||||
return 1
|
||||
|
||||
def use_p2shp2wsh(self) -> bool:
|
||||
# p2sh-p2wsh
|
||||
return True
|
||||
@@ -75,24 +84,24 @@ class NAVInterface(BTCInterface):
|
||||
pass
|
||||
|
||||
def getWalletSeedID(self):
|
||||
return self.rpc_callback('getwalletinfo')['hdmasterkeyid']
|
||||
return self.rpc('getwalletinfo')['hdmasterkeyid']
|
||||
|
||||
def withdrawCoin(self, value, addr_to: str, subfee: bool):
|
||||
strdzeel = ''
|
||||
params = [addr_to, value, '', '', strdzeel, subfee]
|
||||
return self.rpc_callback('sendtoaddress', params)
|
||||
return self.rpc('sendtoaddress', params)
|
||||
|
||||
def getSpendableBalance(self) -> int:
|
||||
return self.make_int(self.rpc_callback('getwalletinfo')['balance'])
|
||||
return self.make_int(self.rpc('getwalletinfo')['balance'])
|
||||
|
||||
def signTxWithWallet(self, tx: bytes) -> bytes:
|
||||
rv = self.rpc_callback('signrawtransaction', [tx.hex()])
|
||||
rv = self.rpc('signrawtransaction', [tx.hex()])
|
||||
|
||||
return bytes.fromhex(rv['hex'])
|
||||
|
||||
def checkExpectedSeed(self, key_hash: str):
|
||||
try:
|
||||
rv = self.rpc_callback('dumpmnemonic')
|
||||
rv = self.rpc('dumpmnemonic')
|
||||
entropy = Mnemonic('english').to_entropy(rv.split(' '))
|
||||
|
||||
entropy_hash = self.getAddressHashFromKey(entropy)[::-1].hex()
|
||||
@@ -155,7 +164,7 @@ class NAVInterface(BTCInterface):
|
||||
# TODO: Lock unspent and use same output/s to fund bid
|
||||
|
||||
unspents_by_addr = dict()
|
||||
unspents = self.rpc_callback('listunspent')
|
||||
unspents = self.rpc('listunspent')
|
||||
for u in unspents:
|
||||
if u['spendable'] is not True:
|
||||
continue
|
||||
@@ -211,13 +220,13 @@ class NAVInterface(BTCInterface):
|
||||
|
||||
if self.using_segwit(): # TODO: Use isSegwitAddress when scantxoutset can use combo
|
||||
# 'Address does not refer to key' for non p2pkh
|
||||
addr_info = self.rpc_callback('validateaddress', [addr, ])
|
||||
addr_info = self.rpc('validateaddress', [addr, ])
|
||||
if 'isscript' in addr_info and addr_info['isscript'] and 'hex' in addr_info:
|
||||
pkh = bytes.fromhex(addr_info['hex'])[2:]
|
||||
sign_for_addr = self.pkh_to_address(pkh)
|
||||
self._log.debug('sign_for_addr converted %s', sign_for_addr)
|
||||
|
||||
signature = self.rpc_callback('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex()])
|
||||
signature = self.rpc('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex()])
|
||||
|
||||
return (sign_for_addr, signature, prove_utxos)
|
||||
|
||||
@@ -237,13 +246,13 @@ class NAVInterface(BTCInterface):
|
||||
|
||||
sum_value: int = 0
|
||||
for outpoint in utxos:
|
||||
txout = self.rpc_callback('gettxout', [outpoint[0].hex(), outpoint[1]])
|
||||
txout = self.rpc('gettxout', [outpoint[0].hex(), outpoint[1]])
|
||||
sum_value += self.make_int(txout['value'])
|
||||
|
||||
return sum_value
|
||||
|
||||
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
|
||||
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
||||
txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
||||
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
||||
self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}')
|
||||
if sub_fee:
|
||||
@@ -254,17 +263,17 @@ class NAVInterface(BTCInterface):
|
||||
return self.fundTx(txn, fee_rate, lock_unspents).hex()
|
||||
|
||||
def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
|
||||
addr_info = self.rpc_callback('validateaddress', [address])
|
||||
addr_info = self.rpc('validateaddress', [address])
|
||||
if not or_watch_only:
|
||||
return addr_info['ismine']
|
||||
return addr_info['ismine'] or addr_info['iswatchonly']
|
||||
|
||||
def createRawSignedTransaction(self, addr_to, amount) -> str:
|
||||
txn_funded = self.createRawFundedTransaction(addr_to, amount)
|
||||
return self.rpc_callback('signrawtransaction', [txn_funded])['hex']
|
||||
return self.rpc('signrawtransaction', [txn_funded])['hex']
|
||||
|
||||
def getBlockchainInfo(self):
|
||||
rv = self.rpc_callback('getblockchaininfo')
|
||||
rv = self.rpc('getblockchaininfo')
|
||||
synced = round(rv['verificationprogress'], 3)
|
||||
if synced >= 0.997:
|
||||
rv['verificationprogress'] = 1.0
|
||||
@@ -278,7 +287,7 @@ class NAVInterface(BTCInterface):
|
||||
return pubkeyToAddress(self.chainparams_network()['script_address'], script)
|
||||
|
||||
def find_prevout_info(self, txn_hex: str, txn_script: bytes):
|
||||
txjs = self.rpc_callback('decoderawtransaction', [txn_hex])
|
||||
txjs = self.rpc('decoderawtransaction', [txn_hex])
|
||||
n = getVoutByScriptPubKey(txjs, self.getScriptDest(txn_script).hex())
|
||||
|
||||
return {
|
||||
@@ -290,9 +299,9 @@ class NAVInterface(BTCInterface):
|
||||
}
|
||||
|
||||
def getNewAddress(self, use_segwit: bool, label: str = 'swap_receive') -> str:
|
||||
address: str = self.rpc_callback('getnewaddress', [label,])
|
||||
address: str = self.rpc('getnewaddress', [label,])
|
||||
if use_segwit:
|
||||
return self.rpc_callback('addwitnessaddress', [address,])
|
||||
return self.rpc('addwitnessaddress', [address,])
|
||||
return address
|
||||
|
||||
def createRedeemTxn(self, prevout, output_addr: str, output_value: int, txn_script: bytes) -> str:
|
||||
@@ -385,15 +394,15 @@ class NAVInterface(BTCInterface):
|
||||
chain_blocks: int = self.getChainHeight()
|
||||
|
||||
current_height: int = chain_blocks
|
||||
block_hash = self.rpc_callback('getblockhash', [current_height])
|
||||
block_hash = self.rpc('getblockhash', [current_height])
|
||||
|
||||
script_hash: bytes = self.decodeAddress(addr_find)
|
||||
find_scriptPubKey = self.getDestForScriptHash(script_hash)
|
||||
|
||||
while current_height > height_start:
|
||||
block_hash = self.rpc_callback('getblockhash', [current_height])
|
||||
block_hash = self.rpc('getblockhash', [current_height])
|
||||
|
||||
block = self.rpc_callback('getblock', [block_hash, False])
|
||||
block = self.rpc('getblock', [block_hash, False])
|
||||
decoded_block = CBlock()
|
||||
decoded_block = FromHex(decoded_block, block)
|
||||
for tx in decoded_block.vtx:
|
||||
@@ -403,8 +412,8 @@ class NAVInterface(BTCInterface):
|
||||
txid = i2b(tx.sha256)
|
||||
self._log.info('Found output to addr: {} in tx {} in block {}'.format(addr_find, txid.hex(), block_hash))
|
||||
self._log.info('rescanblockchain hack invalidateblock {}'.format(block_hash))
|
||||
self.rpc_callback('invalidateblock', [block_hash])
|
||||
self.rpc_callback('reconsiderblock', [block_hash])
|
||||
self.rpc('invalidateblock', [block_hash])
|
||||
self.rpc('reconsiderblock', [block_hash])
|
||||
return
|
||||
current_height -= 1
|
||||
|
||||
@@ -419,7 +428,7 @@ class NAVInterface(BTCInterface):
|
||||
|
||||
return_txid = True if txid is None else False
|
||||
if txid is None:
|
||||
txns = self.rpc_callback('listunspent', [0, 9999999, [dest_address, ]])
|
||||
txns = self.rpc('listunspent', [0, 9999999, [dest_address, ]])
|
||||
|
||||
for tx in txns:
|
||||
if self.make_int(tx['amount']) == bid_amount:
|
||||
@@ -430,11 +439,11 @@ class NAVInterface(BTCInterface):
|
||||
return None
|
||||
|
||||
try:
|
||||
tx = self.rpc_callback('gettransaction', [txid.hex()])
|
||||
tx = self.rpc('gettransaction', [txid.hex()])
|
||||
|
||||
block_height = 0
|
||||
if 'blockhash' in tx:
|
||||
block_header = self.rpc_callback('getblockheader', [tx['blockhash']])
|
||||
block_header = self.rpc('getblockheader', [tx['blockhash']])
|
||||
block_height = block_header['height']
|
||||
|
||||
rv = {
|
||||
@@ -446,7 +455,7 @@ class NAVInterface(BTCInterface):
|
||||
return None
|
||||
|
||||
if find_index:
|
||||
tx_obj = self.rpc_callback('decoderawtransaction', [tx['hex']])
|
||||
tx_obj = self.rpc('decoderawtransaction', [tx['hex']])
|
||||
rv['index'] = find_vout_for_address_from_txobj(tx_obj, dest_address)
|
||||
|
||||
if return_txid:
|
||||
@@ -456,15 +465,15 @@ class NAVInterface(BTCInterface):
|
||||
|
||||
def getBlockWithTxns(self, block_hash):
|
||||
# TODO: Bypass decoderawtransaction and getblockheader
|
||||
block = self.rpc_callback('getblock', [block_hash, False])
|
||||
block_header = self.rpc_callback('getblockheader', [block_hash])
|
||||
block = self.rpc('getblock', [block_hash, False])
|
||||
block_header = self.rpc('getblockheader', [block_hash])
|
||||
decoded_block = CBlock()
|
||||
decoded_block = FromHex(decoded_block, block)
|
||||
|
||||
tx_rv = []
|
||||
for tx in decoded_block.vtx:
|
||||
tx_hex = tx.serialize_with_witness().hex()
|
||||
tx_dec = self.rpc_callback('decoderawtransaction', [tx_hex])
|
||||
tx_dec = self.rpc('decoderawtransaction', [tx_hex])
|
||||
if 'hex' not in tx_dec:
|
||||
tx_dec['hex'] = tx_hex
|
||||
|
||||
@@ -505,7 +514,7 @@ class NAVInterface(BTCInterface):
|
||||
|
||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int) -> bytes:
|
||||
self._log.info('spendBLockTx %s:\n', chain_b_lock_txid.hex())
|
||||
wtx = self.rpc_callback('gettransaction', [chain_b_lock_txid.hex(), ])
|
||||
wtx = self.rpc('gettransaction', [chain_b_lock_txid.hex(), ])
|
||||
lock_tx = self.loadTx(bytes.fromhex(wtx['hex']))
|
||||
|
||||
Kbs = self.getPubkey(kbs)
|
||||
@@ -550,7 +559,7 @@ class NAVInterface(BTCInterface):
|
||||
def findTxnByHash(self, txid_hex: str):
|
||||
# Only works for wallet txns
|
||||
try:
|
||||
rv = self.rpc_callback('gettransaction', [txid_hex])
|
||||
rv = self.rpc('gettransaction', [txid_hex])
|
||||
except Exception as ex:
|
||||
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
||||
return None
|
||||
@@ -573,10 +582,10 @@ class NAVInterface(BTCInterface):
|
||||
'lockUnspents': lock_unspents,
|
||||
'feeRate': feerate_str,
|
||||
}
|
||||
rv = self.rpc_callback('fundrawtransaction', [tx_hex, options])
|
||||
rv = self.rpc('fundrawtransaction', [tx_hex, options])
|
||||
|
||||
# Sign transaction then strip witness data to fill scriptsig
|
||||
rv = self.rpc_callback('signrawtransaction', [rv['hex']])
|
||||
rv = self.rpc('signrawtransaction', [rv['hex']])
|
||||
|
||||
tx_signed = self.loadTx(bytes.fromhex(rv['hex']))
|
||||
if len(tx_signed.vin) != len(tx_signed.wit.vtxinwit):
|
||||
|
||||
@@ -19,7 +19,7 @@ class NMCInterface(BTCInterface):
|
||||
|
||||
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index=False):
|
||||
self._log.debug('[rm] scantxoutset start') # scantxoutset is slow
|
||||
ro = self.rpc_callback('scantxoutset', ['start', ['addr({})'.format(dest_address)]]) # TODO: Use combo(address) where possible
|
||||
ro = self.rpc('scantxoutset', ['start', ['addr({})'.format(dest_address)]]) # TODO: Use combo(address) where possible
|
||||
self._log.debug('[rm] scantxoutset end')
|
||||
return_txid = True if txid is None else False
|
||||
for o in ro['unspents']:
|
||||
|
||||
@@ -83,14 +83,14 @@ class PARTInterface(BTCInterface):
|
||||
return True
|
||||
|
||||
def getNewAddress(self, use_segwit, label='swap_receive') -> str:
|
||||
return self.rpc_callback('getnewaddress', [label])
|
||||
return self.rpc_wallet('getnewaddress', [label])
|
||||
|
||||
def getNewStealthAddress(self, label='swap_stealth') -> str:
|
||||
return self.rpc_callback('getnewstealthaddress', [label])
|
||||
return self.rpc_wallet('getnewstealthaddress', [label])
|
||||
|
||||
def haveSpentIndex(self):
|
||||
version = self.getDaemonVersion()
|
||||
index_info = self.rpc_callback('getinsightinfo' if int(str(version)[:2]) > 19 else 'getindexinfo')
|
||||
index_info = self.rpc('getinsightinfo' if int(str(version)[:2]) > 19 else 'getindexinfo')
|
||||
return index_info['spentindex']
|
||||
|
||||
def initialiseWallet(self, key):
|
||||
@@ -98,14 +98,14 @@ class PARTInterface(BTCInterface):
|
||||
|
||||
def withdrawCoin(self, value, addr_to, subfee):
|
||||
params = [addr_to, value, '', '', subfee, '', True, self._conf_target]
|
||||
return self.rpc_callback('sendtoaddress', params)
|
||||
return self.rpc_wallet('sendtoaddress', params)
|
||||
|
||||
def sendTypeTo(self, type_from, type_to, value, addr_to, subfee):
|
||||
params = [type_from, type_to,
|
||||
[{'address': addr_to, 'amount': value, 'subfee': subfee}, ],
|
||||
'', '', self._anon_tx_ring_size, 1, False,
|
||||
{'conf_target': self._conf_target}]
|
||||
return self.rpc_callback('sendtypeto', params)
|
||||
return self.rpc_wallet('sendtypeto', params)
|
||||
|
||||
def getScriptForPubkeyHash(self, pkh: bytes) -> CScript:
|
||||
return CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG])
|
||||
@@ -122,7 +122,7 @@ class PARTInterface(BTCInterface):
|
||||
return length
|
||||
|
||||
def getWalletRestoreHeight(self) -> int:
|
||||
start_time = self.rpc_callback('getwalletinfo')['keypoololdest']
|
||||
start_time = self.rpc_wallet('getwalletinfo')['keypoololdest']
|
||||
|
||||
blockchaininfo = self.getBlockchainInfo()
|
||||
best_block = blockchaininfo['bestblockhash']
|
||||
@@ -132,8 +132,8 @@ class PARTInterface(BTCInterface):
|
||||
raise ValueError('{} chain isn\'t synced.'.format(self.coin_name()))
|
||||
|
||||
self._log.debug('Finding block at time: {}'.format(start_time))
|
||||
block_hash = self.rpc_callback('getblockhashafter', [start_time])
|
||||
block_header = self.rpc_callback('getblockheader', [block_hash])
|
||||
block_hash = self.rpc('getblockhashafter', [start_time])
|
||||
block_header = self.rpc('getblockheader', [block_hash])
|
||||
return block_header['height']
|
||||
|
||||
def getHTLCSpendTxVSize(self, redeem: bool = True) -> int:
|
||||
@@ -171,16 +171,16 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
if txo['type'] != 'blind':
|
||||
continue
|
||||
try:
|
||||
blinded_info = self.rpc_callback('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()])
|
||||
blinded_info = self.rpc('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()])
|
||||
output_n = txo['n']
|
||||
|
||||
self.rpc_callback('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()])
|
||||
self.rpc('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()])
|
||||
break
|
||||
except Exception as e:
|
||||
self._log.debug('Searching for locked output: {}'.format(str(e)))
|
||||
continue
|
||||
# Should not be possible for commitment not to match
|
||||
v = self.rpc_callback('verifycommitment', [txo['valueCommitment'], blinded_info['blind'], blinded_info['amount']])
|
||||
v = self.rpc('verifycommitment', [txo['valueCommitment'], blinded_info['blind'], blinded_info['amount']])
|
||||
ensure(v['result'] is True, 'verifycommitment failed')
|
||||
return output_n, blinded_info
|
||||
|
||||
@@ -195,7 +195,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
inputs = []
|
||||
outputs = [{'type': 'blind', 'amount': self.format_amount(value), 'address': p2wsh_addr, 'nonce': nonce.hex(), 'data': ephemeral_pubkey.hex()}]
|
||||
params = [inputs, outputs]
|
||||
rv = self.rpc_callback('createrawparttransaction', params)
|
||||
rv = self.rpc_wallet('createrawparttransaction', params)
|
||||
|
||||
tx_bytes = bytes.fromhex(rv['hex'])
|
||||
return tx_bytes
|
||||
@@ -207,11 +207,11 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
tx_hex = tx_bytes.hex()
|
||||
nonce = self.getScriptLockTxNonce(vkbv)
|
||||
|
||||
tx_obj = self.rpc_callback('decoderawtransaction', [tx_hex])
|
||||
tx_obj = self.rpc('decoderawtransaction', [tx_hex])
|
||||
|
||||
assert (len(tx_obj['vout']) == 1)
|
||||
txo = tx_obj['vout'][0]
|
||||
blinded_info = self.rpc_callback('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()])
|
||||
blinded_info = self.rpc('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()])
|
||||
|
||||
outputs_info = {0: {'value': blinded_info['amount'], 'blind': blinded_info['blind'], 'nonce': nonce.hex()}}
|
||||
|
||||
@@ -219,11 +219,11 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
'lockUnspents': True,
|
||||
'feeRate': feerate_str,
|
||||
}
|
||||
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', tx_hex, {}, outputs_info, options])
|
||||
rv = self.rpc('fundrawtransactionfrom', ['blind', tx_hex, {}, outputs_info, options])
|
||||
return bytes.fromhex(rv['hex'])
|
||||
|
||||
def createSCLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv):
|
||||
lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_bytes.hex()])
|
||||
lock_tx_obj = self.rpc('decoderawtransaction', [tx_lock_bytes.hex()])
|
||||
assert (self.getTxid(tx_lock_bytes).hex() == lock_tx_obj['txid'])
|
||||
# Nonce is derived from vkbv, ephemeral_key isn't used
|
||||
ephemeral_key = self.getNewSecretKey()
|
||||
@@ -244,7 +244,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
inputs = [{'txid': tx_lock_id, 'vout': spend_n, 'sequence': lock1_value, 'blindingfactor': input_blinded_info['blind']}]
|
||||
outputs = [{'type': 'blind', 'amount': locked_coin, 'address': p2wsh_addr, 'nonce': output_nonce.hex(), 'data': ephemeral_pubkey.hex()}]
|
||||
params = [inputs, outputs]
|
||||
rv = self.rpc_callback('createrawparttransaction', params)
|
||||
rv = self.rpc_wallet('createrawparttransaction', params)
|
||||
lock_refund_tx_hex = rv['hex']
|
||||
|
||||
# Set dummy witness data for fee estimation
|
||||
@@ -261,7 +261,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
'feeRate': self.format_amount(tx_fee_rate),
|
||||
'subtractFeeFromOutputs': [0, ]
|
||||
}
|
||||
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', lock_refund_tx_hex, inputs_info, outputs_info, options])
|
||||
rv = self.rpc_wallet('fundrawtransactionfrom', ['blind', lock_refund_tx_hex, inputs_info, outputs_info, options])
|
||||
lock_refund_tx_hex = rv['hex']
|
||||
|
||||
for vout, txo in rv['output_amounts'].items():
|
||||
@@ -275,7 +275,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
# The follower will sign the multisig path with a signature encumbered by the leader's coinB spend pubkey
|
||||
# If the leader publishes the decrypted signature the leader's coinB spend privatekey will be revealed to the follower
|
||||
|
||||
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_refund_bytes.hex()])
|
||||
lock_refund_tx_obj = self.rpc('decoderawtransaction', [tx_lock_refund_bytes.hex()])
|
||||
# Nonce is derived from vkbv
|
||||
nonce = self.getScriptLockRefundTxNonce(vkbv)
|
||||
|
||||
@@ -285,7 +285,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
|
||||
tx_lock_refund_id = lock_refund_tx_obj['txid']
|
||||
addr_out = self.pkh_to_address(pkh_refund_to)
|
||||
addr_info = self.rpc_callback('getaddressinfo', [addr_out])
|
||||
addr_info = self.rpc_wallet('getaddressinfo', [addr_out])
|
||||
output_pubkey_hex = addr_info['pubkey']
|
||||
|
||||
# Follower won't be able to decode output to check amount, shouldn't matter as fee is public and output is to leader, sum has to balance
|
||||
@@ -293,7 +293,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
inputs = [{'txid': tx_lock_refund_id, 'vout': spend_n, 'sequence': 0, 'blindingfactor': input_blinded_info['blind']}]
|
||||
outputs = [{'type': 'blind', 'amount': input_blinded_info['amount'], 'address': addr_out, 'pubkey': output_pubkey_hex}]
|
||||
params = [inputs, outputs]
|
||||
rv = self.rpc_callback('createrawparttransaction', params)
|
||||
rv = self.rpc_wallet('createrawparttransaction', params)
|
||||
lock_refund_spend_tx_hex = rv['hex']
|
||||
|
||||
# Set dummy witness data for fee estimation
|
||||
@@ -311,7 +311,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
'subtractFeeFromOutputs': [0, ]
|
||||
}
|
||||
|
||||
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', lock_refund_spend_tx_hex, inputs_info, outputs_info, options])
|
||||
rv = self.rpc_wallet('fundrawtransactionfrom', ['blind', lock_refund_spend_tx_hex, inputs_info, outputs_info, options])
|
||||
lock_refund_spend_tx_hex = rv['hex']
|
||||
|
||||
return bytes.fromhex(lock_refund_spend_tx_hex)
|
||||
@@ -321,7 +321,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
Kal, Kaf,
|
||||
feerate,
|
||||
check_lock_tx_inputs, vkbv):
|
||||
lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
|
||||
lock_tx_obj = self.rpc('decoderawtransaction', [tx_bytes.hex()])
|
||||
lock_txid_hex = lock_tx_obj['txid']
|
||||
self._log.info('Verifying lock tx: {}.'.format(lock_txid_hex))
|
||||
|
||||
@@ -363,7 +363,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
def verifySCLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out,
|
||||
prevout_id, prevout_n, prevout_seq, prevout_script,
|
||||
Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv):
|
||||
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
|
||||
lock_refund_tx_obj = self.rpc('decoderawtransaction', [tx_bytes.hex()])
|
||||
lock_refund_txid_hex = lock_refund_tx_obj['txid']
|
||||
self._log.info('Verifying lock refund tx: {}.'.format(lock_refund_txid_hex))
|
||||
|
||||
@@ -396,10 +396,10 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
ensure(C == Kaf, 'Bad script pubkey')
|
||||
|
||||
# Check rangeproofs and commitments sum
|
||||
lock_tx_obj = self.rpc_callback('decoderawtransaction', [lock_tx_bytes.hex()])
|
||||
lock_tx_obj = self.rpc('decoderawtransaction', [lock_tx_bytes.hex()])
|
||||
prevout = lock_tx_obj['vout'][prevout_n]
|
||||
prevtxns = [{'txid': prevout_id.hex(), 'vout': prevout_n, 'scriptPubKey': prevout['scriptPubKey']['hex'], 'amount_commitment': prevout['valueCommitment']}]
|
||||
rv = self.rpc_callback('verifyrawtransaction', [tx_bytes.hex(), prevtxns])
|
||||
rv = self.rpc('verifyrawtransaction', [tx_bytes.hex(), prevtxns])
|
||||
ensure(rv['outputs_valid'] is True, 'Invalid outputs')
|
||||
ensure(rv['inputs_valid'] is True, 'Invalid inputs')
|
||||
|
||||
@@ -422,7 +422,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
lock_refund_tx_id, prevout_script,
|
||||
Kal,
|
||||
prevout_n, prevout_value, feerate, vkbv):
|
||||
lock_refund_spend_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
|
||||
lock_refund_spend_tx_obj = self.rpc('decoderawtransaction', [tx_bytes.hex()])
|
||||
lock_refund_spend_txid_hex = lock_refund_spend_tx_obj['txid']
|
||||
self._log.info('Verifying lock refund spend tx: {}.'.format(lock_refund_spend_txid_hex))
|
||||
|
||||
@@ -441,10 +441,10 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
# Follower is not concerned with them as they pay to leader
|
||||
|
||||
# Check rangeproofs and commitments sum
|
||||
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [lock_refund_tx_bytes.hex()])
|
||||
lock_refund_tx_obj = self.rpc('decoderawtransaction', [lock_refund_tx_bytes.hex()])
|
||||
prevout = lock_refund_tx_obj['vout'][prevout_n]
|
||||
prevtxns = [{'txid': lock_refund_tx_id.hex(), 'vout': prevout_n, 'scriptPubKey': prevout['scriptPubKey']['hex'], 'amount_commitment': prevout['valueCommitment']}]
|
||||
rv = self.rpc_callback('verifyrawtransaction', [tx_bytes.hex(), prevtxns])
|
||||
rv = self.rpc('verifyrawtransaction', [tx_bytes.hex(), prevtxns])
|
||||
ensure(rv['outputs_valid'] is True, 'Invalid outputs')
|
||||
ensure(rv['inputs_valid'] is True, 'Invalid inputs')
|
||||
|
||||
@@ -459,28 +459,28 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
return True
|
||||
|
||||
def getLockTxSwapOutputValue(self, bid, xmr_swap):
|
||||
lock_tx_obj = self.rpc_callback('decoderawtransaction', [xmr_swap.a_lock_tx.hex()])
|
||||
lock_tx_obj = self.rpc('decoderawtransaction', [xmr_swap.a_lock_tx.hex()])
|
||||
nonce = self.getScriptLockTxNonce(xmr_swap.vkbv)
|
||||
output_n, _ = self.findOutputByNonce(lock_tx_obj, nonce)
|
||||
ensure(output_n is not None, 'Output not found in tx')
|
||||
return bytes.fromhex(lock_tx_obj['vout'][output_n]['valueCommitment'])
|
||||
|
||||
def getLockRefundTxSwapOutputValue(self, bid, xmr_swap):
|
||||
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [xmr_swap.a_lock_refund_tx.hex()])
|
||||
lock_refund_tx_obj = self.rpc('decoderawtransaction', [xmr_swap.a_lock_refund_tx.hex()])
|
||||
nonce = self.getScriptLockRefundTxNonce(xmr_swap.vkbv)
|
||||
output_n, _ = self.findOutputByNonce(lock_refund_tx_obj, nonce)
|
||||
ensure(output_n is not None, 'Output not found in tx')
|
||||
return bytes.fromhex(lock_refund_tx_obj['vout'][output_n]['valueCommitment'])
|
||||
|
||||
def getLockRefundTxSwapOutput(self, xmr_swap):
|
||||
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [xmr_swap.a_lock_refund_tx.hex()])
|
||||
lock_refund_tx_obj = self.rpc('decoderawtransaction', [xmr_swap.a_lock_refund_tx.hex()])
|
||||
nonce = self.getScriptLockRefundTxNonce(xmr_swap.vkbv)
|
||||
output_n, _ = self.findOutputByNonce(lock_refund_tx_obj, nonce)
|
||||
ensure(output_n is not None, 'Output not found in tx')
|
||||
return output_n
|
||||
|
||||
def createSCLockSpendTx(self, tx_lock_bytes: bytes, script_lock: bytes, pk_dest: bytes, tx_fee_rate: int, vkbv: bytes, fee_info={}) -> bytes:
|
||||
lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_bytes.hex()])
|
||||
lock_tx_obj = self.rpc('decoderawtransaction', [tx_lock_bytes.hex()])
|
||||
lock_txid_hex = lock_tx_obj['txid']
|
||||
|
||||
ensure(lock_tx_obj['version'] == self.txVersion(), 'Bad version')
|
||||
@@ -496,7 +496,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
inputs = [{'txid': lock_txid_hex, 'vout': spend_n, 'sequence': 0, 'blindingfactor': blinded_info['blind']}]
|
||||
outputs = [{'type': 'blind', 'amount': blinded_info['amount'], 'address': addr_out, 'pubkey': pk_dest.hex()}]
|
||||
params = [inputs, outputs]
|
||||
rv = self.rpc_callback('createrawparttransaction', params)
|
||||
rv = self.rpc_wallet('createrawparttransaction', params)
|
||||
lock_spend_tx_hex = rv['hex']
|
||||
|
||||
# Set dummy witness data for fee estimation
|
||||
@@ -513,9 +513,9 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
'subtractFeeFromOutputs': [0, ]
|
||||
}
|
||||
|
||||
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', lock_spend_tx_hex, inputs_info, outputs_info, options])
|
||||
rv = self.rpc_wallet('fundrawtransactionfrom', ['blind', lock_spend_tx_hex, inputs_info, outputs_info, options])
|
||||
lock_spend_tx_hex = rv['hex']
|
||||
lock_spend_tx_obj = self.rpc_callback('decoderawtransaction', [lock_spend_tx_hex])
|
||||
lock_spend_tx_obj = self.rpc('decoderawtransaction', [lock_spend_tx_hex])
|
||||
pay_fee = make_int(lock_spend_tx_obj['vout'][0]['ct_fee'])
|
||||
|
||||
# lock_spend_tx_hex does not include the dummy witness stack
|
||||
@@ -535,7 +535,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
def verifySCLockSpendTx(self, tx_bytes,
|
||||
lock_tx_bytes, lock_tx_script,
|
||||
a_pk_f, feerate, vkbv):
|
||||
lock_spend_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()])
|
||||
lock_spend_tx_obj = self.rpc('decoderawtransaction', [tx_bytes.hex()])
|
||||
lock_spend_txid_hex = lock_spend_tx_obj['txid']
|
||||
self._log.info('Verifying lock spend tx: {}.'.format(lock_spend_txid_hex))
|
||||
|
||||
@@ -543,7 +543,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
ensure(lock_spend_tx_obj['locktime'] == 0, 'Bad nLockTime')
|
||||
ensure(len(lock_spend_tx_obj['vin']) == 1, 'tx doesn\'t have one input')
|
||||
|
||||
lock_tx_obj = self.rpc_callback('decoderawtransaction', [lock_tx_bytes.hex()])
|
||||
lock_tx_obj = self.rpc('decoderawtransaction', [lock_tx_bytes.hex()])
|
||||
lock_txid_hex = lock_tx_obj['txid']
|
||||
|
||||
# Find the output of the lock tx to verify
|
||||
@@ -559,7 +559,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
ensure(len(lock_spend_tx_obj['vout']) == 3, 'tx doesn\'t have three outputs')
|
||||
|
||||
addr_out = self.pubkey_to_address(a_pk_f)
|
||||
privkey = self.rpc_callback('dumpprivkey', [addr_out])
|
||||
privkey = self.rpc_wallet('dumpprivkey', [addr_out])
|
||||
|
||||
# Find output:
|
||||
output_blinded_info = None
|
||||
@@ -568,7 +568,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
if txo['type'] != 'blind':
|
||||
continue
|
||||
try:
|
||||
output_blinded_info = self.rpc_callback('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], privkey, txo['data_hex']])
|
||||
output_blinded_info = self.rpc('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], privkey, txo['data_hex']])
|
||||
output_n = txo['n']
|
||||
break
|
||||
except Exception as e:
|
||||
@@ -577,13 +577,13 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
ensure(output_n is not None, 'Output not found in tx')
|
||||
|
||||
# Commitment
|
||||
v = self.rpc_callback('verifycommitment', [lock_spend_tx_obj['vout'][output_n]['valueCommitment'], output_blinded_info['blind'], output_blinded_info['amount']])
|
||||
v = self.rpc('verifycommitment', [lock_spend_tx_obj['vout'][output_n]['valueCommitment'], output_blinded_info['blind'], output_blinded_info['amount']])
|
||||
ensure(v['result'] is True, 'verifycommitment failed')
|
||||
|
||||
# Check rangeproofs and commitments sum
|
||||
prevout = lock_tx_obj['vout'][spend_n]
|
||||
prevtxns = [{'txid': lock_txid_hex, 'vout': spend_n, 'scriptPubKey': prevout['scriptPubKey']['hex'], 'amount_commitment': prevout['valueCommitment']}]
|
||||
rv = self.rpc_callback('verifyrawtransaction', [tx_bytes.hex(), prevtxns])
|
||||
rv = self.rpc('verifyrawtransaction', [tx_bytes.hex(), prevtxns])
|
||||
ensure(rv['outputs_valid'] is True, 'Invalid outputs')
|
||||
ensure(rv['inputs_valid'] is True, 'Invalid inputs')
|
||||
|
||||
@@ -607,7 +607,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv):
|
||||
# lock refund swipe tx
|
||||
# Sends the coinA locked coin to the follower
|
||||
lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_refund_bytes.hex()])
|
||||
lock_refund_tx_obj = self.rpc('decoderawtransaction', [tx_lock_refund_bytes.hex()])
|
||||
nonce = self.getScriptLockRefundTxNonce(vkbv)
|
||||
|
||||
# Find the output of the lock refund tx to spend
|
||||
@@ -616,7 +616,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
|
||||
tx_lock_refund_id = lock_refund_tx_obj['txid']
|
||||
addr_out = self.pkh_to_address(pkh_dest)
|
||||
addr_info = self.rpc_callback('getaddressinfo', [addr_out])
|
||||
addr_info = self.rpc_wallet('getaddressinfo', [addr_out])
|
||||
output_pubkey_hex = addr_info['pubkey']
|
||||
|
||||
A, B, lock2_value, C = self.extractScriptLockRefundScriptValues(script_lock_refund)
|
||||
@@ -626,7 +626,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
inputs = [{'txid': tx_lock_refund_id, 'vout': spend_n, 'sequence': lock2_value, 'blindingfactor': input_blinded_info['blind']}]
|
||||
outputs = [{'type': 'blind', 'amount': input_blinded_info['amount'], 'address': addr_out, 'pubkey': output_pubkey_hex}]
|
||||
params = [inputs, outputs]
|
||||
rv = self.rpc_callback('createrawparttransaction', params)
|
||||
rv = self.rpc_wallet('createrawparttransaction', params)
|
||||
|
||||
lock_refund_swipe_tx_hex = rv['hex']
|
||||
|
||||
@@ -645,13 +645,13 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
'subtractFeeFromOutputs': [0, ]
|
||||
}
|
||||
|
||||
rv = self.rpc_callback('fundrawtransactionfrom', ['blind', lock_refund_swipe_tx_hex, inputs_info, outputs_info, options])
|
||||
rv = self.rpc_wallet('fundrawtransactionfrom', ['blind', lock_refund_swipe_tx_hex, inputs_info, outputs_info, options])
|
||||
lock_refund_swipe_tx_hex = rv['hex']
|
||||
|
||||
return bytes.fromhex(lock_refund_swipe_tx_hex)
|
||||
|
||||
def getSpendableBalance(self) -> int:
|
||||
return self.make_int(self.rpc_callback('getbalances')['mine']['blind_trusted'])
|
||||
return self.make_int(self.rpc_wallet('getbalances')['mine']['blind_trusted'])
|
||||
|
||||
def publishBLockTx(self, vkbv: bytes, Kbs: bytes, output_amount: int, feerate: int, delay_for: int = 10, unlock_time: int = 0) -> bytes:
|
||||
Kbv = self.getPubkey(vkbv)
|
||||
@@ -664,7 +664,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
'', '', self._anon_tx_ring_size, 1, False,
|
||||
{'conf_target': self._conf_target, 'blind_watchonly_visible': True}]
|
||||
|
||||
txid = self.rpc_callback('sendtypeto', params)
|
||||
txid = self.rpc_wallet('sendtypeto', params)
|
||||
return bytes.fromhex(txid)
|
||||
|
||||
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height: int, bid_sender: bool):
|
||||
@@ -675,17 +675,17 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
if bid_sender:
|
||||
cb_swap_value *= -1
|
||||
else:
|
||||
addr_info = self.rpc_callback('getaddressinfo', [sx_addr])
|
||||
addr_info = self.rpc_wallet('getaddressinfo', [sx_addr])
|
||||
if not addr_info['iswatchonly']:
|
||||
wif_prefix = self.chainparams_network()['key_prefix']
|
||||
wif_scan_key = toWIF(wif_prefix, kbv)
|
||||
self.rpc_callback('importstealthaddress', [wif_scan_key, Kbs.hex()])
|
||||
self.rpc_wallet('importstealthaddress', [wif_scan_key, Kbs.hex()])
|
||||
self._log.info('Imported watch-only sx_addr: {}'.format(sx_addr))
|
||||
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
|
||||
self.rpc_callback('rescanblockchain', [restore_height])
|
||||
self.rpc_wallet('rescanblockchain', [restore_height])
|
||||
|
||||
params = [{'include_watchonly': True, 'search': sx_addr}]
|
||||
txns = self.rpc_callback('filtertransactions', params)
|
||||
txns = self.rpc_wallet('filtertransactions', params)
|
||||
|
||||
if len(txns) == 1:
|
||||
tx = txns[0]
|
||||
@@ -695,7 +695,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
if make_int(tx['outputs'][0]['amount']) == cb_swap_value:
|
||||
height = 0
|
||||
if tx['confirmations'] > 0:
|
||||
chain_height = self.rpc_callback('getblockcount')
|
||||
chain_height = self.rpc('getblockcount')
|
||||
height = chain_height - (tx['confirmations'] - 1)
|
||||
return {'txid': tx['txid'], 'amount': cb_swap_value, 'height': height}
|
||||
else:
|
||||
@@ -707,20 +707,20 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
Kbv = self.getPubkey(kbv)
|
||||
Kbs = self.getPubkey(kbs)
|
||||
sx_addr = self.formatStealthAddress(Kbv, Kbs)
|
||||
addr_info = self.rpc_callback('getaddressinfo', [sx_addr])
|
||||
addr_info = self.rpc_wallet('getaddressinfo', [sx_addr])
|
||||
if not addr_info['ismine']:
|
||||
wif_prefix = self.chainparams_network()['key_prefix']
|
||||
wif_scan_key = toWIF(wif_prefix, kbv)
|
||||
wif_spend_key = toWIF(wif_prefix, kbs)
|
||||
self.rpc_callback('importstealthaddress', [wif_scan_key, wif_spend_key])
|
||||
self.rpc_wallet('importstealthaddress', [wif_scan_key, wif_spend_key])
|
||||
self._log.info('Imported spend key for sx_addr: {}'.format(sx_addr))
|
||||
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
|
||||
self.rpc_callback('rescanblockchain', [restore_height])
|
||||
self.rpc_wallet('rescanblockchain', [restore_height])
|
||||
|
||||
# TODO: Remove workaround
|
||||
# utxos = self.rpc_callback('listunspentblind', [1, 9999999, [sx_addr]])
|
||||
# utxos = self.rpc_wallet('listunspentblind', [1, 9999999, [sx_addr]])
|
||||
utxos = []
|
||||
all_utxos = self.rpc_callback('listunspentblind', [1, 9999999])
|
||||
all_utxos = self.rpc_wallet('listunspentblind', [1, 9999999])
|
||||
for utxo in all_utxos:
|
||||
if utxo.get('stealth_address', '_') == sx_addr:
|
||||
utxos.append(utxo)
|
||||
@@ -741,14 +741,14 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
[{'address': address_to, 'amount': self.format_amount(cb_swap_value), 'subfee': True}, ],
|
||||
'', '', self._anon_tx_ring_size, 1, False,
|
||||
{'conf_target': self._conf_target, 'inputs': inputs, 'show_fee': True}]
|
||||
rv = self.rpc_callback('sendtypeto', params)
|
||||
rv = self.rpc_wallet('sendtypeto', params)
|
||||
return bytes.fromhex(rv['txid'])
|
||||
|
||||
def findTxnByHash(self, txid_hex):
|
||||
# txindex is enabled for Particl
|
||||
|
||||
try:
|
||||
rv = self.rpc_callback('getrawtransaction', [txid_hex, True])
|
||||
rv = self.rpc('getrawtransaction', [txid_hex, True])
|
||||
except Exception as ex:
|
||||
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
||||
return None
|
||||
@@ -759,7 +759,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
return None
|
||||
|
||||
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
|
||||
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
||||
txn = self.rpc_wallet('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
||||
|
||||
options = {
|
||||
'lockUnspents': lock_unspents,
|
||||
@@ -767,7 +767,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
}
|
||||
if sub_fee:
|
||||
options['subtractFeeFromOutputs'] = [0,]
|
||||
return self.rpc_callback('fundrawtransactionfrom', ['blind', txn, options])['hex']
|
||||
return self.rpc_wallet('fundrawtransactionfrom', ['blind', txn, options])['hex']
|
||||
|
||||
|
||||
class PARTInterfaceAnon(PARTInterface):
|
||||
@@ -801,7 +801,7 @@ class PARTInterfaceAnon(PARTInterface):
|
||||
'', '', self._anon_tx_ring_size, 1, False,
|
||||
{'conf_target': self._conf_target, 'blind_watchonly_visible': True}]
|
||||
|
||||
txid = self.rpc_callback('sendtypeto', params)
|
||||
txid = self.rpc_wallet('sendtypeto', params)
|
||||
return bytes.fromhex(txid)
|
||||
|
||||
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender):
|
||||
@@ -813,17 +813,17 @@ class PARTInterfaceAnon(PARTInterface):
|
||||
if bid_sender:
|
||||
cb_swap_value *= -1
|
||||
else:
|
||||
addr_info = self.rpc_callback('getaddressinfo', [sx_addr])
|
||||
addr_info = self.rpc_wallet('getaddressinfo', [sx_addr])
|
||||
if not addr_info['iswatchonly']:
|
||||
wif_prefix = self.chainparams_network()['key_prefix']
|
||||
wif_scan_key = toWIF(wif_prefix, kbv)
|
||||
self.rpc_callback('importstealthaddress', [wif_scan_key, Kbs.hex()])
|
||||
self.rpc_wallet('importstealthaddress', [wif_scan_key, Kbs.hex()])
|
||||
self._log.info('Imported watch-only sx_addr: {}'.format(sx_addr))
|
||||
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
|
||||
self.rpc_callback('rescanblockchain', [restore_height])
|
||||
self.rpc_wallet('rescanblockchain', [restore_height])
|
||||
|
||||
params = [{'include_watchonly': True, 'search': sx_addr}]
|
||||
txns = self.rpc_callback('filtertransactions', params)
|
||||
txns = self.rpc_wallet('filtertransactions', params)
|
||||
|
||||
if len(txns) == 1:
|
||||
tx = txns[0]
|
||||
@@ -833,7 +833,7 @@ class PARTInterfaceAnon(PARTInterface):
|
||||
if make_int(tx['outputs'][0]['amount']) == cb_swap_value:
|
||||
height = 0
|
||||
if tx['confirmations'] > 0:
|
||||
chain_height = self.rpc_callback('getblockcount')
|
||||
chain_height = self.rpc('getblockcount')
|
||||
height = chain_height - (tx['confirmations'] - 1)
|
||||
return {'txid': tx['txid'], 'amount': cb_swap_value, 'height': height}
|
||||
else:
|
||||
@@ -845,17 +845,17 @@ class PARTInterfaceAnon(PARTInterface):
|
||||
Kbv = self.getPubkey(kbv)
|
||||
Kbs = self.getPubkey(kbs)
|
||||
sx_addr = self.formatStealthAddress(Kbv, Kbs)
|
||||
addr_info = self.rpc_callback('getaddressinfo', [sx_addr])
|
||||
addr_info = self.rpc_wallet('getaddressinfo', [sx_addr])
|
||||
if not addr_info['ismine']:
|
||||
wif_prefix = self.chainparams_network()['key_prefix']
|
||||
wif_scan_key = toWIF(wif_prefix, kbv)
|
||||
wif_spend_key = toWIF(wif_prefix, kbs)
|
||||
self.rpc_callback('importstealthaddress', [wif_scan_key, wif_spend_key])
|
||||
self.rpc_wallet('importstealthaddress', [wif_scan_key, wif_spend_key])
|
||||
self._log.info('Imported spend key for sx_addr: {}'.format(sx_addr))
|
||||
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
|
||||
self.rpc_callback('rescanblockchain', [restore_height])
|
||||
self.rpc_wallet('rescanblockchain', [restore_height])
|
||||
|
||||
autxos = self.rpc_callback('listunspentanon', [1, 9999999, [sx_addr]])
|
||||
autxos = self.rpc_wallet('listunspentanon', [1, 9999999, [sx_addr]])
|
||||
|
||||
if len(autxos) < 1:
|
||||
raise TemporaryError('No spendable outputs')
|
||||
@@ -874,14 +874,14 @@ class PARTInterfaceAnon(PARTInterface):
|
||||
[{'address': address_to, 'amount': self.format_amount(cb_swap_value), 'subfee': True}, ],
|
||||
'', '', self._anon_tx_ring_size, 1, False,
|
||||
{'conf_target': self._conf_target, 'inputs': inputs, 'show_fee': True}]
|
||||
rv = self.rpc_callback('sendtypeto', params)
|
||||
rv = self.rpc_wallet('sendtypeto', params)
|
||||
return bytes.fromhex(rv['txid'])
|
||||
|
||||
def findTxnByHash(self, txid_hex: str):
|
||||
# txindex is enabled for Particl
|
||||
|
||||
try:
|
||||
rv = self.rpc_callback('getrawtransaction', [txid_hex, True])
|
||||
rv = self.rpc('getrawtransaction', [txid_hex, True])
|
||||
except Exception as ex:
|
||||
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
||||
return None
|
||||
@@ -892,4 +892,4 @@ class PARTInterfaceAnon(PARTInterface):
|
||||
return None
|
||||
|
||||
def getSpendableBalance(self) -> int:
|
||||
return self.make_int(self.rpc_callback('getbalances')['mine']['anon_trusted'])
|
||||
return self.make_int(self.rpc_wallet('getbalances')['mine']['anon_trusted'])
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
from io import BytesIO
|
||||
|
||||
from .btc import BTCInterface
|
||||
from basicswap.rpc import make_rpc_func
|
||||
from basicswap.chainparams import Coins
|
||||
from basicswap.util.address import decodeAddress
|
||||
from .contrib.pivx_test_framework.messages import (
|
||||
@@ -29,12 +30,20 @@ class PIVXInterface(BTCInterface):
|
||||
def coin_type():
|
||||
return Coins.PIVX
|
||||
|
||||
def __init__(self, coin_settings, network, swap_client=None):
|
||||
super(PIVXInterface, self).__init__(coin_settings, network, swap_client)
|
||||
# No multiwallet support
|
||||
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
|
||||
|
||||
def checkWallets(self) -> int:
|
||||
return 1
|
||||
|
||||
def signTxWithWallet(self, tx):
|
||||
rv = self.rpc_callback('signrawtransaction', [tx.hex()])
|
||||
rv = self.rpc('signrawtransaction', [tx.hex()])
|
||||
return bytes.fromhex(rv['hex'])
|
||||
|
||||
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
|
||||
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
||||
txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
|
||||
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
||||
self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}')
|
||||
options = {
|
||||
@@ -43,25 +52,25 @@ class PIVXInterface(BTCInterface):
|
||||
}
|
||||
if sub_fee:
|
||||
options['subtractFeeFromOutputs'] = [0,]
|
||||
return self.rpc_callback('fundrawtransaction', [txn, options])['hex']
|
||||
return self.rpc('fundrawtransaction', [txn, options])['hex']
|
||||
|
||||
def createRawSignedTransaction(self, addr_to, amount) -> str:
|
||||
txn_funded = self.createRawFundedTransaction(addr_to, amount)
|
||||
return self.rpc_callback('signrawtransaction', [txn_funded])['hex']
|
||||
return self.rpc('signrawtransaction', [txn_funded])['hex']
|
||||
|
||||
def decodeAddress(self, address):
|
||||
return decodeAddress(address)[1:]
|
||||
|
||||
def getBlockWithTxns(self, block_hash):
|
||||
# TODO: Bypass decoderawtransaction and getblockheader
|
||||
block = self.rpc_callback('getblock', [block_hash, False])
|
||||
block_header = self.rpc_callback('getblockheader', [block_hash])
|
||||
block = self.rpc('getblock', [block_hash, False])
|
||||
block_header = self.rpc('getblockheader', [block_hash])
|
||||
decoded_block = CBlock()
|
||||
decoded_block = FromHex(decoded_block, block)
|
||||
|
||||
tx_rv = []
|
||||
for tx in decoded_block.vtx:
|
||||
tx_dec = self.rpc_callback('decoderawtransaction', [ToHex(tx)])
|
||||
tx_dec = self.rpc('decoderawtransaction', [ToHex(tx)])
|
||||
tx_rv.append(tx_dec)
|
||||
|
||||
block_rv = {
|
||||
@@ -77,10 +86,10 @@ class PIVXInterface(BTCInterface):
|
||||
|
||||
def withdrawCoin(self, value, addr_to, subfee):
|
||||
params = [addr_to, value, '', '', subfee]
|
||||
return self.rpc_callback('sendtoaddress', params)
|
||||
return self.rpc('sendtoaddress', params)
|
||||
|
||||
def getSpendableBalance(self) -> int:
|
||||
return self.make_int(self.rpc_callback('getwalletinfo')['balance'])
|
||||
return self.make_int(self.rpc('getwalletinfo')['balance'])
|
||||
|
||||
def loadTx(self, tx_bytes):
|
||||
# Load tx from bytes to internal representation
|
||||
@@ -101,13 +110,13 @@ class PIVXInterface(BTCInterface):
|
||||
|
||||
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
|
||||
key_wif = self.encodeKey(key)
|
||||
rv = self.rpc_callback('signrawtransaction', [tx.hex(), [], [key_wif, ]])
|
||||
rv = self.rpc('signrawtransaction', [tx.hex(), [], [key_wif, ]])
|
||||
return bytes.fromhex(rv['hex'])
|
||||
|
||||
def findTxnByHash(self, txid_hex: str):
|
||||
# Only works for wallet txns
|
||||
try:
|
||||
rv = self.rpc_callback('gettransaction', [txid_hex])
|
||||
rv = self.rpc('gettransaction', [txid_hex])
|
||||
except Exception as ex:
|
||||
self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex))
|
||||
return None
|
||||
|
||||
@@ -83,9 +83,9 @@ class XMRInterface(CoinInterface):
|
||||
daemon_login = None
|
||||
if coin_settings.get('rpcuser', '') != '':
|
||||
daemon_login = (coin_settings.get('rpcuser', ''), coin_settings.get('rpcpassword', ''))
|
||||
self.rpc_cb = make_xmr_rpc_func(coin_settings['rpcport'], daemon_login, host=coin_settings.get('rpchost', '127.0.0.1'))
|
||||
self.rpc_cb2 = make_xmr_rpc2_func(coin_settings['rpcport'], daemon_login, host=coin_settings.get('rpchost', '127.0.0.1')) # non-json endpoint
|
||||
self.rpc_wallet_cb = make_xmr_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'], host=coin_settings.get('walletrpchost', '127.0.0.1'))
|
||||
self.rpc = make_xmr_rpc_func(coin_settings['rpcport'], daemon_login, host=coin_settings.get('rpchost', '127.0.0.1'))
|
||||
self.rpc2 = make_xmr_rpc2_func(coin_settings['rpcport'], daemon_login, host=coin_settings.get('rpchost', '127.0.0.1')) # non-json endpoint
|
||||
self.rpc_wallet = make_xmr_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'], host=coin_settings.get('walletrpchost', '127.0.0.1'))
|
||||
|
||||
self.blocks_confirmed = coin_settings['blocks_confirmed']
|
||||
self._restore_height = coin_settings.get('restore_height', 0)
|
||||
@@ -95,6 +95,9 @@ class XMRInterface(CoinInterface):
|
||||
self._wallet_password = None
|
||||
self._have_checked_seed = False
|
||||
|
||||
def checkWallets(self) -> int:
|
||||
return 1
|
||||
|
||||
def setFeePriority(self, new_priority):
|
||||
ensure(new_priority >= 0 and new_priority < 4, 'Invalid fee_priority value')
|
||||
self._fee_priority = new_priority
|
||||
@@ -105,7 +108,7 @@ class XMRInterface(CoinInterface):
|
||||
def createWallet(self, params):
|
||||
if self._wallet_password is not None:
|
||||
params['password'] = self._wallet_password
|
||||
rv = self.rpc_wallet_cb('generate_from_keys', params)
|
||||
rv = self.rpc_wallet('generate_from_keys', params)
|
||||
self._log.info('generate_from_keys %s', dumpj(rv))
|
||||
|
||||
def openWallet(self, filename):
|
||||
@@ -115,10 +118,10 @@ class XMRInterface(CoinInterface):
|
||||
|
||||
try:
|
||||
# Can't reopen the same wallet in windows, !is_keys_file_locked()
|
||||
self.rpc_wallet_cb('close_wallet')
|
||||
self.rpc_wallet('close_wallet')
|
||||
except Exception:
|
||||
pass
|
||||
self.rpc_wallet_cb('open_wallet', params)
|
||||
self.rpc_wallet('open_wallet', params)
|
||||
|
||||
def initialiseWallet(self, key_view, key_spend, restore_height=None):
|
||||
with self._mx_wallet:
|
||||
@@ -147,14 +150,14 @@ class XMRInterface(CoinInterface):
|
||||
with self._mx_wallet:
|
||||
self.openWallet(self._wallet_filename)
|
||||
|
||||
def testDaemonRPC(self, with_wallet=True):
|
||||
self.rpc_wallet_cb('get_languages')
|
||||
def testDaemonRPC(self, with_wallet=True) -> None:
|
||||
self.rpc_wallet('get_languages')
|
||||
|
||||
def getDaemonVersion(self):
|
||||
return self.rpc_wallet_cb('get_version')['version']
|
||||
return self.rpc_wallet('get_version')['version']
|
||||
|
||||
def getBlockchainInfo(self):
|
||||
get_height = self.rpc_cb2('get_height', timeout=30)
|
||||
get_height = self.rpc2('get_height', timeout=30)
|
||||
rv = {
|
||||
'blocks': get_height['height'],
|
||||
'verificationprogress': 0.0,
|
||||
@@ -165,7 +168,7 @@ class XMRInterface(CoinInterface):
|
||||
# get_block_count returns "Internal error" if bootstrap-daemon is active
|
||||
if get_height['untrusted'] is True:
|
||||
rv['bootstrapping'] = True
|
||||
get_info = self.rpc_cb2('get_info', timeout=30)
|
||||
get_info = self.rpc2('get_info', timeout=30)
|
||||
if 'height_without_bootstrap' in get_info:
|
||||
rv['blocks'] = get_info['height_without_bootstrap']
|
||||
|
||||
@@ -173,7 +176,7 @@ class XMRInterface(CoinInterface):
|
||||
if rv['known_block_count'] > rv['blocks']:
|
||||
rv['verificationprogress'] = rv['blocks'] / rv['known_block_count']
|
||||
else:
|
||||
rv['known_block_count'] = self.rpc_cb('get_block_count', timeout=30)['count']
|
||||
rv['known_block_count'] = self.rpc('get_block_count', timeout=30)['count']
|
||||
rv['verificationprogress'] = rv['blocks'] / rv['known_block_count']
|
||||
except Exception as e:
|
||||
self._log.warning('XMR get_block_count failed with: %s', str(e))
|
||||
@@ -182,7 +185,7 @@ class XMRInterface(CoinInterface):
|
||||
return rv
|
||||
|
||||
def getChainHeight(self):
|
||||
return self.rpc_cb2('get_height', timeout=30)['height']
|
||||
return self.rpc2('get_height', timeout=30)['height']
|
||||
|
||||
def getWalletInfo(self):
|
||||
with self._mx_wallet:
|
||||
@@ -195,8 +198,8 @@ class XMRInterface(CoinInterface):
|
||||
raise e
|
||||
|
||||
rv = {}
|
||||
self.rpc_wallet_cb('refresh')
|
||||
balance_info = self.rpc_wallet_cb('get_balance')
|
||||
self.rpc_wallet('refresh')
|
||||
balance_info = self.rpc_wallet('get_balance')
|
||||
rv['balance'] = self.format_amount(balance_info['unlocked_balance'])
|
||||
rv['unconfirmed_balance'] = self.format_amount(balance_info['balance'] - balance_info['unlocked_balance'])
|
||||
rv['encrypted'] = False if self._wallet_password is None else True
|
||||
@@ -209,13 +212,13 @@ class XMRInterface(CoinInterface):
|
||||
def getMainWalletAddress(self) -> str:
|
||||
with self._mx_wallet:
|
||||
self.openWallet(self._wallet_filename)
|
||||
return self.rpc_wallet_cb('get_address')['address']
|
||||
return self.rpc_wallet('get_address')['address']
|
||||
|
||||
def getNewAddress(self, placeholder) -> str:
|
||||
with self._mx_wallet:
|
||||
self.openWallet(self._wallet_filename)
|
||||
new_address = self.rpc_wallet_cb('create_address', {'account_index': 0})['address']
|
||||
self.rpc_wallet_cb('store')
|
||||
new_address = self.rpc_wallet('create_address', {'account_index': 0})['address']
|
||||
self.rpc_wallet('store')
|
||||
return new_address
|
||||
|
||||
def get_fee_rate(self, conf_target: int = 2):
|
||||
@@ -280,7 +283,7 @@ class XMRInterface(CoinInterface):
|
||||
def publishBLockTx(self, kbv: bytes, Kbs: bytes, output_amount: int, feerate: int, delay_for: int = 10, unlock_time: int = 0) -> bytes:
|
||||
with self._mx_wallet:
|
||||
self.openWallet(self._wallet_filename)
|
||||
self.rpc_wallet_cb('refresh')
|
||||
self.rpc_wallet('refresh')
|
||||
|
||||
Kbv = self.getPubkey(kbv)
|
||||
shared_addr = xmr_util.encode_address(Kbv, Kbs)
|
||||
@@ -288,7 +291,7 @@ class XMRInterface(CoinInterface):
|
||||
params = {'destinations': [{'amount': output_amount, 'address': shared_addr}], 'unlock_time': unlock_time}
|
||||
if self._fee_priority > 0:
|
||||
params['priority'] = self._fee_priority
|
||||
rv = self.rpc_wallet_cb('transfer', params)
|
||||
rv = self.rpc_wallet('transfer', params)
|
||||
self._log.info('publishBLockTx %s to address_b58 %s', rv['tx_hash'], shared_addr)
|
||||
tx_hash = bytes.fromhex(rv['tx_hash'])
|
||||
|
||||
@@ -296,7 +299,7 @@ class XMRInterface(CoinInterface):
|
||||
i = 0
|
||||
while not self._sc.delay_event.is_set():
|
||||
gt_params = {'out': True, 'pending': True, 'failed': True, 'pool': True, }
|
||||
rv = self.rpc_wallet_cb('get_transfers', gt_params)
|
||||
rv = self.rpc_wallet('get_transfers', gt_params)
|
||||
self._log.debug('get_transfers {}'.format(dumpj(rv)))
|
||||
if 'pending' not in rv:
|
||||
break
|
||||
@@ -325,26 +328,26 @@ class XMRInterface(CoinInterface):
|
||||
self.createWallet(params)
|
||||
self.openWallet(address_b58)
|
||||
|
||||
self.rpc_wallet_cb('refresh', timeout=600)
|
||||
self.rpc_wallet('refresh', timeout=600)
|
||||
|
||||
'''
|
||||
# Debug
|
||||
try:
|
||||
current_height = self.rpc_wallet_cb('get_height')['height']
|
||||
current_height = self.rpc_wallet('get_height')['height']
|
||||
self._log.info('findTxB XMR current_height %d\nAddress: %s', current_height, address_b58)
|
||||
except Exception as e:
|
||||
self._log.info('rpc_cb failed %s', str(e))
|
||||
self._log.info('rpc failed %s', str(e))
|
||||
current_height = None # If the transfer is available it will be deep enough
|
||||
# and (current_height is None or current_height - transfer['block_height'] > cb_block_confirmed):
|
||||
'''
|
||||
params = {'transfer_type': 'available'}
|
||||
transfers = self.rpc_wallet_cb('incoming_transfers', params)
|
||||
transfers = self.rpc_wallet('incoming_transfers', params)
|
||||
rv = None
|
||||
if 'transfers' in transfers:
|
||||
for transfer in transfers['transfers']:
|
||||
# unlocked <- wallet->is_transfer_unlocked() checks unlock_time and CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE
|
||||
if not transfer['unlocked']:
|
||||
full_tx = self.rpc_wallet_cb('get_transfer_by_txid', {'txid': transfer['tx_hash']})
|
||||
full_tx = self.rpc_wallet('get_transfer_by_txid', {'txid': transfer['tx_hash']})
|
||||
unlock_time = full_tx['transfer']['unlock_time']
|
||||
if unlock_time != 0:
|
||||
self._log.warning('Coin b lock txn is locked: {}, unlock_time {}'.format(transfer['tx_hash'], unlock_time))
|
||||
@@ -360,17 +363,17 @@ class XMRInterface(CoinInterface):
|
||||
def findTxnByHash(self, txid):
|
||||
with self._mx_wallet:
|
||||
self.openWallet(self._wallet_filename)
|
||||
self.rpc_wallet_cb('refresh', timeout=600)
|
||||
self.rpc_wallet('refresh', timeout=600)
|
||||
|
||||
try:
|
||||
current_height = self.rpc_cb2('get_height', timeout=30)['height']
|
||||
current_height = self.rpc2('get_height', timeout=30)['height']
|
||||
self._log.info('findTxnByHash XMR current_height %d\nhash: %s', current_height, txid)
|
||||
except Exception as e:
|
||||
self._log.info('rpc_cb failed %s', str(e))
|
||||
self._log.info('rpc failed %s', str(e))
|
||||
current_height = None # If the transfer is available it will be deep enough
|
||||
|
||||
params = {'transfer_type': 'available'}
|
||||
rv = self.rpc_wallet_cb('incoming_transfers', params)
|
||||
rv = self.rpc_wallet('incoming_transfers', params)
|
||||
if 'transfers' in rv:
|
||||
for transfer in rv['transfers']:
|
||||
if transfer['tx_hash'] == txid \
|
||||
@@ -405,11 +408,11 @@ class XMRInterface(CoinInterface):
|
||||
self.createWallet(params)
|
||||
self.openWallet(wallet_filename)
|
||||
|
||||
self.rpc_wallet_cb('refresh')
|
||||
rv = self.rpc_wallet_cb('get_balance')
|
||||
self.rpc_wallet('refresh')
|
||||
rv = self.rpc_wallet('get_balance')
|
||||
if rv['balance'] < cb_swap_value:
|
||||
self._log.warning('Balance is too low, checking for existing spend.')
|
||||
txns = self.rpc_wallet_cb('get_transfers', {'out': True})
|
||||
txns = self.rpc_wallet('get_transfers', {'out': True})
|
||||
if 'out' in txns:
|
||||
txns = txns['out']
|
||||
if len(txns) > 0:
|
||||
@@ -434,7 +437,7 @@ class XMRInterface(CoinInterface):
|
||||
if self._fee_priority > 0:
|
||||
params['priority'] = self._fee_priority
|
||||
|
||||
rv = self.rpc_wallet_cb('sweep_all', params)
|
||||
rv = self.rpc_wallet('sweep_all', params)
|
||||
self._log.debug('sweep_all {}'.format(json.dumps(rv)))
|
||||
|
||||
return bytes.fromhex(rv['tx_hash_list'][0])
|
||||
@@ -444,24 +447,24 @@ class XMRInterface(CoinInterface):
|
||||
value_sats = make_int(value, self.exp())
|
||||
|
||||
self.openWallet(self._wallet_filename)
|
||||
self.rpc_wallet_cb('refresh')
|
||||
self.rpc_wallet('refresh')
|
||||
|
||||
if subfee:
|
||||
balance = self.rpc_wallet_cb('get_balance')
|
||||
balance = self.rpc_wallet('get_balance')
|
||||
diff = balance['unlocked_balance'] - value_sats
|
||||
if diff >= 0 and diff <= 10:
|
||||
self._log.info('subfee enabled and value close to total, using sweep_all.')
|
||||
params = {'address': addr_to}
|
||||
if self._fee_priority > 0:
|
||||
params['priority'] = self._fee_priority
|
||||
rv = self.rpc_wallet_cb('sweep_all', params)
|
||||
rv = self.rpc_wallet('sweep_all', params)
|
||||
return rv['tx_hash_list'][0]
|
||||
raise ValueError('Withdraw value must be close to total to use subfee/sweep_all.')
|
||||
|
||||
params = {'destinations': [{'amount': value_sats, 'address': addr_to}]}
|
||||
if self._fee_priority > 0:
|
||||
params['priority'] = self._fee_priority
|
||||
rv = self.rpc_wallet_cb('transfer', params)
|
||||
rv = self.rpc_wallet('transfer', params)
|
||||
return rv['tx_hash']
|
||||
|
||||
def showLockTransfers(self, kbv, Kbs, restore_height):
|
||||
@@ -488,9 +491,9 @@ class XMRInterface(CoinInterface):
|
||||
self.createWallet(params)
|
||||
self.openWallet(address_b58)
|
||||
|
||||
self.rpc_wallet_cb('refresh')
|
||||
self.rpc_wallet('refresh')
|
||||
|
||||
rv = self.rpc_wallet_cb('get_transfers', {'in': True, 'out': True, 'pending': True, 'failed': True})
|
||||
rv = self.rpc_wallet('get_transfers', {'in': True, 'out': True, 'pending': True, 'failed': True})
|
||||
rv['filename'] = wallet_file
|
||||
return rv
|
||||
except Exception as e:
|
||||
@@ -500,8 +503,8 @@ class XMRInterface(CoinInterface):
|
||||
with self._mx_wallet:
|
||||
self.openWallet(self._wallet_filename)
|
||||
|
||||
self.rpc_wallet_cb('refresh')
|
||||
balance_info = self.rpc_wallet_cb('get_balance')
|
||||
self.rpc_wallet('refresh')
|
||||
balance_info = self.rpc_wallet('get_balance')
|
||||
return balance_info['unlocked_balance']
|
||||
|
||||
def changeWalletPassword(self, old_password, new_password):
|
||||
@@ -511,7 +514,7 @@ class XMRInterface(CoinInterface):
|
||||
self._wallet_password = old_password
|
||||
try:
|
||||
self.openWallet(self._wallet_filename)
|
||||
self.rpc_wallet_cb('change_wallet_password', {'old_password': old_password, 'new_password': new_password})
|
||||
self.rpc_wallet('change_wallet_password', {'old_password': old_password, 'new_password': new_password})
|
||||
except Exception as e:
|
||||
self._wallet_password = orig_password
|
||||
raise e
|
||||
@@ -536,4 +539,4 @@ class XMRInterface(CoinInterface):
|
||||
raise ValueError('Balance too low')
|
||||
|
||||
def getTransaction(self, txid: bytes):
|
||||
return self.rpc_cb2('get_transactions', {'txs_hashes': [txid.hex(), ]})
|
||||
return self.rpc2('get_transactions', {'txs_hashes': [txid.hex(), ]})
|
||||
|
||||
Reference in New Issue
Block a user