'Coinbase API BTC account missing
thanks for coming to my aid.
In the course of using the coinbase API, when I callclient.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 |