UNPKG

@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
"use strict"; 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