@lunch-money/coinbase-to-lunch-money
Version:
A wrapper around the coinbase API for enabling Lunch Money to gather information about a user's account.
152 lines • 5.24 kB
JavaScript
import axios from 'axios';
import { sign } from 'jsonwebtoken';
import { URL } from 'url';
const BASE_URL = 'https://api.coinbase.com';
const ENDPOINTS = {
accounts: 'api/v3/brokerage/accounts',
};
const QUERY_PARAMS = {
accounts: { limit: 100 },
};
/**
* Coinbase Client
*
* Coinbase doesn't have an official node client, so a basic one is provided.
*
* There are two authentication methods: API key and OAuth2. Coinbase
* discourages the use of API Keys except when writing your own software, so
* OAuth2 is preferred.
*/
export class CoinbaseClient {
config;
/**
* Create the client instance with baseUrl and scopes
*/
constructor(config) {
this.config = config;
}
/**
* Execute a request and handle the response
*/
async request(method, path, query = {}, data = '') {
const url = new URL(path, BASE_URL).href;
const sJWT = this.generateSignedJwt(method, url);
const requestConfig = {
url,
params: query,
method,
data,
headers: {
Authorization: `Bearer ${sJWT}`,
},
};
// Make the request
let response;
try {
response = await axios(requestConfig);
}
catch (err) {
// re-throw normal errors
if (!axios.isAxiosError(err)) {
throw err;
}
// return axios errors
// as endpoints do return content even when triggering status errors
response = err.response;
}
// Process response
if (!response) {
throw new Error('Invalid response');
}
if (typeof response.data === 'undefined') {
throw new Error(`Coinbase API responded with no data`);
}
const result = response.data;
if (typeof result === 'undefined') {
throw new Error(`Coinbase API responded with no data`);
}
// Process results based on the type of request
if (path == ENDPOINTS.accounts) {
// Loop through pagination to fetch all results
// @see https://docs.cdp.coinbase.com/advanced-trade/reference/retailbrokerageapi_getaccounts
if (typeof result.has_next && result.cursor) {
// If there is another page of resources after this one, request it and
// append to our results. This will act recursively until all pages have
// been returned.
const nextResult = await this.request(method, ENDPOINTS.accounts, { ...query, cursor: result.cursor });
result.accounts = result.accounts.concat(nextResult || []);
}
return result.accounts;
}
else {
throw new Error(`Invalid path: ${path}. Path must match one of the defined endpoints.`);
}
}
/**
* Generate a JWT for the current request
*/
generateSignedJwt(method, url) {
if (this.config.mockApiResponseTest) {
return '';
}
const key_name = this.config.name;
const key_secret = this.config.privateKey;
const strippedUrl = url.replace(/^https?:\/\//, '');
const uri = `${method} ${strippedUrl}`;
const algorithm = 'ES256';
const payload = {
iss: 'cdp',
nbf: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 120,
sub: key_name,
uri,
};
const options = {
algorithm,
header: {
kid: key_name,
alg: algorithm,
},
};
try {
const token = sign(payload, key_secret, options);
return token;
}
catch (e) {
console.log(`Failed to get signed token with API credentials: ${e.message}`);
throw new Error('Unable to access Coinbase API with supplied credentials!');
}
}
/**
* Returns current coinbase accounts
*
* @see https://docs.cdp.coinbase.com/advanced-trade/reference/retailbrokerageapi_getaccounts
*/
async getAccounts() {
const query = this.config.testPagination ? { ...QUERY_PARAMS.accounts, limit: 1 } : QUERY_PARAMS.accounts;
const accounts = await this.request('GET', ENDPOINTS.accounts, query);
if (!accounts) {
throw new Error('Could not fetch accounts data');
}
return accounts;
}
/**
* Returns current coinbase holdings
*/
async getBalances() {
const accounts = await this.getAccounts();
const balances = accounts
// .filter((account: { available_balance: { value: string; currency: string } }) => {
// return parseFloat(account.available_balance.value) > 0;
// })
.map((account) => {
return {
asset: account.available_balance.currency,
amount: account.available_balance.value,
};
});
return balances;
}
}
export { BASE_URL as coinbaseAPIBaseUrl, ENDPOINTS as coinbaseEndpoints };
//# sourceMappingURL=CoinbaseClient.js.map