UNPKG

crypto-janitor

Version:

The Crypto Janitor provides a simple interface for fetching and cleaning crypto account data.

188 lines (179 loc) 7.41 kB
/* eslint-disable max-len */ import { CcxtConnection, Transaction } from "../target.types"; import * as _ from "lodash"; /** * A Coinbase Implementation of ccxtConnection * @todo Properly parse ledger transaction and merge them with others */ export default class Coinbase extends CcxtConnection { accounts: any; /** * @description Create Coinbase instance * @param {any} creds - API creds of exchange */ constructor(creds: any) { super("coinbase", creds, { rateLimit: 100, requireSymbols: true, requireUsdValuation: false, }); } /** * @description Get corresponding coinbase account ID from symbol * @param {string} symbol - Currency symbol * @return {string} Account ID */ _getAccountIdFromSymbol(symbol: string): string { const matches: Array<any> = this.accounts.filter( (x: any) => x.currency.code === symbol ); const accountId: string = matches[0].id; return accountId; } /** * @description Format orders * @param {any} entry - Unformatted coinbase transaction * @param {string} type - Type of transaction * @return {Transaction} Formatted transaction */ _formatLedgerEntry(entry: any, type: string): Transaction { const feeQty: number = entry.network && entry.network.transaction_fee ? parseFloat(entry.network.transaction_fee.amount) : 0; const feeCurrency: string = feeQty > 0 ? entry.network.transaction_fee.currency : "USD"; const nativeTotal: number = Math.abs( parseFloat(entry.native_amount.amount) ); let baseQuantity: number = Math.abs(parseFloat(entry.amount.amount)); const price: number = nativeTotal / baseQuantity; baseQuantity = entry.amount.currency === feeCurrency ? baseQuantity - feeQty : baseQuantity; const feeUsdPrice: number = feeQty > 0 ? price : 1; const feeTotal: number = feeQty * feeUsdPrice; const subTotal: number = nativeTotal - feeTotal; const formatted: Transaction = { id: entry.id, timestamp: new Date(entry.created_at), type: type, baseCurrency: entry.amount.currency, baseQuantity: baseQuantity, baseUsdPrice: subTotal / baseQuantity, feeCurrency: feeCurrency, feeQuantity: feeQty, feeUsdPrice: feeUsdPrice, feeTotal: feeTotal, subTotal: subTotal, total: nativeTotal, }; return formatted; } /** * @description Initialize coinbase and get active accounts * @override ccxtConnection.initialize * @param {boolean} forceReload - (Optional) Additional paramaters * @return {Promise<void>} */ async initialize(forceReload: boolean = false): Promise<void> { await super.initialize(forceReload); this.accounts = this.balances.info.data.filter( (account: any) => account.created_at !== account.updated_at ); // active account if created_at and updated_at dont match this.symbols = this.accounts.map((x: any) => x.currency.code); } /** * @description Fetch all account transactions (withdrawals, deposits, and orders) * @todo Paginate requests rather than just set max limit of 100 * @override ccxtConnection.getLedger * @param {string} symbol Currency symbol * @param {number} since (Optional) Timestamp to get transactions since * @param {number} limit (Optional) Max number of entries per request * @return {Promise<any>} Array of withdrawal objects */ async getLedger( symbol?: string, since?: number, limit: number = 100 ): Promise<any> { const excludeTypes = ["buy", "sell", "fiat_deposit", "fiat_withdrawal"]; const ledger = await super.getLedger(symbol, since, limit); const rawEntries = ledger .map((x: any) => x.info) .filter((x: any) => !excludeTypes.includes(x.type)); const parsedEntries = rawEntries.map((entry: any) => { if (entry.type === "staking-reward") { return this._formatLedgerEntry(entry, "interest-in-stake"); // staking interest } else if (entry.type === "interest") { return this._formatLedgerEntry(entry, "interest-in-account"); // account interest } else if ( entry.hasOwnProperty("from") && entry.type === "send" && entry.network.status === "off_blockchain" ) { return this._formatLedgerEntry(entry, "reward"); // reward from coinbase earn } else if ( entry.hasOwnProperty("to") || entry.type === "pro_deposit" ) { return this._formatLedgerEntry(entry, "send"); // crypto was sent } else if ( entry.hasOwnProperty("from") || entry.type === "pro_withdrawal" ) { return this._formatLedgerEntry(entry, "receive"); // crypto was received } else if (entry.type === "exchange_deposit") { return this._formatLedgerEntry(entry, "withdrawal"); // crypto was withdrawn } else if (entry.type === "exchange_withdrawal") { return this._formatLedgerEntry(entry, "deposit"); // crypto was deposited } else { console.log(entry.type, entry.created_at); } return null; }); return parsedEntries; } /** * @description Fetch Coinbase Orders * @override ccxtConnection.getOrders * @param {string} symbol Currency symbol * @param {number} since (Optional) Timestamp to get transactions since * @return {Array<any>} Array of order objects */ async getOrders(symbol: string, since?: number): Promise<Array<any>> { const accountId = this._getAccountIdFromSymbol(symbol); let trades: Array<any> = await Promise.all([ this.connection.fetchMyBuys(symbol, since, undefined, { accountId, }), this.connection.fetchMySells(symbol, since, undefined, { accountId, }), ]); trades = _.flatten(trades); return trades.map((trade: any) => this._formatOrder(trade)); } /** * @description Fetch Fetch Coinbase transactions * @override ccxtConnection.getTransactions * @param {string} symbol Currency symbol * @param {number} since (Optional) Timestamp to get transactions since * @return {any} Transactions object */ async getTransactions(symbol: string, since?: number): Promise<any> { const reqs = [ this.getOrders(symbol, since), this.getLedger(symbol, since), ]; if (symbol === "USD") { // only get withdrawals and deposits for fiat reqs.push(this.getWithdrawals(symbol, "withdrawal", since)); reqs.push(this.getDeposits(symbol, "deposit", since)); } let results = await Promise.all(reqs); results = _.flatten(results); return _.sortBy(results, "timestamp"); } }