Source code for bitcoinlib.services.bitgo

# -*- coding: utf-8 -*-
#
#    BitcoinLib - Python Cryptocurrency Library
#    BitGo Client
#    © 2017-2019 July - 1200 Web Development <http://1200wd.com/>
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

import logging
from datetime import datetime
from bitcoinlib.main import MAX_TRANSACTIONS
from bitcoinlib.services.baseclient import BaseClient, ClientError
from bitcoinlib.transactions import Transaction

_logger = logging.getLogger(__name__)

PROVIDERNAME = 'bitgo'
LIMIT_TX = 49


[docs]class BitGoClient(BaseClient): def __init__(self, network, base_url, denominator, *args): super(self.__class__, self).__init__(network, PROVIDERNAME, base_url, denominator, *args)
[docs] def compose_request(self, category, data, cmd='', variables=None, method='get'): if data: data = '/' + data url_path = category + data if cmd != '': url_path += '/' + cmd return self.request(url_path, variables, method=method)
[docs] def getbalance(self, addresslist): balance = 0 for address in addresslist: res = self.compose_request('address', address) balance += res['balance'] return balance
[docs] def getutxos(self, address, after_txid='', limit=MAX_TRANSACTIONS): utxos = [] skip = 0 total = 1 while total > skip: variables = {'limit': 100, 'skip': skip} res = self.compose_request('address', address, 'unspents', variables) for utxo in res['unspents'][::-1]: if utxo['tx_hash'] == after_txid: break utxos.append( { 'address': utxo['address'], 'tx_hash': utxo['tx_hash'], 'confirmations': utxo['confirmations'], 'output_n': utxo['tx_output_n'], 'input_n': 0, 'block_height': utxo['blockHeight'], 'fee': None, 'size': 0, 'value': int(round(utxo['value'] * self.units, 0)), 'script': utxo['script'], 'date': datetime.strptime(utxo['date'], "%Y-%m-%dT%H:%M:%S.%fZ") } ) total = res['total'] skip = res['start'] + res['count'] if skip > 2000: _logger.info("BitGoClient: UTXO's list has been truncated, list is incomplete") break return utxos[::-1][:limit]
# RAW TRANSACTION DOES NOT CONTAIN CORRECT RAW TRANSACTION (MISSING SIGS) # def gettransaction(self, txid): # tx = self.compose_request('tx', txid) # t = Transaction.import_raw(tx['hex'], network=self.network) # t.status = 'unconfirmed' # t.date = None # if tx['confirmations']: # t.status = 'confirmed' # 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 = to_bytes(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 and not t.coinbase: # 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.info("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 # RAW TRANSACTION DOES NOT CONTAIN CORRECT RAW TRANSACTION (MISSING SIGS) # def gettransactions(self, address, after_txid='', limit=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 len(txids) > limit: # break # if after_txid: # txids = txids[txids.index(after_txid) + 1:] # for txid in txids[:limit]: # txs.append(self.gettransaction(txid)) # return txs # RAW TRANSACTION DOES NOT CONTAIN CORRECT RAW TRANSACTION (MISSING SIGS) # def getrawtransaction(self, txid): # tx = self.compose_request('tx', txid) # t = Transaction.import_raw(tx['hex'], 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['hex'] # def sendrawtransaction
[docs] def estimatefee(self, blocks): res = self.compose_request('tx', 'fee', variables={'numBlocks': blocks}) return res['feePerKb']
[docs] def blockcount(self): return self.compose_request('block', 'latest')['height']
# def mempool # def getblock(self, blockid, parse_transactions, page, limit): # bd = self.compose_request('block', str(blockid)) # if parse_transactions: # txs = [] # for txid in bd['transactions'][(page-1)*limit:page*limit]: # try: # txs.append(self.gettransaction(txid)) # except Exception as e: # _logger.error("Could not parse tx %s with error %s" % (txid, e)) # else: # txs = bd['transactions'] # # block = { # 'bits': None, # 'depth': None, # 'hash': bd['id'], # 'height': bd['height'], # 'merkle_root': bd['merkleRoot'], # 'nonce': bd['nonce'], # 'prev_block': bd['previous'], # 'time': datetime.strptime(bd['date'], "%Y-%m-%dT%H:%M:%S.%fZ").replace(microsecond=0), # 'total_txs': len(bd['transactions']), # 'txs': txs, # 'version': bd['version'], # 'page': page, # 'pages': int(len(bd['transactions']) // limit) + (len(bd['transactions']) % limit > 0), # 'limit': limit # } # return block # def isspent(self, txid, index): # def getinfo(self):