def extract_secret_from_redeem_transaction( cls, contract_address: str) -> Optional[str]: ''' Extracting secret from redeem transaction based on contract address. Args: contract_address (str): address of the contract atomic swap contract Returns: str, None: Secret string or None if contract wasn't redeemed yet. Example: >>> from clove.network import BitcoinTestNet >>> network = BitcoinTestNet() >>> network.extract_secret_from_redeem_transaction('2N7Gxryn4dD1mdyGM3DMxMAwD7k3RBTJ1gP') 90f6b9b9a34acb486654b3e9cdc02cce0b8e40a8845924ffda68453ac2477d20 ''' contract_transactions = clove_req_json( f'{cls.api_url}/addr/{contract_address}')['transactions'] if not contract_transactions: logger.error( f'Cannot get contract transactions ({cls.symbols[0]})') return if len(contract_transactions) < 2: logger.debug( 'There is no redeem transaction on this contract yet.') return redeem_transaction = cls.get_transaction(contract_transactions[0]) if not redeem_transaction: logger.error(f'Cannot get redeem transaction ({cls.symbols[0]})') return return cls.extract_secret( scriptsig=redeem_transaction['vin'][0]['scriptSig']['hex'])
def extract_secret_from_redeem_transaction( cls, contract_address: str) -> Optional[str]: ''' Extracting secret from redeem transaction based on contract address. Args: contract_address (str): address of the contract atomic swap contract Returns: str, None: Secret string or None if contract wasn't redeemed yet. Example: >>> from clove.network import BitcoinTestNet >>> network = BitcoinTestNet() >>> network.extract_secret_from_redeem_transaction('2N7Gxryn4dD1mdyGM3DMxMAwD7k3RBTJ1gP') 90f6b9b9a34acb486654b3e9cdc02cce0b8e40a8845924ffda68453ac2477d20 ''' data = clove_req_json( f'{cls.blockcypher_url()}/addrs/{contract_address}/full') if not data: logger.error('Unexpected response from blockcypher') raise ValueError('Unexpected response from blockcypher') transactions = data['txs'] if len(transactions) == 1: logger.debug('Contract was not redeemed yet.') return return cls.extract_secret( scriptsig=transactions[0]['inputs'][0]['script'])
def get_utxo(cls, address: str, amount: float): data = clove_req_json( f'{cls.blockcypher_url()}/addrs/{address}' '?limit=2000&unspentOnly=true&includeScript=true&confirmations=6') unspent = data.get('txrefs', []) for output in unspent: output['value'] = int(output['value']) unspent = sorted(unspent, key=lambda k: k['value'], reverse=True) utxo = [] total = 0 for output in unspent: value = from_base_units(output['value']) utxo.append( Utxo( tx_id=output['tx_hash'], vout=output['tx_output_n'], value=value, tx_script=output['script'], )) total += value if total > amount: return utxo logger.debug(f'Cannot find enough UTXO\'s. Found %.8f from %.8f.', total, amount)
def get_utxo(cls, address: str, amount: float): api_key = os.environ.get('CRYPTOID_API_KEY') if not api_key: raise ValueError('API key for cryptoid is required to get UTXOs.') data = clove_req_json(f'{cls.cryptoid_url()}/api.dws?q=unspent&key={api_key}&active={address}') unspent = data.get('unspent_outputs', []) for output in unspent: output['value'] = int(output['value']) unspent = sorted(unspent, key=lambda k: k['value'], reverse=True) utxo = [] total = 0 for output in unspent: value = from_base_units(output['value']) utxo.append( Utxo( tx_id=output['tx_hash'], vout=output['tx_ouput_n'], value=value, tx_script=output['script'], ) ) total += value if total > amount: return utxo logger.debug(f'Cannot find enough UTXO\'s. Found %.8f from %.8f.', total, amount)
def get_latest_block(cls) -> Optional[int]: ''' Returns the number of the latest block. Returns: int, None: number of the latest block or None if API is not working Example: >>> from clove.network import Bitcoin >>> network = Bitcoin() >>> network.get_latest_block() 544989 ''' try: latest_block = clove_req_json( f'{cls.api_url}/status?q=getInfo')['info']['blocks'] except (TypeError, KeyError): logger.error( f'Cannot get latest block, bad response ({cls.symbols[0]})') return if not latest_block: logger.debug(f'Latest block not found ({cls.symbols[0]})') return logger.debug(f'Latest block found: {latest_block}') return latest_block
def get_fee(cls) -> Optional[float]: ''' Getting actual fee per kb Returns: float, None: actual fee per kb or None if eg. API is not responding Example: >>> from clove.network import BitcoinTestNet >>> network = BitcoinTestNet() >>> network.get_fee() 0.00024538 ''' try: # This endpoint is available from v0.3.1 fee = clove_req_json( f'{cls.api_url}/utils/estimatefee?nbBlocks=1')['1'] except (TypeError, KeyError): logger.error( f'Incorrect response from API when getting fee from {cls.api_url}/utils/estimatefee?nbBlocks=1' ) return cls._calculate_fee() if fee == -1: logger.debug(f'Incorrect value in estimatedFee: {fee}') return cls._calculate_fee() fee = float(fee) if fee > 0: logger.warning( f'Got fee = 0 for ({cls.symbols[0]}), calculating manually') return fee return cls._calculate_fee()
def extract_secret_from_redeem_transaction( cls, contract_address: str) -> Optional[str]: query = """ { allAddressTxes(orderBy: TIME_ASC, condition: { address: "%s" }) { nodes { txId } } } """ % (contract_address) data = clove_req_json(f'{cls.api_url}/graphql', post_data={'query': query}) contract_transactions = data['data']['allAddressTxes']['nodes'] if not contract_transactions: logger.error( f'Cannot get contract transactions ({cls.symbols[0]})') return if len(contract_transactions) < 2: logger.debug( 'There is no redeem transaction on this contract yet.') return redeem_transaction = cls.get_transaction( contract_transactions[1]['txId']) if not redeem_transaction: logger.error(f'Cannot get redeem transaction ({cls.symbols[0]})') return return cls.extract_secret(scriptsig=redeem_transaction['vinsByTxId'] ['nodes'][0]['scriptSig'])
def get_latest_block(cls) -> Optional[int]: '''Returns the number of the latest block.''' query = ''' { allBlocks(orderBy: HEIGHT_DESC, first: 1) { nodes { height } } } ''' try: json_response = clove_req_json(f'{cls.api_url}/graphql', post_data={'query': query}) latest_block = json_response['data']['allBlocks']['nodes'][0][ 'height'] except (TypeError, KeyError): logger.error( f'Cannot get latest block, bad response ({cls.symbols[0]})') return if not latest_block: logger.debug(f'Latest block not found ({cls.symbols[0]})') return logger.debug(f'Latest block found: {latest_block}') return latest_block
def get_balance(cls, wallet_address: str) -> Optional[float]: data = clove_req_json( f'{cls.blockcypher_url()}/addrs/{wallet_address}/balance') if data is None: logger.error('Could not get details for address %s in %s network', wallet_address, cls.symbols[0]) return return from_base_units(data['balance'] or data['unconfirmed_balance'])
def _get_block_hash(cls, block_number: int) -> str: '''Getting block hash by its number''' try: block_hash = clove_req_json(f'{cls.api_url}/block-index/{block_number}')['blockHash'] except (TypeError, KeyError): logger.error(f'Cannot get block hash for block {block_number} ({cls.symbols[0]})') return logger.debug(f'Found hash for block {block_number}: {block_hash}') return block_hash
def get_balance(cls, wallet_address: str) -> float: api_key = os.environ.get('CRYPTOID_API_KEY') if api_key is None: raise ValueError('API key for cryptoid is required to get balance.') data = clove_req_json(f'{cls.cryptoid_url()}/api.dws?q=getbalance&a={wallet_address}&key={api_key}') if data is None: logger.debug('Could not get details for address %s in %s network', wallet_address, cls.symbols[0]) return return data
def get_fee(cls) -> Optional[float]: '''Returns actual fee per kb.''' response = clove_req_json(cls.fee_endpoint) fee = response.get('high_fee_per_kb') if not fee: logger.error( 'Cannot find the right key (high_fee_per_kb) while getting fee in blockcypher.' ) return return from_base_units(fee)
def extract_secret_from_redeem_transaction( cls, contract_address: str) -> Optional[str]: contract_transactions = clove_req_json( f'https://mona.chainseeker.info/api/v1/txids/{contract_address}') if len(contract_transactions) < 2: logger.debug( 'There is no redeem transaction on this contract yet.') return redeem_transaction = cls.get_transaction(contract_transactions[1]) return cls.extract_secret(redeem_transaction['hex'])
def _get_transactions_from_block(cls, block_number: int): '''Getting transactions from block by given block number''' block_hash = cls._get_block_hash(block_number) if not block_hash: return transactions_page = clove_req_json(f'{cls.api_url}/txs/?block={block_hash}') if not transactions_page: return transactions = transactions_page['txs'] logger.debug(f'Found {len(transactions)} in block {block_number}') return transactions
def get_latest_block(cls) -> Optional[int]: '''Returns the number of the latest block.''' try: latest_block = clove_req_json(f'{cls.api_url}/status?q=getInfo')['info']['blocks'] except (TypeError, KeyError): logger.error(f'Cannot get latest block, bad response ({cls.symbols[0]})') return if not latest_block: logger.debug(f'Latest block not found ({cls.symbols[0]})') return logger.debug(f'Latest block found: {latest_block}') return latest_block
def get_utxo(cls, address: str, amount: float) -> Optional[list]: ''' Getting list of UTXO objects. Args: address (str): wallet address to look for UTXO amount (float): minimum value that should be satisfied in UTXO objects Returns: list, None: list of UTXO objects or None it there was not enough UTXO Example: >>> from clove.network import Litecoin >>> network = Litecoin() >>> network.get_utxo(address='LUAn5PWmsPavgz32mGkqsUuAKncftS37Jq', amount=0.01) [ Utxo(tx_id='0cd90567497823097d03464b4b2d08dd659f1c5621dd55e9540bc9bcd3e191ec', vout='0', value='0.00976168', tx_script='76a91485c0522f6e23beb11cc3d066cd20ed732648a4e688ac', wallet=None, secret=None, refund=False), # noqa: E501 Utxo(tx_id='a5c027027c695f403fe570850e35ffd44bb28479ecaaee039372015fe0aae7b2', vout='0', value='0.00097114', tx_script='76a91485c0522f6e23beb11cc3d066cd20ed732648a4e688ac', wallet=None, secret=None, refund=False) # noqa: E501 ] ''' data = clove_req_json( f'{cls.blockcypher_url()}/addrs/{address}' '?limit=2000&unspentOnly=true&includeScript=true&confirmations=6') if not data: logger.debug( f'Cannot find UTXO for address {address} ({cls.symbols[0]})') return unspent = data.get('txrefs', []) for output in unspent: output['value'] = int(output['value']) unspent = sorted(unspent, key=lambda k: k['value'], reverse=True) utxo = [] total = 0 for output in unspent: value = from_base_units(output['value']) utxo.append( Utxo( tx_id=output['tx_hash'], vout=output['tx_output_n'], value=value, tx_script=output['script'], )) total += value if total > amount: return utxo logger.debug(f'Cannot find enough UTXO\'s. Found %.8f from %.8f.', total, amount)
def extract_secret_from_redeem_transaction(cls, contract_address: str) -> Optional[str]: contract_transactions = clove_req_json(f'{cls.api_url}/txids/{contract_address}') if not contract_transactions: logger.error(f'Cannot get contract transactions ({cls.symbols[0]})') return if len(contract_transactions) < 2: logger.debug('There is no redeem transaction on this contract yet.') return redeem_transaction = cls.get_transaction(contract_transactions[1]) if not redeem_transaction: logger.error(f'Cannot get redeem transaction ({cls.symbols[0]})') return return cls.extract_secret(redeem_transaction['hex'])
def get_current_fee_per_kb(cls) -> Optional[float]: """Getting current network fee from Clove API""" network = cls.symbols[0].upper() if cls.testnet: network += '-TESTNET' resp = clove_req_json(f'{CLOVE_API_URL}/fee/{network}') if not resp: logger.debug('Could not get current fee for %s network', network) return return resp['fee']
def get_fee(cls) -> float: """Ravencoin has a different endpoint for fee (estimatesmartfee, not estimatefee)""" try: fee = clove_req_json(f'{cls.api_url}/utils/estimatesmartfee?nbBlocks=1')['1'] except (TypeError, KeyError): logger.error( f'Incorrect response from API when getting fee from {cls.api_url}/utils/estimatefee?nbBlocks=1' ) return cls._calculate_fee() if fee > 0: return fee logger.warning(f'({cls.symbols[0]}) Got fee = 0, calculating manually') return cls._calculate_fee()
def extract_secret_from_redeem_transaction(cls, contract_address: str) -> Optional[str]: api_key = os.environ.get('CRYPTOID_API_KEY') if not api_key: raise ValueError('API key for cryptoid is required.') data = clove_req_json(f'{cls.cryptoid_url()}/api.dws?q=multiaddr&active={contract_address}&key={api_key}') if not data: logger.debug('Unexpected response from cryptoid') raise ValueError('Unexpected response from cryptoid') transactions = data['txs'] if len(transactions) == 1: logger.debug('Contract was not redeemed yet.') return redeem_tx_hash = transactions[0]['hash'] logger.warning('Using undocumented endpoint used by chainz.cryptoid.info site.') data = clove_req_json(f'{cls.api_url}/explorer/tx.raw.dws?coin={cls.symbols[0].lower()}&id={redeem_tx_hash}') if not data: logger.debug('Unexpected response from cryptoid') raise ValueError('Unexpected response from cryptoid') return cls.extract_secret(scriptsig=data['vin'][0]['scriptSig']['hex'])
def get_latest_block(cls) -> Optional[int]: ''' Returns the number of the latest block. Returns: int, None: number of the latest block or None if API is not working Example: >>> from clove.network import Bitcoin >>> network = Bitcoin() >>> network.get_latest_block() 544989 ''' return clove_req_json(f'{cls.blockcypher_url()}')['height']
def get_transaction(cls, tx_address: str) -> Optional[dict]: ''' Getting transaction details. Args: tx_address (str): transaction address Returns: dict, None: dictionary with transaction details or None if transaction doesn't exist Example: >>> from clove.network import Bitcoin >>> network = Bitcoin() >>> network.get_transaction('a82213fd237a2b4bf05c805611dc913125aef138bf2874f0668133a4bb5f3b64') {'blockhash': '0000000000000000000e2d8d964b4da69f2f30b79aaa58597848719bf0b86a1b', 'blockheight': 544987, 'blocktime': 1539068621, 'confirmations': 3, 'fees': 0.0011, 'locktime': 0, 'size': 192, 'time': 1539068621, 'txid': 'a82213fd237a2b4bf05c805611dc913125aef138bf2874f0668133a4bb5f3b64', 'valueIn': 0.60725408, 'valueOut': 0.60615408, 'version': 1, 'vin': [{'addr': '163o7ig87TnUnp1QF1rBrsjU1uhfEW9nNU', 'doubleSpentTxID': None, 'n': 0, 'scriptSig': { 'asm': '3045022100ad5db8c05d7f702c8328ae5a817a13dd7f0fda876e3bb3b7729b041bb612275502200af30b833c06c8485ccec95de48c2238a4ffa4e4820dd6466b95dc5d26e883ae[ALL] 03b504de54d5940a81cf5f8c483025c6f39bfc7eed60a022549513fd8d6e41d74f', # noqa: E50 'hex': '483045022100ad5db8c05d7f702c8328ae5a817a13dd7f0fda876e3bb3b7729b041bb612275502200af30b833c06c8485ccec95de48c2238a4ffa4e4820dd6466b95dc5d26e883ae012103b504de54d5940a81cf5f8c483025c6f39bfc7eed60a022549513fd8d6e41d74f'}, # noqa: E501 'sequence': 4294967295, 'txid': '101cc115cc6882e1fd150c35efd056d18a73c12a3321c406960844561922dfc0', 'value': 0.60725408, 'valueSat': 60725408, 'vout': 0}], 'vout': [{'n': 0, 'scriptPubKey': {'addresses': ['13xMGnw7sTTVXT26YpfZQkk2rvuvJFoMvi'], 'asm': 'OP_DUP OP_HASH160 20680d49e72e1de6af9a0180b3293f2cbd0d0666 OP_EQUALVERIFY OP_CHECKSIG', 'hex': '76a91420680d49e72e1de6af9a0180b3293f2cbd0d066688ac', 'type': 'pubkeyhash'}, 'spentHeight': None, 'spentIndex': None, 'spentTxId': None, 'value': '0.60615408'}]} ''' return clove_req_json( f'{cls.blockcypher_url()}/txs/{tx_address}?includeHex=true')
def extract_secret_from_redeem_transaction( cls, contract_address: str) -> Optional[str]: data = clove_req_json( f'{cls.blockcypher_url()}/addrs/{contract_address}/full') if not data: logger.error('Unexpected response from blockcypher') raise ValueError('Unexpected response from blockcypher') transactions = data['txs'] if len(transactions) == 1: logger.debug('Contract was not redeemed yet.') return return cls.extract_secret( scriptsig=transactions[0]['inputs'][0]['script'])
def get_utxo(cls, address, amount): query = """ { getAddressTxs(_address: "%s") { nodes { voutsByTxId(condition: { spendingN: null }) { nodes { txId n value scriptPubKey } } } } } """ % (address) data = clove_req_json(f'{cls.api_url}/graphql', post_data={'query': query}) vouts = [] for node in data['data']['getAddressTxs']['nodes']: for vout in node['voutsByTxId']['nodes']: vouts.append(vout) unspent = sorted(vouts, key=lambda k: k['value'], reverse=True) utxo = [] total = 0 for output in unspent: value = float(output['value']) utxo.append( Utxo( tx_id=output['txId'], vout=output['n'], value=value, tx_script=output['scriptPubKey'], )) total += value if total > amount: return utxo logger.debug(f'Cannot find enough UTXO\'s. Found %.8f from %.8f.', total, amount)
def get_fee(cls) -> Optional[float]: # This endpoint is available from v0.3.1 try: fee = clove_req_json(f'{cls.api_url}/utils/estimatefee?nbBlocks=1')['1'] except (TypeError, KeyError): logger.error( f'Incorrect response from API when getting fee from {cls.api_url}/utils/estimatefee?nbBlocks=1' ) return cls._calculate_fee() if fee == -1: logger.debug(f'Incorrect value in estimatedFee: {fee}') return cls._calculate_fee() fee = float(fee) if fee > 0: logger.warning(f'Got fee = 0 for ({cls.symbols[0]}), calculating manually') return fee return cls._calculate_fee()
def find_redeem_transaction(self, recipient_address: str, contract_address: str, value: int) -> Optional[str]: recipient_address = recipient_address.lower() contract_address = contract_address.lower() value = str(value) etherscan_api_key = os.getenv('ETHERSCAN_API_KEY') if not etherscan_api_key: raise ValueError('API key for etherscan is required.') data = clove_req_json( f'http://{self.etherscan_api_subdomain}.etherscan.io/api?module=account&action=txlistinternal' f'&address={recipient_address}&apikey={etherscan_api_key}' ) for result in reversed(data['result']): if result['to'] == recipient_address and result['from'] == contract_address and result['value'] == value: return result['hash'] logger.debug('Redeem transaction not found.')
def get_balance(cls, wallet_address: str) -> float: ''' Returns wallet balance without unconfirmed transactions. Args: wallet_address (str): wallet address Returns: float: amount converted from base units Example: >>> from clove.network import Ravencoin >>> r = Ravencoin() >>> r.get_balance('RM7w75BcC21LzxRe62jy8JhFYykRedqu8k') >>> 18.99 ''' wallet_utxo = clove_req_json(f'{cls.api_url}/addr/{wallet_address}/balance') if not wallet_utxo: return 0 return from_base_units(wallet_utxo)
def get_fee(cls) -> Optional[float]: ''' Getting actual fee per kb Returns: float, None: actual fee per kb or None if eg. API is not responding Example: >>> from clove.network import BitcoinTestNet >>> network = BitcoinTestNet() >>> network.get_fee() 0.00024538 ''' response = clove_req_json(cls.blockcypher_url()) fee = response.get('high_fee_per_kb') if not fee: logger.error( 'Cannot find the right key (high_fee_per_kb) while getting fee in blockcypher.' ) return return from_base_units(fee)
def get_balance(cls, wallet_address: str) -> float: ''' Returns wallet balance without unconfirmed transactions. Args: wallet_address (str): wallet address Returns: float: amount converted from base units Example: >>> from clove.network import Ravencoin >>> r = Ravencoin() >>> r.get_balance('RM7w75BcC21LzxRe62jy8JhFYykRedqu8k') >>> 18.99 ''' query = """ { getAddressTxs(_address: "%s") { nodes { voutsByTxId(condition: { spendingN: null }) { nodes { value } } } } } """ % (wallet_address) data = clove_req_json(f'{cls.api_url}/graphql', post_data={'query': query}) total = 0 for node in data['data']['getAddressTxs']['nodes']: for vout in node['voutsByTxId']['nodes']: total += float(vout['value']) return total
def get_utxo(cls, address, amount): data = clove_req_json(f'{cls.api_url}/addrs/{address}/utxo') unspent = sorted(data, key=lambda k: k['satoshis'], reverse=True) utxo = [] total = 0 for output in unspent: value = from_base_units(output['satoshis']) utxo.append( Utxo( tx_id=output['txid'], vout=output['vout'], value=value, tx_script=output['scriptPubKey'], ) ) total += value if total > amount: return utxo logger.debug(f'Cannot find enough UTXO\'s. Found %.8f from %.8f.', total, amount)