@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.
174 lines • 6.89 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.coinbaseEndpoints = exports.coinbaseAPIBaseUrl = exports.CoinbaseClient = void 0;
const axios_1 = __importDefault(require("axios"));
const jsonwebtoken_1 = require("jsonwebtoken");
const url_1 = require("url");
const BASE_URL = 'https://api.coinbase.com';
exports.coinbaseAPIBaseUrl = BASE_URL;
const ENDPOINTS = {
accounts: 'api/v3/brokerage/accounts',
};
exports.coinbaseEndpoints = ENDPOINTS;
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.
*/
class CoinbaseClient {
/**
* Create the client instance with baseUrl and scopes
*/
constructor(config) {
this.config = config;
}
/**
* Execute a request and handle the response
*/
request(method, path, query = {}, data = '') {
return __awaiter(this, void 0, void 0, function* () {
const url = new url_1.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 = yield (0, axios_1.default)(requestConfig);
}
catch (err) {
// re-throw normal errors
if (!axios_1.default.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 = yield this.request(method, ENDPOINTS.accounts, Object.assign(Object.assign({}, 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 = (0, jsonwebtoken_1.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
*/
getAccounts() {
return __awaiter(this, void 0, void 0, function* () {
const query = this.config.testPagination ? Object.assign(Object.assign({}, QUERY_PARAMS.accounts), { limit: 1 }) : QUERY_PARAMS.accounts;
const accounts = yield this.request('GET', ENDPOINTS.accounts, query);
if (!accounts) {
throw new Error('Could not fetch accounts data');
}
return accounts;
});
}
/**
* Returns current coinbase holdings
*/
getBalances() {
return __awaiter(this, void 0, void 0, function* () {
const accounts = yield 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;
});
}
}
exports.CoinbaseClient = CoinbaseClient;
//# sourceMappingURL=CoinbaseClient.js.map