def gettransaction(self, txid): tx = self.compose_request('txs', txid, variables={'includeHex': 'true'}) t = Transaction.import_raw(tx['hex'], network=self.network) if tx['confirmations']: t.status = 'confirmed' t.date = datetime.strptime(tx['confirmed'][:19], "%Y-%m-%dT%H:%M:%S") else: t.status = 'unconfirmed' t.confirmations = tx['confirmations'] t.block_height = tx['block_height'] if tx['block_height'] > 0 else None t.fee = tx['fees'] t.rawtx = bytes.fromhex(tx['hex']) t.size = int(len(tx['hex']) / 2) t.network = self.network t.input_total = 0 if len(t.inputs) != len(tx['inputs']): raise ClientError("Invalid number of inputs provided. Raw tx: %d, blockcypher: %d" % (len(t.inputs), len(tx['inputs']))) for n, i in enumerate(t.inputs): if not t.coinbase and not (tx['inputs'][n]['output_index'] == i.output_n_int and tx['inputs'][n]['prev_hash'] == i.prev_txid.hex()): raise ClientError("Transaction inputs do not match raw transaction") if 'output_value' in tx['inputs'][n]: if not t.coinbase: i.value = tx['inputs'][n]['output_value'] t.input_total += i.value if len(t.outputs) != len(tx['outputs']): raise ClientError("Invalid number of outputs provided. Raw tx: %d, blockcypher: %d" % (len(t.outputs), len(tx['outputs']))) for n, o in enumerate(t.outputs): if 'spent_by' in tx['outputs'][n]: o.spent = True o.spending_txid = tx['outputs'][n]['spent_by'] return t
def gettransactions(self, address, after_txid='', max_txs=MAX_TRANSACTIONS): txs = [] res1 = self.compose_request('get_tx_received', address, after_txid) if res1['status'] != 'success': raise ClientError("Chainso get_tx_received request unsuccessful, status: %s" % res1['status']) res2 = self.compose_request('get_tx_spent', address, after_txid) if res2['status'] != 'success': raise ClientError("Chainso get_tx_spent request unsuccessful, status: %s" % res2['status']) res = res1['data']['txs'] + res2['data']['txs'] tx_conf = [(t['txid'], t['confirmations']) for t in res] tx_conf_sorted = sorted(tx_conf, key=lambda x: x[1], reverse=True) for tx in tx_conf_sorted[:max_txs]: t = self.gettransaction(tx[0]) txs.append(t) return txs
def getutxos(self, address, after_txid='', limit=MAX_TRANSACTIONS): if not self.api_key: raise ClientError("Method getutxos() is not available for CryptoID without API key") utxos = [] address = self._address_convert(address) variables = {'active': address.address} res = self.compose_request('unspent', variables=variables) if len(res['unspent_outputs']) > 50: _logger.info("CryptoID: Large number of outputs for address %s, " "UTXO list may be incomplete" % address.address) for utxo in res['unspent_outputs'][::-1]: if utxo['txid'] == after_txid: break utxos.append({ 'address': address.address_orig, 'txid': utxo['txid'], 'confirmations': utxo['confirmations'], 'output_n': utxo['tx_output_n'] if 'tx_output_n' in utxo else utxo['tx_ouput_n'], 'input_n': 0, 'block_height': None, 'fee': None, 'size': 0, 'value': int(utxo['value']), 'script': utxo['script'], 'date': None }) return utxos[::-1][:limit]
def getrawtransaction(self, tx_id): tx = self.compose_request('rawtx', tx_id) t = Transaction.import_raw(tx['rawtx'], network=self.network) for i in t.inputs: if not i.address: raise ClientError("Address missing in input. Provider might not support segwit transactions") return tx['rawtx']
def getutxos(self, address, after_txid='', limit=MAX_TRANSACTIONS): txs = [] res = self.proxy.getaddressinfo(address) if not (res['ismine'] or res['iswatchonly']): raise ClientError( "Address %s not found in bitcoind wallet, use 'importpubkey' or 'importaddress' to add " "address to wallet." % address) txs_list = self.proxy.listunspent(0, 99999999, [address]) for t in sorted(txs_list, key=lambda x: x['confirmations'], reverse=True): txs.append({ 'address': t['address'], 'tx_hash': t['txid'], 'confirmations': t['confirmations'], 'output_n': t['vout'], 'input_n': -1, 'block_height': None, 'fee': None, 'size': 0, 'value': int(t['amount'] * self.units), 'script': t['scriptPubKey'], 'date': None, }) if t['txid'] == after_txid: txs = [] return txs
def getblock(self, blockid, parse_transactions, page, limit): block = self.compose_request('block', str(blockid)) # FIXME: This doesnt work if page or limit is used, also see pages calc below block['tx_count'] = len(block['txs']) txs = block['txs'] parsed_txs = [] if parse_transactions: txs = txs[(page-1)*limit:page*limit] for tx in txs: tx['confirmations'] = block['depth'] tx['time'] = block['time'] tx['height'] = block['height'] tx['block'] = block['hash'] if parse_transactions: t = self._parse_transaction(tx) if t.txid != tx['hash']: raise ClientError("Could not parse tx %s. Different txid's" % (tx['hash'])) parsed_txs.append(t) else: parsed_txs.append(tx['hash']) block['time'] = block['time'] block['txs'] = parsed_txs block['page'] = page block['pages'] = int(block['tx_count'] // limit) + (block['tx_count'] % limit > 0) block['limit'] = limit block['prev_block'] = block.pop('prevBlock') block['merkle_root'] = block.pop('merkleRoot') block['block_hash'] = block.pop('hash') return block
def gettransactions(self, address, after_txid='', max_txs=MAX_TRANSACTIONS): txs = [] address = self._address_convert(address) if address.witness_type != 'legacy': raise ClientError("Provider does not support segwit addresses") res = self.compose_request('addrs', address.address, variables={ 'unspentOnly': 0, 'limit': 2000 }) if not isinstance(res, list): res = [res] for a in res: if 'txrefs' not in a: continue txids = [] for t in a['txrefs'][::-1]: if t['tx_hash'] not in txids: txids.append(t['tx_hash']) if t['tx_hash'] == after_txid: txids = [] if len(txids) > 500: _logger.warning( "BlockCypher: Large number of transactions for address %s, " "Transaction list may be incomplete" % address.address_orig) for txid in txids[:max_txs]: t = self.gettransaction(txid) txs.append(t) return txs
def getutxos(self, addresslist): txs = [] for addr in addresslist: res = self.proxy.validateaddress(addr) if not (res['ismine'] or res['iswatchonly']): raise ClientError( "Address %s not found in Litecoind wallet, use 'importaddress' to add address to " "wallet." % addr) for t in self.proxy.listunspent(0, 99999999, addresslist): txs.append({ 'address': t['address'], 'tx_hash': t['txid'], 'confirmations': t['confirmations'], 'output_n': t['vout'], 'input_n': -1, 'block_height': None, 'fee': None, 'size': 0, 'value': int(t['amount'] * self.units), 'script': t['scriptPubKey'], 'date': None, }) return txs
def _parse_transaction(self, tx, strict=True): status = 'unconfirmed' if tx['confirmations']: status = 'confirmed' t = Transaction.parse_hex(tx['hex'], strict=False, network=self.network) if not t.txid == tx['hash']: if strict: raise ClientError('Received transaction has different txid') else: t.txid = tx['hash'] _logger.warning('Received transaction has different txid') t.locktime = tx['locktime'] t.network = self.network t.fee = tx['fee'] t.date = datetime.utcfromtimestamp(tx['time']) if tx['time'] else None t.confirmations = tx['confirmations'] t.block_height = tx['height'] if tx['height'] > 0 else None t.block_hash = tx['block'] t.status = status if not t.coinbase: for i in t.inputs: i.value = tx['inputs'][t.inputs.index(i)]['coin']['value'] for o in t.outputs: o.spent = None t.update_totals() return t
def getutxos(self, address, after_txid='', max_txs=MAX_TRANSACTIONS): txs = [] res = self.proxy.getaddressinfo(address) if not (res['ismine'] or res['iswatchonly']): raise ClientError( "Address %s not found in litecoind wallet, use 'importaddress' to add address to " "wallet." % address) for t in self.proxy.listunspent(0, 99999999, [address]): txs.append({ 'address': t['address'], 'tx_hash': t['txid'], 'confirmations': t['confirmations'], 'output_n': t['vout'], 'input_n': -1, 'block_height': None, 'fee': None, 'size': 0, 'value': int(t['amount'] * self.units), 'script': t['scriptPubKey'], 'date': None, }) return txs
def getutxos(self, address, after_txid='', limit=MAX_TRANSACTIONS): utxos = [] offset = 0 while True: res = self.compose_request('outputs', {'recipient': address, 'is_spent': 'false'}, offset=offset) if len(res['data']) == REQUEST_LIMIT: raise ClientError("Blockchair returned more then maximum of %d data rows" % REQUEST_LIMIT) current_block = res['context']['state'] for utxo in res['data'][::-1]: if utxo['is_spent']: continue if utxo['transaction_hash'] == after_txid: utxos = [] continue utxos.append({ 'address': address, 'txid': utxo['transaction_hash'], 'confirmations': current_block - utxo['block_id'], 'output_n': utxo['index'], 'input_n': 0, 'block_height': utxo['block_id'], 'fee': None, 'size': 0, 'value': utxo['value'], 'script': utxo['script_hex'], 'date': datetime.strptime(utxo['time'], "%Y-%m-%d %H:%M:%S") }) if not len(res['data']) or len(res['data']) < REQUEST_LIMIT: break offset += REQUEST_LIMIT return utxos[:limit]
def gettransaction(self, tx_id): tx = self.compose_request('transaction', tx_id) rawtx = tx['raw'] t = Transaction.import_raw(rawtx, network=self.network) if tx['confirmations']: t.status = 'confirmed' else: t.status = 'unconfirmed' if t.coinbase: t.input_total = t.output_total else: t.input_total = tx['total_input_value'] t.output_total = tx['total_output_value'] t.fee = tx['total_fee'] t.hash = tx['hash'] t.block_hash = tx['block_hash'] t.block_height = tx['block_height'] t.confirmations = tx['confirmations'] t.date = datetime.strptime(tx['block_time'], "%Y-%m-%dT%H:%M:%S+%f") t.size = tx['size'] for n, i in enumerate(t.inputs): if not tx['inputs'][n]['address']: raise ClientError( "Address missing in input. Provider might not support segwit transactions" ) i.value = tx['inputs'][n]['value'] for n, o in enumerate(t.outputs): if tx['outputs'][n]['address']: o.spent = True if 'spent_hash' in tx['outputs'][n] else False return t
def getutxos(self, address, after_txid='', limit=MAX_TRANSACTIONS): utxos = [] page = 1 while True: variables = { 'mode': 'verbose', 'limit': 50, 'page': page, 'order': 'asc' } try: res = self.compose_request('address', 'transactions', address, variables) res2 = self.compose_request('address', 'unconfirmed/transactions', address, variables) except ClientError as e: if "address not found" in self.resp.text: return [] else: raise ClientError(e.msg) txs = res['data']['list'] txs += res2['data']['list'] for tx in txs: for outp in tx['vOut']: utxo = tx['vOut'][outp] if 'address' not in utxo or utxo[ 'address'] != address or utxo['spent']: continue utxos.append({ 'address': utxo['address'], 'txid': tx['txId'], 'confirmations': 0 if 'confirmations' not in tx else tx['confirmations'], 'output_n': int(outp), 'input_n': 0, 'block_height': None if 'blockHeight' not in tx else tx['blockHeight'], 'fee': None, 'size': 0, 'value': utxo['value'], 'script': utxo['scriptPubKey'], 'date': datetime.utcfromtimestamp(tx['timestamp']) }) if tx['txId'] == after_txid: utxos = [] page += 1 if page > res['data']['pages']: break return utxos[:limit]
def getutxos(self, address, after_txid='', limit=MAX_TRANSACTIONS): # First get all transactions for this address from the blockchain from bitcoinlib.services.services import Service srv = Service(network=self.network.name, providers=['bcoin']) txs = srv.gettransactions(address, limit=25) # Fail if large number of transactions are found if not srv.complete: raise ClientError("If not all transactions known, we cannot determine utxo's. " "Increase limit or use other provider") utxos = [] for tx in txs: for unspent in tx.outputs: if unspent.address != address: continue if not srv.isspent(tx.txid, unspent.output_n): utxos.append( { 'address': unspent.address, 'txid': tx.txid, 'confirmations': tx.confirmations, 'output_n': unspent.output_n, 'input_n': 0, 'block_height': tx.block_height, 'fee': tx.fee, 'size': tx.size, 'value': unspent.value, 'script': unspent.lock_script.hex(), 'date': tx.date, } ) if tx.txid == after_txid: utxos = [] return utxos[:limit]
def gettransaction(self, tx_id): tx = self.compose_request('tx', tx_id) t = Transaction.import_raw(tx['hex'], network=self.network) if tx['confirmations']: t.status = 'confirmed' t.hash = tx_id t.date = datetime.strptime(tx['date'], "%Y-%m-%dT%H:%M:%S.%fZ") t.confirmations = tx['confirmations'] if 'height' in tx: t.block_height = tx['height'] t.block_hash = tx['blockhash'] t.fee = tx['fee'] t.rawtx = tx['hex'] t.size = len(tx['hex']) // 2 t.network = self.network if t.coinbase: input_values = [] t.input_total = t.output_total else: input_values = [(inp['account'], -inp['value']) for inp in tx['entries'] if inp['value'] < 0] if len(input_values) >= 49: raise ClientError( "More then 49 transaction inputs not supported by bitgo") t.input_total = sum([x[1] for x in input_values]) for i in t.inputs: if not i.address: raise ClientError( "Address missing in input. Provider might not support segwit transactions" ) if len(t.inputs) != len(input_values): i.value = None continue value = [x[1] for x in input_values if x[0] == i.address] if len(value) != 1: _logger.warning( "BitGoClient: Address %s input value should be found exactly 1 times in value list" % i.address) i.value = None else: i.value = value[0] for o in t.outputs: o.spent = None if t.input_total != t.output_total + t.fee: t.input_total = t.output_total + t.fee return t
def getblock(self, blockid, parse_transactions, page, limit): variables = { 'parse_transactions': parse_transactions, 'page': page, 'limit': limit } bd = self.compose_request('block', str(blockid), variables=variables) txs = [] if parse_transactions and bd['transactions'] and isinstance( bd['transactions'][0], dict): block_count = self.blockcount() for tx in bd['transactions']: tx['confirmations'] = bd['depth'] tx['time'] = bd['time'] tx['block_height'] = bd['height'] tx['block_hash'] = bd['block_hash'] t = self._parse_transaction(tx, block_count) if t.txid != tx['txid']: raise ClientError( "Could not parse tx %s. Different txid's" % (tx['txid'])) txs.append(t) else: txs = bd['transactions'] block = { 'bits': bd['bits'], 'depth': bd['depth'], 'block_hash': bd['block_hash'], 'height': bd['height'], 'merkle_root': bd['merkle_root'], 'nonce': bd['nonce'], 'prev_block': bd['prev_block'], 'time': bd['time'], 'tx_count': bd['tx_count'], 'txs': txs, 'version': bd['version'], 'page': page, 'pages': None if not limit else int(bd['tx_count'] // limit) + (bd['tx_count'] % limit > 0), 'limit': limit } return block
def gettransactions(self, address, after_txid='', limit=MAX_TRANSACTIONS): txs = [] res1 = self.compose_request('get_tx_received', address, after_txid) if res1['status'] != 'success': raise ClientError("Chainso get_tx_received request unsuccessful, status: %s" % res1['status']) res2 = self.compose_request('get_tx_spent', address, after_txid) if res2['status'] != 'success': raise ClientError("Chainso get_tx_spent request unsuccessful, status: %s" % res2['status']) res = res1['data']['txs'] + res2['data']['txs'] res = sorted(res, key=lambda x: x['time']) tx_conf = [] for t in res: tt = (t['confirmations'], t['txid']) if tt not in tx_conf: tx_conf.append(tt) for tx in tx_conf[:limit]: t = self.gettransaction(tx[1]) txs.append(t) return txs
def getutxos(self, address, after_txid='', max_txs=MAX_TRANSACTIONS): address = self._address_convert(address) if address.witness_type != 'legacy': raise ClientError("Provider does not support segwit addresses") res = self.compose_request('addrs', address.address, variables={ 'unspentOnly': 1, 'limit': 2000 }) transactions = [] if not isinstance(res, list): res = [res] for a in res: if 'txrefs' not in a: continue if len(a['txrefs']) > 500: _logger.warning( "BlockCypher: Large number of transactions for address %s, " "Transaction list may be incomplete" % address) for tx in a['txrefs']: if tx['tx_hash'] == after_txid: break try: tdate = datetime.strptime(tx['confirmed'], "%Y-%m-%dT%H:%M:%SZ") except ValueError: tdate = datetime.strptime(tx['confirmed'], "%Y-%m-%dT%H:%M:%S.%fZ") transactions.append({ 'address': address.address_orig, 'tx_hash': tx['tx_hash'], 'confirmations': tx['confirmations'], 'output_n': tx['tx_output_n'], 'index': 0, 'value': int(round(tx['value'] * self.units, 0)), 'script': '', 'block_height': None, 'date': tdate }) return transactions[::-1][:max_txs]
def compose_request(self, func=None, path_type='api', variables=None, method='get'): # API path: http://chainz.cryptoid.info/ltc/api.dws # Explorer path for raw tx: https://chainz.cryptoid.info/explorer/tx.raw.dws if variables is None: variables = {} if path_type == 'api': url_path = '%s/api.dws' % self.provider_coin_id variables.update({'q': func}) else: url_path = 'explorer/tx.raw.dws' variables.update({'coin': self.provider_coin_id}) if not self.api_key: raise ClientError("Request a CryptoID API key before using this provider") variables.update({'key': self.api_key}) return self.request(url_path, variables, method)
def compose_request(self, func, variables, service_id='fallback', include_raw=False, method='get'): url_path = func + '/' + service_id if not isinstance(variables, dict): raise ClientError( "Cannot compose request without variables. Variables must be of type dictionary." ) if method == 'get': variables.update({'currency': self.provider_coin_id}) else: url_path += '?currency=%s' % self.provider_coin_id if include_raw: variables.update({'include_raw': None}) return self.request(url_path, variables, method=method)
def gettransactions(self, address, after_txid='', max_txs=MAX_TRANSACTIONS): txs = [] while True: variables = {'limit': LIMIT_TX, 'after': after_txid} retries = 0 while retries < 3: try: res = self.compose_request('tx', 'address', address, variables) except ReadTimeout as e: sleep(3) _logger.info("Bcoin client error: %s" % e) retries += 1 else: break finally: if retries == 3: raise ClientError( "Max retries exceeded with bcoin Client") for tx in res: txs.append(self._parse_transaction(tx)) if len(txs) >= max_txs: break if len(res) == LIMIT_TX: after_txid = res[LIMIT_TX - 1]['hash'] else: break # Check which outputs are spent/unspent for this address if not after_txid: address_inputs = [(to_hexstring(inp.prev_hash), inp.output_n_int) for ti in [t.inputs for t in txs] for inp in ti if inp.address == address] for tx in txs: for to in tx.outputs: if to.address != address: continue spent = True if (tx.hash, to.output_n) in address_inputs else False txs[txs.index(tx)].outputs[to.output_n].spent = spent return txs
def getbalance(self, addresslist): balance = 0.0 from bitcoinlib.services.services import Service for address in addresslist: # First get all transactions for this address from the blockchain srv = Service(network=self.network.name, providers=['bcoin']) txs = srv.gettransactions(address, limit=25) # Fail if large number of transactions are found if not srv.complete: raise ClientError("If not all transactions known, we cannot determine utxo's. " "Increase limit or use other provider") for a in [output for outputs in [t.outputs for t in txs] for output in outputs]: if a.address == address: balance += a.value for a in [i for inputs in [t.inputs for t in txs] for i in inputs]: if a.address == address: balance -= a.value return int(balance)
def getutxos(self, addresslist): if not self.api_key: raise ClientError( "Method getutxos() is not available for CryptoID without API key" ) utxos = [] addresslist = self._addresslist_convert(addresslist) for a in addresslist: variables = {'active': a.address} res = self.compose_request('unspent', variables=variables) if len(res['unspent_outputs']) > 29: _logger.warning( "CryptoID: Large number of outputs for address %s, " "UTXO list may be incomplete" % a.address) for utxo in res['unspent_outputs']: utxos.append({ 'address': a.address_orig, 'tx_hash': utxo['tx_hash'], 'confirmations': utxo['confirmations'], 'output_n': utxo['tx_output_n'] if 'tx_output_n' in utxo else utxo['tx_ouput_n'], 'input_n': 0, 'block_height': None, 'fee': None, 'size': 0, 'value': int(utxo['value']), 'script': utxo['script'], 'date': None }) return utxos
def _parse_transaction(self, tx): t = Transaction.import_raw(tx['raw_hex'], network=self.network) if t.hash != tx['txid']: raise ClientError( "Different hash from Blocksmurfer when parsing transaction") t.block_height = None if not tx['block_height'] else tx['block_height'] t.confirmations = tx['confirmations'] t.date = None if not tx['date'] else datetime.strptime( tx['date'][:19], "%Y-%m-%dT%H:%M:%S") if t.block_height and not t.confirmations and tx[ 'status'] == 'confirmed': block_count = self.blockcount() t.confirmations = block_count - t.block_height t.status = tx['status'] t.fee = tx['fee'] for ti in t.inputs: t.inputs[ti.index_n].value = tx['inputs'][ti.index_n]['value'] for to in t.outputs: t.outputs[to.output_n].spent = tx['outputs'][to.output_n]['spent'] t.update_totals() return t
def _convert_to_transaction(self, tx): if tx['confirmations']: status = 'confirmed' else: status = 'unconfirmed' fees = None if 'fees' not in tx else int(round(float(tx['fees']) * self.units, 0)) value_in = 0 if 'valueIn' not in tx else tx['valueIn'] isCoinbase = False if 'isCoinBase' in tx and tx['isCoinBase']: value_in = tx['valueOut'] isCoinbase = True t = Transaction(locktime=tx['locktime'], version=tx['version'], network=self.network, fee=fees, size=tx['size'], hash=tx['txid'], date=datetime.fromtimestamp(tx['blocktime']), confirmations=tx['confirmations'], block_height=tx['blockheight'], block_hash=tx['blockhash'], status=status, input_total=int(round(float(value_in) * self.units, 0)), coinbase=isCoinbase, output_total=int(round(float(tx['valueOut']) * self.units, 0))) for ti in tx['vin']: # sequence = struct.pack('<L', ti['sequence']) if isCoinbase: t.add_input(prev_hash=32 * b'\0', output_n=4*b'\xff', unlocking_script=ti['coinbase'], index_n=ti['n'], script_type='coinbase', sequence=ti['sequence']) else: value = int(round(float(ti['value']) * self.units, 0)) if not ti['scriptSig']['hex']: raise ClientError("Missing unlocking script in BlockExplorer Input. Possible reason: Segwit is not " "supported") t.add_input(prev_hash=ti['txid'], output_n=ti['vout'], unlocking_script=ti['scriptSig']['hex'], index_n=ti['n'], value=value, sequence=ti['sequence'], double_spend=False if ti['doubleSpentTxID'] is None else ti['doubleSpentTxID']) for to in tx['vout']: value = int(round(float(to['value']) * self.units, 0)) address = '' try: address = to['scriptPubKey']['addresses'][0] except (ValueError, KeyError): pass t.add_output(value=value, address=address, lock_script=to['scriptPubKey']['hex'], spent=True if to['spentTxId'] else False, output_n=to['n']) return t
def gettransactions(self, address, after_txid='', max_txs=MAX_TRANSACTIONS): txs = [] txids = [] skip = 0 total = 1 while total > skip: variables = {'limit': LIMIT_TX, 'skip': skip} res = self.compose_request('address', address, 'tx', variables) for tx in res['transactions']: if tx['id'] not in txids: txids.insert(0, tx['id']) total = res['total'] if total > 2000: raise ClientError( "BitGoClient: Transactions list limit exceeded > 2000") skip = res['start'] + res['count'] if after_txid: txids = txids[txids.index(after_txid) + 1:] for txid in txids[:max_txs]: txs.append(self.gettransaction(txid)) return txs