Beispiel #1
0
    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
Beispiel #2
0
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
Beispiel #3
0
    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