UNPKG

ccxws

Version:

Websocket client for 37 cryptocurrency exchanges

373 lines 12.7 kB
"use strict"; /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SingleCexClient = exports.CexClient = void 0; const crypto = __importStar(require("crypto")); const BasicClient_1 = require("../BasicClient"); const BasicMultiClient_1 = require("../BasicMultiClient"); const Candle_1 = require("../Candle"); const CandlePeriod_1 = require("../CandlePeriod"); const Level2Point_1 = require("../Level2Point"); const Level2Snapshots_1 = require("../Level2Snapshots"); const NotImplementedFn_1 = require("../NotImplementedFn"); const Ticker_1 = require("../Ticker"); const Trade_1 = require("../Trade"); const Watcher_1 = require("../Watcher"); function createSignature(timestamp, apiKey, apiSecret) { const hmac = crypto.createHmac("sha256", apiSecret); hmac.update(timestamp.toString() + apiKey); return hmac.digest("hex"); } function createAuthToken(apiKey, apiSecret) { const timestamp = Math.floor(Date.now() / 1000); return { key: apiKey, signature: createSignature(timestamp, apiKey, apiSecret), timestamp, }; } const multiplier = { ADA: 1e6, ATOM: 1e6, BAT: 1e6, GAS: 1e8, NEO: 1e6, ONT: 1e6, ONG: 1e6, MATIC: 1e6, LINK: 1e6, XTZ: 1e6, BCH: 1e8, BTC: 1e8, BTG: 1e8, BTT: 1e6, DASH: 1e8, ETH: 1e6, GUSD: 1e2, LTC: 1e8, MHC: 1e6, OMG: 1e6, TRX: 1e6, XLM: 1e7, XRP: 1e6, ZEC: 1e8, }; function formatAmount(amount, symbol) { return (parseInt(amount) / multiplier[symbol]).toFixed(8); } class CexClient extends BasicMultiClient_1.BasicMultiClient { /** * Creates a new CEX.io client using the supplied credentials */ constructor(options) { super(); this._clients = new Map(); this.name = "CEX_MULTI"; this.options = options; this.hasTickers = true; this.hasTrades = true; this.hasCandles = true; this.hasLevel2Snapshots = true; this.candlePeriod = CandlePeriod_1.CandlePeriod._1m; } _createBasicClient(clientArgs) { return new SingleCexClient({ ...this.options, market: clientArgs.market, parent: this, }); } } exports.CexClient = CexClient; class SingleCexClient extends BasicClient_1.BasicClient { constructor({ wssPath = "wss://ws.cex.io/ws", watcherMs = 900 * 1000, apiKey, apiSecret, market, parent, }) { super(wssPath, "CEX", undefined, watcherMs); this._sendSubLevel2Updates = NotImplementedFn_1.NotImplementedFn; this._sendUnsubLevel2Updates = NotImplementedFn_1.NotImplementedFn; this._sendSubLevel3Snapshots = NotImplementedFn_1.NotImplementedFn; this._sendUnsubLevel3Snapshots = NotImplementedFn_1.NotImplementedFn; this._sendSubLevel3Updates = NotImplementedFn_1.NotImplementedFn; this._sendUnsubLevel3Updates = NotImplementedFn_1.NotImplementedFn; this._watcher = new Watcher_1.Watcher(this, watcherMs); this.auth = { apiKey, apiSecret }; this.market = market; this.hasTickers = true; this.hasTrades = true; this.hasCandles = true; this.hasLevel2Snapshots = true; this.authorized = false; this.parent = parent; } get candlePeriod() { return this.parent.candlePeriod; } /** * This method is fired anytime the socket is opened, whether * the first time, or any subsequent reconnects. * Since this is an authenticated feed, we first send an authenticate * request, and the normal subscriptions happen after authentication has * completed in the _onAuthorized method. */ _onConnected() { this._sendAuthorizeRequest(); } /** * Trigger after an authorization packet has been successfully received. * This code triggers the usual _onConnected code afterwards. */ _onAuthorized() { this.authorized = true; this.emit("authorized"); super._onConnected(); } _sendAuthorizeRequest() { this._wss.send(JSON.stringify({ e: "auth", auth: createAuthToken(this.auth.apiKey, this.auth.apiSecret), })); } _sendPong() { if (this._wss) { this._wss.send(JSON.stringify({ e: "pong" })); } } _sendSubTicker() { if (!this.authorized) return; this._wss.send(JSON.stringify({ e: "subscribe", rooms: ["tickers"], })); } _sendUnsubTicker() { // } _sendSubTrades(remote_id) { if (!this.authorized) return; this._wss.send(JSON.stringify({ e: "subscribe", rooms: [`pair-${remote_id.replace("/", "-")}`], })); } _sendUnsubTrades() { // } _sendSubCandles(remote_id) { if (!this.authorized) return; this._wss.send(JSON.stringify({ e: "init-ohlcv", i: candlePeriod(this.candlePeriod), rooms: [`pair-${remote_id.replace("/", "-")}`], })); } _sendUnsubCandles() { // } _sendSubLevel2Snapshots(remote_id) { if (!this.authorized) return; this._wss.send(JSON.stringify({ e: "subscribe", rooms: [`pair-${remote_id.replace("/", "-")}`], })); } _sendUnsubLevel2Snapshots() { // } _onMessage(raw) { const message = JSON.parse(raw); const { e, data } = message; if (e === "ping") { this._sendPong(); return; } if (e === "subscribe") { if (message.error) { throw new Error(`CEX error: ${JSON.stringify(message)}`); } } if (e === "auth") { if (data.ok === "ok") { this._onAuthorized(); } else { throw new Error("Authentication error"); } return; } if (e === "tick") { // {"e":"tick","data":{"symbol1":"BTC","symbol2":"USD","price":"4244.4","open24":"4248.4","volume":"935.58669239"}} const marketId = `${data.symbol1}/${data.symbol2}`; const market = this._tickerSubs.get(marketId); if (!market) return; const ticker = this._constructTicker(data, market); this.emit("ticker", ticker, market); return; } if (e === "md") { const marketId = data.pair.replace(":", "/"); const market = this._level2SnapshotSubs.get(marketId); if (!market) return; const result = this._constructevel2Snapshot(data, market); this.emit("l2snapshot", result, market); return; } if (e === "history") { const marketId = this.market.id; const market = this._tradeSubs.get(marketId); if (!market) return; // sell/buy:timestamp_ms:amount:price:transaction_id for (const rawTrade of data.reverse()) { const tradeData = rawTrade.split(":"); const trade = this._constructTrade(tradeData, market); this.emit("trade", trade, market); } return; } if (e === "history-update") { const marketId = this.market.id; const market = this._tradeSubs.get(marketId); if (this._tradeSubs.has(marketId)) { for (const rawTrade of data) { const trade = this._constructTrade(rawTrade, market); this.emit("trade", trade, market); } return; } } // ohlcv{period} - why the F*** are there three styles of candles??? if (e === `ohlcv${candlePeriod(this.candlePeriod)}`) { const marketId = message.data.pair.replace(":", "/"); const market = this._candleSubs.get(marketId); if (!market) return; const candle = this._constructCandle(message.data); this.emit("candle", candle, market); return; } } _constructTicker(data, market) { // {"e":"tick","data":{"symbol1":"BTC","symbol2":"USD","price":"4244.4","open24":"4248.4","volume":"935.58669239"}} const { open24, price, volume } = data; const change = parseFloat(price) - parseFloat(open24); const changePercent = open24 !== 0 ? ((parseFloat(price) - parseFloat(open24)) / parseFloat(open24)) * 100 : 0; return new Ticker_1.Ticker({ exchange: this.name, base: market.base, quote: market.quote, timestamp: Date.now(), last: price, open: open24, volume: volume, change: change.toFixed(8), changePercent: changePercent.toFixed(8), }); } _constructevel2Snapshot(msg, market) { const asks = msg.sell.map(p => new Level2Point_1.Level2Point(p[0].toFixed(8), formatAmount(p[1], market.base))); const bids = msg.buy.map(p => new Level2Point_1.Level2Point(p[0].toFixed(8), formatAmount(p[1], market.base))); return new Level2Snapshots_1.Level2Snapshot({ exchange: this.name, base: market.base, quote: market.quote, sequenceId: msg.id, asks, bids, }); } _constructTrade(data, market) { //["buy","1543967891439","4110282","3928.1","9437977"] //format: sell/buy, timestamp_ms, amount, price, transaction_id const [side, timestamp_ms, amount, price, tradeId] = data; return new Trade_1.Trade({ exchange: this.name, base: market.base, quote: market.quote, tradeId: tradeId, unix: parseInt(timestamp_ms), side: side, price: price, amount: formatAmount(amount, market.base), rawAmount: amount, }); } /** { e: 'ohlcv1m', data: { pair: 'BTC:USD', time: '1597261140', o: '11566.8', h: '11566.8', l: '11566.8', c: '11566.8', v: 664142, d: 664142 } } */ _constructCandle(data) { const ms = Number(data.time) * 1000; return new Candle_1.Candle(ms, data.o, data.h, data.l, data.c, data.v.toFixed(8)); } } exports.SingleCexClient = SingleCexClient; function candlePeriod(period) { switch (period) { case CandlePeriod_1.CandlePeriod._1m: return "1m"; case CandlePeriod_1.CandlePeriod._3m: return "3m"; case CandlePeriod_1.CandlePeriod._5m: return "5m"; case CandlePeriod_1.CandlePeriod._15m: return "15m"; case CandlePeriod_1.CandlePeriod._30m: return "30m"; case CandlePeriod_1.CandlePeriod._1h: return "1h"; case CandlePeriod_1.CandlePeriod._2h: return "2h"; case CandlePeriod_1.CandlePeriod._4h: return "4h"; case CandlePeriod_1.CandlePeriod._6h: return "6h"; case CandlePeriod_1.CandlePeriod._12h: return "12h"; case CandlePeriod_1.CandlePeriod._1d: return "1d"; case CandlePeriod_1.CandlePeriod._3d: return "3d"; case CandlePeriod_1.CandlePeriod._1w: return "1w"; } } //# sourceMappingURL=CexClient.js.map