def query_btc_account_balance(account: BTCAddress) -> FVal: """Queries blockchain.info for the balance of account May raise: - RemotError if there is a problem querying blockchain.info or blockcypher """ try: if account.lower()[0:3] == 'bc1': url = f'https://api.blockcypher.com/v1/btc/main/addrs/{account.lower()}/balance' response_data = request_get(url=url) if 'balance' not in response_data: raise RemoteError(f'Unexpected blockcypher balance response: {response_data}') btc_resp = response_data['balance'] else: btc_resp = request_get_direct( url='https://blockchain.info/q/addressbalance/%s' % account, handle_429=True, # If we get a 429 then their docs suggest 10 seconds # https://blockchain.info/q backoff_in_seconds=10, ) except (requests.exceptions.ConnectionError, UnableToDecryptRemoteData) as e: raise RemoteError(f'bitcoin external API request failed due to {str(e)}') return satoshis_to_btc(FVal(btc_resp)) # result is in satoshis
def _check_blockcypher_for_transactions( accounts: List[BTCAddress], ) -> Dict[BTCAddress, Tuple[bool, FVal]]: have_transactions = {} new_accounts = _prepare_blockcypher_accounts(accounts) # blockcypher's batching takes up as many api queries as the batch, # and the api rate limit is 3 requests per second. So we should make # sure each batch is of max size 3 # https://www.blockcypher.com/dev/bitcoin/#batching batches = [new_accounts[x:x + 3] for x in range(0, len(new_accounts), 3)] total_idx = 0 for batch in batches: params = ';'.join(batch) url = f'https://api.blockcypher.com/v1/btc/main/addrs/{params}/balance' response_data = request_get(url=url, handle_429=True, backoff_in_seconds=4) if isinstance(response_data, dict): # If only one account was requested put it in a list so the # rest of the code works response_data = [response_data] for idx, entry in enumerate(response_data): balance = satoshis_to_btc(FVal(entry['final_balance'])) # we don't use the returned address as it may be lowercased have_transactions[accounts[total_idx + idx]] = (entry['final_n_tx'] != 0, balance) total_idx += len(batch) return have_transactions
def query_btc_accounts_balances( accounts: List[BTCAddress]) -> Dict[BTCAddress, FVal]: """Queries blockchain.info for the balance of account May raise: - RemotError if there is a problem querying blockchain.info or blockcypher """ source = 'blockchain.info' balances = {} try: if any(account.lower()[0:3] == 'bc1' for account in accounts): # if 1 account is bech32 we have to query blockcypher. blockchaininfo won't work source = 'blockcypher.com' # the bech32 accounts have to be given lowercase to the # blockcypher query. No idea why. new_accounts = [] for x in accounts: lowered = x.lower() if lowered[0:3] == 'bc1': new_accounts.append(lowered) else: new_accounts.append(x) # blockcypher's batching takes up as many api queries as the batch, # and the api rate limit is 3 requests per second. So we should make # sure each batch is of max size 3 # https://www.blockcypher.com/dev/bitcoin/#batching batches = [ new_accounts[x:x + 3] for x in range(0, len(new_accounts), 3) ] total_idx = 0 for batch in batches: params = ';'.join(batch) url = f'https://api.blockcypher.com/v1/btc/main/addrs/{params}/balance' response_data = request_get(url=url, handle_429=True, backoff_in_seconds=4) if isinstance(response_data, dict): # If only one account was requested put it in a list so the # rest of the code works response_data = [response_data] for idx, entry in enumerate(response_data): # we don't use the returned address as it may be lowercased balances[accounts[total_idx + idx]] = satoshis_to_btc( FVal(entry['final_balance']), ) total_idx += len(batch) else: params = '|'.join(accounts) btc_resp = request_get_dict( url=f'https://blockchain.info/multiaddr?active={params}', handle_429=True, # If we get a 429 then their docs suggest 10 seconds # https://blockchain.info/q backoff_in_seconds=10, ) for idx, entry in enumerate(btc_resp['addresses']): balances[accounts[idx]] = satoshis_to_btc( FVal(entry['final_balance'])) except (requests.exceptions.ConnectionError, UnableToDecryptRemoteData) as e: raise RemoteError( f'bitcoin external API request failed due to {str(e)}') except KeyError as e: raise RemoteError( f'Malformed response when querying bitcoin blockchain via {source}.' f'Did not find key {e}', ) return balances