'Coinbase API BTC account missing

thanks for coming to my aid. In the course of using the coinbase API, when I call
client.getAccounts({}, function(error, accounts){
console.log('accounts', accounts) })
I notice a number of accounts including ethereum, litecoin, but no bitcoin (BTC) Can someone help figure out why ? Thanks so much!



Solution 1:[1]

For some reason bitcoin is not in the main account that comes back. you can use this call to get specific accounts.

btcAccount = client.get_account('BTC')

Solution 2:[2]

TL;DR

Add rxjs to your project with yarn add rxjs or npm install rxjs and then do this:

import { EMPTY, from, reduce, expand } from 'rxjs';

// Promisify: Wrap API call in a promise
const getAccountsAsync = (options) => {
    return new Promise((resolve, reject) => {
        client.getAccounts(options, (err, accounts, pagination) => {
            if (err) return reject(err);
            resolve({accounts, pagination});
        })
    })
}

// Number of accounts to fetch per request
const paginationLimit = 100;

from(getAccountsAsync({limit: paginationLimit})).pipe(
    // As long as there is a next_uri set, continue recursion
    expand(res => res.pagination.next_uri ? 
        getAccountsAsync({
            limit: paginationLimit,
            next_uri: res.pagination.next_uri, 
            starting_after: res.pagination.next_starting_after
        }) : 
        EMPTY
    ),
    // Filter and concatenate the result of all API calls
    reduce((acc, current) => acc.concat(current.accounts.filter(account => account.balance.amount > 0)), [])
).subscribe(allAccounts => console.log(allAccounts));

Explanation

The reason why only the first 25 accounts get returned is because the coinbase API uses pagination and 25 is the default page size (see official docs).

When you call getAccounts() you actually get three return values back (even though the API only specifies two):

client.getAccounts({}, (err, accounts, pagination) => {});

The pagination object looks something like this:

{
  ending_before: null,
  starting_after: null,
  previous_ending_before: null,
  next_starting_after: 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX',
  limit: 25,
  order: 'desc',
  previous_uri: null,
  next_uri: '/v2/accounts?limit=25&starting_after=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
}

You can increase the limit like this:

client.getAccounts({limit: 100}, (err, accounts, pagination) => {});

However, there are currently 175 accounts (currencies) in coinbase, and the maximum pagination limit we can provide is 100.

So, in order to fetch the rest of the pages, we have to make additional API calls, providing the starting_after and next_uri as options, like this:

client.getAccounts({
  limit: 100,
  next_uri: '/v2/accounts?limit=100&starting_after=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX',
  starting_after: 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
}, (err, accounts, pagination) => {});

Side Note on next_uri

It seems redundant that we have to provide next_uri when there is already starting_after. In fact, the docs do not mention that we have to provide next_uri. However, if I omit this value I get the following error:

ClientError [AuthenticationError]: invalid signature

After digging through the source code I found that before checking if starting_after is set, they check if next_uri is set. So, apparently we do have to provide this value.

Solution 1 - Recursive Promises

To print all of your accounts (accounts where balance.amount > 0), we can first wrap the API call in a promise and then use recursion. A possible solution would look like this:

// Promisify: Wrap API call in a promise
const getAccountsAsync = (options) => {
    return new Promise((resolve, reject) => {
        client.getAccounts(options, (err, accounts, pagination) => {
            if (err) return reject(err);
            resolve({accounts, pagination});
        })
    })
}

// Fetch all accounts recursively 
const fetchAllAccounts = (nextUri, lastFetchedId) => {
    getAccountsAsync({limit: 100, next_uri: nextUri, starting_after: lastFetchedId})
        .then(res => {
            // Print your accounts from current batch
            res.accounts.forEach(account => {
                if (account.balance.amount > 0) {
                    console.log(account)     
                }
            });
            
            // Terminate recursion when next_uri is empty
            if (res.pagination.next_uri != null) {
                // Call next batch of 100 accounts, starting after lastFetchedId
                fetchAllAccounts(res.pagination.next_uri, res.pagination.next_starting_after)
            }
        })
        .catch(err => console.log(err));
};

// Initial call
fetchAllAccounts(null, null);

Unfortunately, using this approach the accounts are spread across multiple callbacks. We cannot easily work with them. If you want to do more with your accounts than just printing them to the console you can use RxJS.

Solution 2 - Using RxJS

The more elegant solution is to use the expand function from RxJS. Again, we have to wrap the API call in a promise first.

Add rxjs to your project with yarn add rxjs or npm install rxjs and then do this:

import { EMPTY, from, reduce, expand } from 'rxjs';

// Promisify: Wrap API call in a promise
const getAccountsAsync = (options) => {
    return new Promise((resolve, reject) => {
        client.getAccounts(options, (err, accounts, pagination) => {
            if (err) return reject(err);
            resolve({accounts, pagination});
        })
    })
}

// Number of accounts to fetch per request
const paginationLimit = 100;

from(getAccountsAsync({limit: paginationLimit})).pipe(
    // As long as there is a next_uri set, continue recursion
    expand(res => res.pagination.next_uri ? 
        getAccountsAsync({
            limit: paginationLimit,
            next_uri: res.pagination.next_uri, 
            starting_after: res.pagination.next_starting_after
        }) : 
        EMPTY
    ),
    // Filter and concatenate the result of all API calls
    reduce((acc, current) => acc.concat(current.accounts.filter(account => account.balance.amount > 0)), [])
).subscribe(allAccounts => console.log(allAccounts));

The advantage to this approach is, we can concatenate the results from all API calls and combine them to a single array.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 David Blumer
Solution 2