├── .gitignore ├── LICENSE.md ├── README.md ├── doc ├── rest-client.md └── websocket-client.md ├── index.js ├── lib ├── logger.js ├── request.js ├── rest-client.js ├── utility.js └── websocket-client.js ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | !.gitkeep 2 | .DS_STORE 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | node_modules/ 14 | .npm 15 | .eslintcache 16 | .node_repl_history 17 | *.tgz 18 | .yarn-integrity 19 | .env 20 | .env.test 21 | .cache 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2019 Stefan Aebischer 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED @pxtrn/bybit-api 2 | 3 | **This package has been deprecated and is no longer maintained. Please use [bybit-api](https://github.com/tiagosiebler/bybit-api) from @tiagosiebler** 4 | 5 | An unofficial node.js lowlevel wrapper for the Bybit Cryptocurrency Derivative 6 | exchange API. 7 | 8 | 9 | ## Installation 10 | 11 | `npm install --save @pxtrn/bybit-api` 12 | 13 | 14 | ## Usage 15 | 16 | Create API credentials at bybit (obviously you need to be logged in): 17 | - [Livenet](https://bybit.com/app/user/api-management) 18 | - [Testnet](https://testnet.bybit.com/app/user/api-management) 19 | 20 | 21 | ### Rest client 22 | 23 | ```js 24 | const {RestClient} = require('@pxtrn/bybit-api'); 25 | 26 | const API_KEY = 'xxx'; 27 | const PRIVATE_KEY = 'yyy'; 28 | 29 | const client = new RestClient(API_KEY, PRIVATE_KEY); 30 | 31 | client.changeUserLeverage({leverage: 4, symbol: 'ETHUSD'}) 32 | .then(result => { 33 | console.log(result); 34 | }) 35 | .catch(err => { 36 | console.error(error); 37 | }); 38 | ``` 39 | 40 | See rest client [api docs](./doc/rest-client.md) for further information. 41 | 42 | 43 | ### Websocket client 44 | 45 | ```js 46 | const {WebsocketClient} = require('@pxtrn/bybit-api'); 47 | 48 | const API_KEY = 'xxx'; 49 | const PRIVATE_KEY = 'yyy'; 50 | 51 | const ws = new WebsocketClient({key: API_KEY, secret: PRIVATE_KEY}); 52 | 53 | ws.subscribe(['position', 'execution', 'trade']); 54 | ws.subscribe('kline.BTCUSD.1m'); 55 | 56 | ws.on('open', function() { 57 | console.log('connection open'); 58 | }); 59 | 60 | ws.on('update', function(message) { 61 | console.log('update', message); 62 | }); 63 | 64 | ws.on('response', function(response) { 65 | console.log('response', response); 66 | }); 67 | 68 | ws.on('close', function() { 69 | console.log('connection closed'); 70 | }); 71 | 72 | ws.on('error', function(err) { 73 | console.error('ERR', err); 74 | }); 75 | ``` 76 | 77 | See websocket client [api docs](./doc/websocket-client.md) for further information. 78 | 79 | ### Customise Logging 80 | Pass a custom logger which supports the log methods `silly`, `debug`, `notice`, `info`, `warning` and `error`, or override methods from the default logger as desired: 81 | 82 | ```js 83 | const { RestClient, WebsocketClient, DefaultLogger } = require('@pxtrn/bybit-api'); 84 | 85 | // Disable all logging on the silly level 86 | DefaultLogger.silly = () => {}; 87 | 88 | const API_KEY = 'xxx'; 89 | const PRIVATE_KEY = 'yyy'; 90 | 91 | const ws = new WebsocketClient({key: API_KEY, secret: PRIVATE_KEY}, DefaultLogger); 92 | ``` 93 | 94 | ## Donations 95 | 96 | If this library helps you to trade better on bybit, feel free to donate a coffee, 97 | or create a bybit account using my [ref link](https://www.bybit.com/app/register?ref=j8q5l). 98 | 99 | - BTC `1Fh1158pXXudfM6ZrPJJMR7Y5SgZUz4EdF` 100 | - ETH `0x21aEdeC53ab7593b77C9558942f0c9E78131e8d7` 101 | - LTC `LNdHSVtG6UWsriMYLJR3qLdfVNKwJ6GSLF` 102 | -------------------------------------------------------------------------------- /doc/rest-client.md: -------------------------------------------------------------------------------- 1 | # Rest API 2 | 3 | 4 | ## Class: RestClient 5 | 6 | 7 | ### new RestClient([key][, secret][, livenet][, options]) 8 | - `key` {String} Bybit API Key 9 | - `secret` {String} Bybit private key 10 | - `livenet` {Boolean} If false (default), use testnet. 11 | - `options` {Object} Optional settings for custom behaviour. 12 | - `recv_window` {Number} Optional, default 5000. Increase if recv errors are seen. 13 | - `sync_interval_ms` {Number} Optional, default 3600000. Interval at which syncTime is performed. 14 | 15 | If you only use the [public endpoints](#public-endpoints) you can ommit key and secret. 16 | 17 | 18 | ### Private enpoints 19 | 20 | #### async placeActiveOrder(params) 21 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-placev2active) 22 | 23 | #### async getActiveOrder(params) 24 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-getactive) 25 | 26 | #### async cancelActiveOrder(params) 27 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-cancelv2active) 28 | 29 | #### async cancelAllActiveOrders(params) 30 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-cancelallactive) 31 | 32 | #### async replaceActiveOrder(params) 33 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-replaceactive) 34 | 35 | #### async queryActiveOrder(params) 36 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-queryactive) 37 | 38 | #### async placeConditionalOrder(params) 39 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-placecond) 40 | 41 | #### async getConditioanlOrder(params) 42 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-getcond) 43 | 44 | #### async cancelConditionalOrder(params) 45 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-cancelcond) 46 | 47 | #### async cancelAllConditionalOrders(params) 48 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-cancelallcond) 49 | 50 | #### async queryConditionalOrder(params) 51 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-querycond) 52 | 53 | #### async getUserLeverage() 54 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-getleverage) 55 | 56 | #### async changeUserLeverage(params) 57 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-changeleverage) 58 | 59 | #### async getPosition(params) 60 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-mypositionv2) 61 | 62 | #### async getPositions() 63 | *Deprecated v1 method* 64 | [See bybit documentation](https://github.com/bybit-exchange/bybit-official-api-docs/blob/master/en/rest_api.md#positionlistget) 65 | 66 | #### async changePositionMargin(params) 67 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-changemargin) 68 | 69 | #### async setTradingStop(params) 70 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-tradingstop) 71 | 72 | #### async getWalletFundRecords(params) 73 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-walletrecords) 74 | 75 | #### async getWithdrawRecords(params) 76 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-withdrawrecords) 77 | 78 | #### async getWalletBalance(params) 79 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-balance) 80 | 81 | #### async setRiskLimit(params) 82 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-setrisklimit) 83 | 84 | #### async getRiskLimitList() 85 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-getrisklimit) 86 | 87 | #### async getLastFundingRate(params) 88 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-fundingrate) 89 | 90 | #### async getMyLastFundingFee(params) 91 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-mylastfundingfee) 92 | 93 | #### async getPredictedFunding(params) 94 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-predictedfunding) 95 | 96 | #### async getTradeRecords(params) 97 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-usertraderecords) 98 | 99 | ### Public enpoints 100 | 101 | #### async getOrderBook(params) 102 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-orderbook) 103 | 104 | #### async getKline(params) 105 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-querykline) 106 | 107 | #### async getLatestInformation() 108 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-latestsymbolinfo) 109 | 110 | #### async getPublicTradingRecords(params) 111 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-publictradingrecords) 112 | 113 | #### async getServerTime() 114 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-servertime) 115 | 116 | #### async getApiAnnouncements() 117 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-announcement) 118 | 119 | #### async getSymbols() 120 | Returns symbol information (such as tick size & min notional): 121 | [Meeting price restrictions](https://bybit-exchange.github.io/docs/inverse/#price-price) 122 | 123 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-querysymbol) 124 | 125 | #### async getTimeOffset() 126 | 127 | Returns the time offset in ms to the server time retrieved by [`async getServerTime`](#async-getservertime). 128 | If positive the time on the server is ahead of the clients time, if negative the time on the server is behind the clients time. 129 | 130 | 131 | ## Example 132 | 133 | ```js 134 | const {RestClient} = require('@pxtrn/bybit-api'); 135 | 136 | const API_KEY = 'xxx'; 137 | const PRIVATE_KEY = 'yyy'; 138 | 139 | const client = new RestClient(API_KEY, PRIVATE_KEY); 140 | 141 | client.changeUserLeverage({leverage: 4, symbol: 'ETHUSD'}) 142 | .then(result => { 143 | console.log(result); 144 | }) 145 | .catch(err => { 146 | console.error(err); 147 | }); 148 | ``` 149 | -------------------------------------------------------------------------------- /doc/websocket-client.md: -------------------------------------------------------------------------------- 1 | # Websocket API 2 | 3 | 4 | ## Class: WebsocketClient 5 | 6 | The `WebsocketClient` inherits from `EventEmitter`. After establishing a 7 | connection, the client sends heartbeats in regular intervalls, and reconnects 8 | to the server once connection has been lost. 9 | 10 | 11 | ### new WebsocketClient([options][, logger]) 12 | - `options` {Object} Configuration options 13 | - `key` {String} Bybit API Key. Only needed if private topics are subscribed 14 | - `secret` {String} Bybit private Key. Only needed if private topics are 15 | subscribed 16 | - `livenet` {Bool} Weather to connect to livenet (`true`). Default `false`. 17 | - `pingInterval` {Integer} Interval in ms for heartbeat ping. Default: `10000`, 18 | - `pongTimeout` {Integer} Timeout in ms waiting for heartbeat pong response 19 | from server. Default: `1000`, 20 | - `reconnectTimeout` {Integer} Timeout in ms the client waits before trying 21 | to reconnect after a lost connection. Default: 500 22 | - `logger` {Object} Optional custom logger 23 | 24 | Custom logger must contain the following methods: 25 | ```js 26 | const logger = { 27 | silly: function(message, data) {}, 28 | debug: function(message, data) {}, 29 | notice: function(message, data) {}, 30 | info: function(message, data) {}, 31 | warning: function(message, data) {}, 32 | error: function(message, data) {}, 33 | } 34 | ``` 35 | 36 | ### ws.subscribe(topics) 37 | 38 | - `topics` {String|Array} Single topic as string or multiple topics as array of strings. 39 | Subscribe to one or multiple topics. See [available topics](#available-topics) 40 | 41 | ### ws.unsubscribe(topics) 42 | 43 | - `topics` {String|Array} Single topic as string or multiple topics as array of strings. 44 | Unsubscribe from one or multiple topics. 45 | 46 | ### ws.close() 47 | 48 | Close the connection to the server. 49 | 50 | 51 | ### Event: 'open' 52 | 53 | Emmited when the connection has been opened for the first time. 54 | 55 | 56 | ### Event: 'reconnected' 57 | 58 | Emmited when the client has been opened after a reconnect. 59 | 60 | 61 | ### Event: 'update' 62 | 63 | - `message` {Object} 64 | - `topic` {String} the topic for which the update occured 65 | - `data` {Array|Object} updated data (see docs for each [topic](#available-topics)). 66 | - `type` {String} Some topics might have different update types (see docs for each [topic](#available-topics)). 67 | 68 | Emmited whenever an update to a subscribed topic occurs. 69 | 70 | 71 | ### Event: 'response' 72 | 73 | - `response` {Object} 74 | - `success` {Bool} 75 | - `ret_msg` {String} empty if operation was successfull, otherwise error message. 76 | - `conn_id` {String} connection id 77 | - `request` {Object} Original request, to which the response belongs 78 | - `op` {String} operation 79 | - `args` {Array} Request Arguments 80 | 81 | Emited when the server responds to an operation sent by the client (usually after subscribing to a topic). 82 | 83 | 84 | ### Event: 'close' 85 | 86 | Emitted when the connection has been finally closed, after a call to `ws.close()` 87 | 88 | 89 | ### Event: 'reconnect' 90 | 91 | Emitted when the connection has been closed, but the client will try to reconnect. 92 | 93 | 94 | ### Event: 'error' 95 | 96 | - `error` {Error} 97 | 98 | Emitted when an error occurs. 99 | 100 | 101 | ## Available Topics 102 | 103 | Generaly all [public](https://bybit-exchange.github.io/docs/inverse/#t-publictopics) and [private](https://bybit-exchange.github.io/docs/inverse/#t-privatetopics) 104 | topics are available. 105 | 106 | ### Private topics 107 | 108 | #### Positions of your account 109 | 110 | All positions of your account. 111 | Topic: `position` 112 | 113 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-websocketposition) 114 | 115 | #### Execution message 116 | 117 | Execution message, whenever an order has been (partially) filled. 118 | Topic: `execution` 119 | 120 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-websocketexecution) 121 | 122 | #### Update for your orders 123 | 124 | Updates for your active orders 125 | Topic: `order` 126 | 127 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-websocketorder) 128 | 129 | #### Update for your conditional orders 130 | 131 | Updates for your active conditional orders 132 | Topic: `stop_order` 133 | 134 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-websocketstoporder) 135 | 136 | 137 | ### Public topics 138 | 139 | #### Candlestick chart 140 | 141 | Candlestick OHLC "candles" for selected symbol and interval. 142 | Example topic: `klineV2.BTCUSD.1m` 143 | 144 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-websocketklinev2) 145 | 146 | #### Real-time trading information 147 | 148 | All trades as they occur. 149 | Topic: `trade` 150 | 151 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-websockettrade) 152 | 153 | #### Daily insurance fund update 154 | 155 | Topic: `insurance` 156 | 157 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-websocketinsurance) 158 | 159 | #### OrderBook of 25 depth per side 160 | 161 | OrderBook for selected symbol 162 | Example topic: `orderBookL2_25.BTCUSD` 163 | 164 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-websocketorderbook25) 165 | 166 | #### OrderBook of 200 depth per side 167 | 168 | OrderBook for selected symbol 169 | Example topic: `orderBook_200.100ms.BTCUS` 170 | 171 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-websocketorderbook200) 172 | 173 | #### Latest information for symbol 174 | 175 | Latest information for selected symbol 176 | Example topic: `instrument_info.100ms.BTCUSD` 177 | 178 | [See bybit documentation](https://bybit-exchange.github.io/docs/inverse/#t-websocketinstrumentinfo) 179 | 180 | 181 | ## Example 182 | 183 | ```js 184 | const {WebsocketClient} = require('@pxtrn/bybit-api'); 185 | 186 | const API_KEY = 'xxx'; 187 | const PRIVATE_KEY = 'yyy'; 188 | 189 | const ws = new WebsocketClient({key: API_KEY, secret: PRIVATE_KEY}); 190 | 191 | ws.subscribe(['position', 'execution', 'trade']); 192 | ws.subscribe('kline.BTCUSD.1m'); 193 | 194 | ws.on('open', function() { 195 | console.log('connection open'); 196 | }); 197 | 198 | ws.on('update', function(message) { 199 | console.log('update', message); 200 | }); 201 | 202 | ws.on('response', function(response) { 203 | console.log('response', response); 204 | }); 205 | 206 | ws.on('close', function() { 207 | console.log('connection closed'); 208 | }); 209 | 210 | ws.on('error', function(err) { 211 | console.error('ERR', err); 212 | }); 213 | ``` 214 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const RestClient = require('./lib/rest-client.js'); 2 | const WebsocketClient = require('./lib/websocket-client.js'); 3 | const DefaultLogger = require('./lib/logger.js'); 4 | 5 | module.exports = { 6 | RestClient, 7 | WebsocketClient, 8 | DefaultLogger 9 | } 10 | -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | silly: function() {console.log(arguments)}, 4 | debug: function() {console.log(arguments)}, 5 | notice: function() {console.log(arguments)}, 6 | info: function() {console.info(arguments)}, 7 | warning: function() {console.warn(arguments)}, 8 | error: function() {console.error(arguments)}, 9 | } 10 | -------------------------------------------------------------------------------- /lib/request.js: -------------------------------------------------------------------------------- 1 | 2 | const assert = require('assert'); 3 | 4 | const request = require('request'); 5 | 6 | const {signMessage} = require('./utility.js'); 7 | 8 | const baseUrls = { 9 | livenet: 'https://api.bybit.com', 10 | testnet: 'https://api-testnet.bybit.com' 11 | }; 12 | 13 | module.exports = class Request { 14 | 15 | constructor(key, secret, livenet=false, options={}) { 16 | this.baseUrl = baseUrls[livenet === true ? 'livenet' : 'testnet']; 17 | this._timeOffset = null; 18 | this._syncTimePromise = null; 19 | 20 | this.options = { 21 | recv_window: 5000, 22 | sync_interval_ms: 3600000, 23 | ...options 24 | } 25 | 26 | if(key) assert(secret, 'Secret is required for private enpoints'); 27 | 28 | this._syncTime(); 29 | setInterval(this._syncTime.bind(this), parseInt(this.options.sync_interval_ms)); 30 | 31 | this.key = key; 32 | this.secret = secret; 33 | } 34 | 35 | 36 | async get(endpoint, params) { 37 | const result = await this._call('GET', endpoint, params); 38 | 39 | return result; 40 | } 41 | 42 | async post(endpoint, params) { 43 | const result = await this._call('POST', endpoint, params); 44 | 45 | return result; 46 | } 47 | 48 | async getTimeOffset() { 49 | const start = Date.now(); 50 | const result = await this.get('/v2/public/time'); 51 | const end = Date.now(); 52 | 53 | return Math.ceil((result.time_now * 1000) - end + ((end - start) / 2)); 54 | } 55 | 56 | async _call(method, endpoint, params) { 57 | const publicEndpoint = endpoint.startsWith('/v2/public'); 58 | 59 | if(!publicEndpoint) { 60 | if(!this.key || !this.secret) throw new Error('Private endpoints require api and private keys set'); 61 | 62 | if(this._timeOffset === null) await this._syncTime(); 63 | 64 | params = this._signRequest(params); 65 | } 66 | 67 | const options = { 68 | url: [this.baseUrl, endpoint].join('/'), 69 | method: method, 70 | json: true 71 | }; 72 | 73 | switch(method) { 74 | case 'GET': 75 | options.qs = params 76 | break; 77 | case 'POST': 78 | options.body = params 79 | break; 80 | } 81 | 82 | return new Promise((resolve, reject) => { 83 | request(options, function callback(error, response, body) { 84 | if(!error && response.statusCode == 200) { 85 | resolve(body); 86 | } else if(error) { 87 | reject(error); 88 | } 89 | }); 90 | }); 91 | } 92 | 93 | _signRequest(data) { 94 | const params = { 95 | ...data, 96 | api_key: this.key, 97 | timestamp: Date.now() + this._timeOffset 98 | }; 99 | 100 | // Optional, set to 5000 by default. Increase if timestamp/recv_window errors are seen. 101 | if(this.options.recv_window && !params.recv_window) { 102 | params.recv_window = this.options.recv_window; 103 | } 104 | 105 | if(this.key && this.secret) { 106 | params.sign = signMessage(this._serializeParams(params), this.secret); 107 | } 108 | 109 | return params; 110 | } 111 | 112 | _serializeParams(params) { 113 | return Object.keys(params) 114 | .sort() 115 | .map(key => `${key}=${params[key]}`) 116 | .join('&'); 117 | } 118 | 119 | async _syncTime() { 120 | if(this._syncTimePromise !== null) return this._syncTimePromise; 121 | 122 | this._syncTimePromise = new Promise(async (resolve, reject) => { 123 | try { 124 | this._timeOffset = await this.getTimeOffset(); 125 | this._syncTimePromise = null; 126 | resolve(); 127 | } catch(err) { 128 | reject(err); 129 | } 130 | }); 131 | 132 | return this._syncTimePromise; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /lib/rest-client.js: -------------------------------------------------------------------------------- 1 | 2 | const assert = require('assert'); 3 | 4 | const Request = require('./request.js'); 5 | 6 | module.exports = class RestClient { 7 | 8 | constructor(key, secret, livenet=false, options={}) { 9 | this.request = new Request(...arguments); 10 | } 11 | 12 | async placeActiveOrder(params) { 13 | assert(params, 'No params passed'); 14 | assert(params.side, 'Parameter side is required'); 15 | assert(params.symbol, 'Parameter symbol is required'); 16 | assert(params.order_type, 'Parameter order_type is required'); 17 | assert(params.qty, 'Parameter qty is required'); 18 | assert(params.time_in_force, 'Parameter time_in_force is required'); 19 | 20 | if(params.order_type === 'Limit') assert(params.price, 'Parameter price is required for limit orders'); 21 | 22 | return await this.request.post('/v2/private/order/create', params); 23 | } 24 | 25 | async getActiveOrder(params) { 26 | return await this.request.get('/open-api/order/list', params); 27 | } 28 | 29 | async cancelActiveOrder(params) { 30 | assert(params, 'No params passed'); 31 | assert(params.symbol, 'Parameter symbol is required'); 32 | assert(params.order_id || params.order_link_id, 'Parameter order_id OR order_link_id is required'); 33 | 34 | return await this.request.post('/v2/private/order/cancel', params); 35 | } 36 | 37 | async cancelAllActiveOrders(params) { 38 | assert(params, 'No params passed'); 39 | assert(params.symbol, 'Parameter symbol is required'); 40 | 41 | return await this.request.post('/v2/private/order/cancelAll', params); 42 | } 43 | 44 | async replaceActiveOrder(params) { 45 | assert(params, 'No params passed'); 46 | assert(params.order_id || params.order_link_id, 'Parameter order_id OR order_link_id is required'); 47 | assert(params.symbol, 'Parameter symbol is required'); 48 | 49 | return await this.request.post('/open-api/order/replace', params); 50 | } 51 | 52 | async queryActiveOrder(params) { 53 | assert(params, 'No params passed'); 54 | assert(params.order_id || params.order_link_id, 'Parameter order_id OR order_link_id is required'); 55 | assert(params.symbol, 'Parameter symbol is required'); 56 | 57 | return await this.request.get('/v2/private/order', params); 58 | } 59 | 60 | async placeConditionalOrder(params) { 61 | assert(params, 'No params passed'); 62 | assert(params.side, 'Parameter side is required'); 63 | assert(params.symbol, 'Parameter symbol is required'); 64 | assert(params.order_type, 'Parameter order_type is required'); 65 | assert(params.qty, 'Parameter qty is required'); 66 | assert(params.time_in_force, 'Parameter time_in_force is required'); 67 | assert(params.base_price, 'Parameter base_price is required'); 68 | assert(params.stop_px, 'Parameter stop_px is required'); 69 | 70 | if(params.order_type === 'Limit') assert(params.price, 'Parameter price is required for limit orders'); 71 | 72 | return await this.request.post('/open-api/stop-order/create', params); 73 | } 74 | 75 | async getConditioanlOrder(params) { 76 | return await this.request.get('/open-api/stop-order/list', params); 77 | } 78 | 79 | async cancelConditionalOrder(params) { 80 | assert(params, 'No params passed'); 81 | assert(params.stop_order_id, 'Parameter stop_order_id is required'); 82 | 83 | return await this.request.post('/open-api/stop-order/cancel', params); 84 | } 85 | 86 | async cancelAllConditionalOrders(params) { 87 | assert(params, 'No params passed'); 88 | assert(params.symbol, 'Parameter symbol is required'); 89 | 90 | return await this.request.post('/v2/private/stop-order/cancelAll', params); 91 | } 92 | 93 | async queryConditionalOrder(params) { 94 | assert(params, 'No params passed'); 95 | assert(params.stop_order_id || params.order_link_id, 'Parameter order_id OR order_link_id is required'); 96 | assert(params.symbol, 'Parameter symbol is required'); 97 | 98 | return await this.request.get('GET /v2/private/stop-order', params); 99 | } 100 | 101 | async getUserLeverage() { 102 | return await this.request.get('/user/leverage'); 103 | } 104 | 105 | async changeUserLeverage(params) { 106 | assert(params, 'No params passed'); 107 | assert(params.leverage, 'Parameter leverage is required'); 108 | assert(params.symbol, 'Parameter symbol is required'); 109 | 110 | return await this.request.post('/user/leverage/save', params); 111 | } 112 | 113 | async getPosition(params) { 114 | assert(params, 'No params passed'); 115 | assert(params.symbol, 'Parameter symbol is required'); 116 | 117 | return await this.request.get('/v2/private/position/list', params); 118 | } 119 | 120 | async getPositions() { 121 | return await this.request.get('/position/list'); 122 | } 123 | 124 | async changePositionMargin(params) { 125 | assert(params, 'No params passed'); 126 | assert(params.margin, 'Parameter margin is required'); 127 | assert(params.symbol, 'Parameter symbol is required'); 128 | 129 | return await this.request.post('/position/change-position-margin', params); 130 | } 131 | 132 | async setTradingStop(params) { 133 | assert(params, 'No params passed'); 134 | assert(params.symbol, 'Parameter symbol is required'); 135 | 136 | return await this.request.post('/open-api/position/trading-stop', params); 137 | } 138 | 139 | async getWalletFundRecords(params) { 140 | return await this.request.get('/open-api/wallet/fund/records', params); 141 | } 142 | 143 | async getWithdrawRecords(params) { 144 | return await this.request.get('/open-api/wallet/withdraw/list', params); 145 | } 146 | 147 | async getWalletBalance(params) { 148 | assert(params, 'No params passed'); 149 | assert(params.coin, 'Parameter coin is required'); 150 | return await this.request.get('/v2/private/wallet/balance', params); 151 | } 152 | 153 | async setRiskLimit(params) { 154 | assert(params, 'No params passed'); 155 | assert(params.symbol, 'Parameter symbol is required'); 156 | assert(params.risk_id, 'Parameter risk_id is required'); 157 | 158 | return await this.request.post('/open-api/wallet/risk-limit', params); 159 | } 160 | 161 | async getRiskLimitList() { 162 | return await this.request.get('/open-api/wallet/risk-limit/list'); 163 | } 164 | 165 | async getLastFundingRate(params) { 166 | assert(params, 'No params passed'); 167 | assert(params.symbol, 'Parameter symbol is required'); 168 | 169 | return await this.request.get('/open-api/funding/prev-funding-rate', params); 170 | } 171 | 172 | async getMyLastFundingFee(params) { 173 | assert(params, 'No params passed'); 174 | assert(params.symbol, 'Parameter symbol is required'); 175 | 176 | return await this.request.get('/open-api/funding/prev-funding', params); 177 | } 178 | 179 | async getPredictedFunding(params) { 180 | assert(params, 'No params passed'); 181 | assert(params.symbol, 'Parameter symbol is required'); 182 | 183 | return await this.request.get('/open-api/funding/predicted-funding', params); 184 | } 185 | 186 | async getTradeRecords(params) { 187 | assert(params, 'No params passed'); 188 | assert(params.order_id || params.symbol, 'Parameter order_id OR symbol is required'); 189 | 190 | return await this.request.get('/v2/private/execution/list', params); 191 | } 192 | 193 | async getOrderBook(params) { 194 | assert(params, 'No params passed'); 195 | assert(params.symbol, 'Parameter symbol is required'); 196 | 197 | return await this.request.get('/v2/public/orderBook/L2', params); 198 | } 199 | 200 | async getKline(params) { 201 | assert(params, 'No params passed'); 202 | assert(params.symbol, 'Parameter symbol is required'); 203 | assert(params.interval, 'Parameter interval is required'); 204 | assert(params.from, 'Parameter from is required'); 205 | 206 | return await this.request.get('/v2/public/kline/list', params); 207 | } 208 | 209 | async getLatestInformation() { 210 | return await this.request.get('/v2/public/tickers'); 211 | } 212 | 213 | async getPublicTradingRecords(params) { 214 | assert(params, 'No params passed'); 215 | assert(params.symbol, 'Parameter symbol is required'); 216 | 217 | return await this.request.get('/v2/public/trading-records', params); 218 | } 219 | 220 | async getServerTime() { 221 | return await this.request.get('/v2/public/time'); 222 | } 223 | 224 | async getApiAnnouncements() { 225 | return await this.request.get('/v2/public/announcement'); 226 | } 227 | 228 | async getSymbols() { 229 | return await this.request.get('/v2/public/symbols'); 230 | } 231 | 232 | async getTimeOffset() { 233 | return await this.request.getTimeOffset(); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /lib/utility.js: -------------------------------------------------------------------------------- 1 | const {createHmac} = require('crypto'); 2 | 3 | module.exports = { 4 | signMessage(message, secret) { 5 | return createHmac('sha256', secret) 6 | .update(message) 7 | .digest('hex'); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/websocket-client.js: -------------------------------------------------------------------------------- 1 | const {EventEmitter} = require('events'); 2 | 3 | const WebSocket = require('ws'); 4 | 5 | const defaultLogger = require('./logger.js'); 6 | const RestClient = require('./rest-client.js'); 7 | const {signMessage} = require('./utility.js'); 8 | 9 | const wsUrls = { 10 | livenet: 'wss://stream.bybit.com/realtime', 11 | testnet: 'wss://stream-testnet.bybit.com/realtime' 12 | }; 13 | 14 | const READY_STATE_INITIAL = 0; 15 | const READY_STATE_CONNECTING = 1; 16 | const READY_STATE_CONNECTED = 2; 17 | const READY_STATE_CLOSING = 3; 18 | const READY_STATE_RECONNECTING = 4; 19 | 20 | module.exports = class WebsocketClient extends EventEmitter { 21 | constructor(options, logger) { 22 | super(); 23 | 24 | this.logger = logger || defaultLogger; 25 | 26 | this.readyState = READY_STATE_INITIAL; 27 | this.pingInterval = null; 28 | this.pongTimeout = null; 29 | 30 | this.options = { 31 | livenet: false, 32 | pongTimeout: 1000, 33 | pingInterval: 10000, 34 | reconnectTimeout: 500, 35 | ...options 36 | } 37 | 38 | this.client = new RestClient(null, null, this.options.livenet); 39 | this._subscriptions = new Set(); 40 | 41 | this._connect(); 42 | } 43 | 44 | subscribe(topics) { 45 | if(!Array.isArray(topics)) topics = [topics]; 46 | topics.forEach(topic => this._subscriptions.add(topic)); 47 | 48 | // subscribe not necessary if not yet connected (will subscribe onOpen) 49 | if(this.readyState === READY_STATE_CONNECTED) this._subscribe(topics); 50 | } 51 | 52 | unsubscribe(topics) { 53 | if(!Array.isArray(topics)) topics = [topics]; 54 | 55 | topics.forEach(topic => this._subscriptions.delete(topic)); 56 | 57 | // unsubscribe not necessary if not yet connected 58 | if(this.readyState === READY_STATE_CONNECTED) this._unsubscribe(topics); 59 | } 60 | 61 | close() { 62 | this.logger.info('Closing connection', {category: 'bybit-ws'}); 63 | this.readyState = READY_STATE_CLOSING; 64 | this._teardown(); 65 | this.ws.close(); 66 | } 67 | 68 | async _connect() { 69 | try { 70 | if(this.readyState === READY_STATE_INITIAL) this.readyState = READY_STATE_CONNECTING; 71 | 72 | const authParams = await this._authenticate(); 73 | const url = wsUrls[this.options.livenet ? 'livenet' : 'testnet'] + authParams; 74 | 75 | this.ws = new WebSocket(url); 76 | 77 | this.ws.on('open', this._wsOpenHandler.bind(this)); 78 | this.ws.on('message', this._wsMessageHandler.bind(this)); 79 | this.ws.on('error', this._wsOnErrorHandler.bind(this)); 80 | this.ws.on('close', this._wsCloseHandler.bind(this)); 81 | } catch(err) { 82 | this.logger.error('Connection failed', err); 83 | this._reconnect(this.options.reconnectTimeout); 84 | } 85 | } 86 | 87 | async _authenticate() { 88 | if(this.options.key && this.options.secret) { 89 | this.logger.debug('Starting authenticated websocket client.', {category: 'bybit-ws'}); 90 | 91 | const timeOffset = await this.client.getTimeOffset(); 92 | 93 | const params = { 94 | api_key: this.options.key, 95 | expires: (Date.now() + timeOffset + 5000) 96 | }; 97 | 98 | params.signature = signMessage('GET/realtime' + params.expires, this.options.secret); 99 | 100 | return '?' + Object.keys(params) 101 | .sort() 102 | .map(key => `${key}=${params[key]}`) 103 | .join('&'); 104 | } else if(this.options.key || this.options.secret) { 105 | this.logger.warning('Could not authenticate websocket, either api key or private key missing.', {category: 'bybit-ws'}); 106 | } else { 107 | this.logger.debug('Starting public only websocket client.', {category: 'bybit-ws'}); 108 | } 109 | 110 | return ''; 111 | } 112 | 113 | _reconnect(timeout) { 114 | this._teardown(); 115 | if(this.readyState !== READY_STATE_CONNECTING) this.readyState = READY_STATE_RECONNECTING; 116 | 117 | setTimeout(() => { 118 | this.logger.info('Reconnecting to server', {category: 'bybit-ws'}); 119 | 120 | this._connect(); 121 | }, timeout); 122 | } 123 | 124 | _ping() { 125 | clearTimeout(this.pongTimeout); 126 | this.pongTimeout = null; 127 | 128 | this.logger.silly('Sending ping', {category: 'bybit-ws'}); 129 | this.ws.send(JSON.stringify({op: 'ping'})); 130 | 131 | this.pongTimeout = setTimeout(() => { 132 | this.logger.info('Pong timeout', {category: 'bybit-ws'}); 133 | this._teardown(); 134 | this.ws.terminate(); 135 | }, this.options.pongTimeout); 136 | } 137 | 138 | _teardown() { 139 | if(this.pingInterval) clearInterval(this.pingInterval); 140 | if(this.pongTimeout) clearTimeout(this.pongTimeout); 141 | 142 | this.pongTimeout = null; 143 | this.pingInterval = null; 144 | } 145 | 146 | _wsOpenHandler() { 147 | if(this.readyState === READY_STATE_CONNECTING) { 148 | this.logger.info('Websocket connected', {category: 'bybit-ws', livenet: this.options.livenet}); 149 | this.emit('open'); 150 | } else if(this.readyState === READY_STATE_RECONNECTING) { 151 | this.logger.info('Websocket reconnected', {category: 'bybit-ws', livenet: this.options.livenet}); 152 | this.emit('reconnected'); 153 | } 154 | 155 | this.readyState = READY_STATE_CONNECTED; 156 | 157 | this._subscribe([...this._subscriptions]); 158 | this.pingInterval = setInterval(this._ping.bind(this), this.options.pingInterval); 159 | } 160 | 161 | _wsMessageHandler(message) { 162 | let msg = JSON.parse(message); 163 | 164 | if('success' in msg) { 165 | this._handleResponse(msg); 166 | } else if(msg.topic) { 167 | this._handleUpdate(msg); 168 | } else { 169 | this.logger.warning('Got unhandled ws message', msg); 170 | } 171 | } 172 | 173 | _wsOnErrorHandler(err) { 174 | this.logger.error('Websocket error', {category: 'bybit-ws', err}); 175 | if(this.readyState === READY_STATE_CONNECTED) this.emit('error', err); 176 | } 177 | 178 | _wsCloseHandler() { 179 | this.logger.info('Websocket connection closed', {category: 'bybit-ws'}); 180 | 181 | if(this.readyState !== READY_STATE_CLOSING) { 182 | this._reconnect(this.options.reconnectTimeout); 183 | this.emit('reconnect'); 184 | } else { 185 | this.readyState = READY_STATE_INITIAL; 186 | this.emit('close'); 187 | } 188 | } 189 | 190 | _handleResponse(response) { 191 | if(response.request && response.request.op === 'ping' && response.ret_msg === 'pong') { 192 | if(response.success === true) { 193 | this.logger.silly('pong recieved', {category: 'bybit-ws'}); 194 | clearTimeout(this.pongTimeout); 195 | } 196 | } else { 197 | this.emit('response', response); 198 | } 199 | } 200 | 201 | _handleUpdate(message) { 202 | this.emit('update', message); 203 | } 204 | 205 | _subscribe(topics) { 206 | const msgStr = JSON.stringify({ 207 | op: 'subscribe', 208 | 'args': topics 209 | }); 210 | 211 | this.ws.send(msgStr); 212 | } 213 | 214 | _unsubscribe(topics) { 215 | const msgStr = JSON.stringify({ 216 | op: 'unsubscribe', 217 | 'args': topics 218 | }); 219 | 220 | this.ws.send(msgStr); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pxtrn/bybit-api", 3 | "version": "1.1.5", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ajv": { 8 | "version": "6.10.2", 9 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", 10 | "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", 11 | "requires": { 12 | "fast-deep-equal": "^2.0.1", 13 | "fast-json-stable-stringify": "^2.0.0", 14 | "json-schema-traverse": "^0.4.1", 15 | "uri-js": "^4.2.2" 16 | } 17 | }, 18 | "asn1": { 19 | "version": "0.2.4", 20 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", 21 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", 22 | "requires": { 23 | "safer-buffer": "~2.1.0" 24 | } 25 | }, 26 | "assert-plus": { 27 | "version": "1.0.0", 28 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 29 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 30 | }, 31 | "async-limiter": { 32 | "version": "1.0.1", 33 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", 34 | "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" 35 | }, 36 | "asynckit": { 37 | "version": "0.4.0", 38 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 39 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 40 | }, 41 | "aws-sign2": { 42 | "version": "0.7.0", 43 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 44 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 45 | }, 46 | "aws4": { 47 | "version": "1.8.0", 48 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", 49 | "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" 50 | }, 51 | "bcrypt-pbkdf": { 52 | "version": "1.0.2", 53 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 54 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 55 | "requires": { 56 | "tweetnacl": "^0.14.3" 57 | } 58 | }, 59 | "caseless": { 60 | "version": "0.12.0", 61 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 62 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 63 | }, 64 | "combined-stream": { 65 | "version": "1.0.8", 66 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 67 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 68 | "requires": { 69 | "delayed-stream": "~1.0.0" 70 | } 71 | }, 72 | "core-util-is": { 73 | "version": "1.0.2", 74 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 75 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 76 | }, 77 | "dashdash": { 78 | "version": "1.14.1", 79 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 80 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 81 | "requires": { 82 | "assert-plus": "^1.0.0" 83 | } 84 | }, 85 | "delayed-stream": { 86 | "version": "1.0.0", 87 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 88 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 89 | }, 90 | "ecc-jsbn": { 91 | "version": "0.1.2", 92 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 93 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", 94 | "requires": { 95 | "jsbn": "~0.1.0", 96 | "safer-buffer": "^2.1.0" 97 | } 98 | }, 99 | "extend": { 100 | "version": "3.0.2", 101 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 102 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 103 | }, 104 | "extsprintf": { 105 | "version": "1.3.0", 106 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 107 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 108 | }, 109 | "fast-deep-equal": { 110 | "version": "2.0.1", 111 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", 112 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" 113 | }, 114 | "fast-json-stable-stringify": { 115 | "version": "2.0.0", 116 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 117 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" 118 | }, 119 | "forever-agent": { 120 | "version": "0.6.1", 121 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 122 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 123 | }, 124 | "form-data": { 125 | "version": "2.3.3", 126 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 127 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 128 | "requires": { 129 | "asynckit": "^0.4.0", 130 | "combined-stream": "^1.0.6", 131 | "mime-types": "^2.1.12" 132 | } 133 | }, 134 | "getpass": { 135 | "version": "0.1.7", 136 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 137 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 138 | "requires": { 139 | "assert-plus": "^1.0.0" 140 | } 141 | }, 142 | "har-schema": { 143 | "version": "2.0.0", 144 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 145 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 146 | }, 147 | "har-validator": { 148 | "version": "5.1.3", 149 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", 150 | "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", 151 | "requires": { 152 | "ajv": "^6.5.5", 153 | "har-schema": "^2.0.0" 154 | } 155 | }, 156 | "http-signature": { 157 | "version": "1.2.0", 158 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 159 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 160 | "requires": { 161 | "assert-plus": "^1.0.0", 162 | "jsprim": "^1.2.2", 163 | "sshpk": "^1.7.0" 164 | } 165 | }, 166 | "is-typedarray": { 167 | "version": "1.0.0", 168 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 169 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 170 | }, 171 | "isstream": { 172 | "version": "0.1.2", 173 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 174 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 175 | }, 176 | "jsbn": { 177 | "version": "0.1.1", 178 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 179 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" 180 | }, 181 | "json-schema": { 182 | "version": "0.2.3", 183 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 184 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 185 | }, 186 | "json-schema-traverse": { 187 | "version": "0.4.1", 188 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 189 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 190 | }, 191 | "json-stringify-safe": { 192 | "version": "5.0.1", 193 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 194 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 195 | }, 196 | "jsprim": { 197 | "version": "1.4.1", 198 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 199 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 200 | "requires": { 201 | "assert-plus": "1.0.0", 202 | "extsprintf": "1.3.0", 203 | "json-schema": "0.2.3", 204 | "verror": "1.10.0" 205 | } 206 | }, 207 | "mime-db": { 208 | "version": "1.40.0", 209 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", 210 | "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" 211 | }, 212 | "mime-types": { 213 | "version": "2.1.24", 214 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", 215 | "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", 216 | "requires": { 217 | "mime-db": "1.40.0" 218 | } 219 | }, 220 | "oauth-sign": { 221 | "version": "0.9.0", 222 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 223 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" 224 | }, 225 | "performance-now": { 226 | "version": "2.1.0", 227 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 228 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 229 | }, 230 | "psl": { 231 | "version": "1.3.1", 232 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.3.1.tgz", 233 | "integrity": "sha512-2KLd5fKOdAfShtY2d/8XDWVRnmp3zp40Qt6ge2zBPFARLXOGUf2fHD5eg+TV/5oxBtQKVhjUaKFsAaE4HnwfSA==" 234 | }, 235 | "punycode": { 236 | "version": "2.1.1", 237 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 238 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 239 | }, 240 | "qs": { 241 | "version": "6.5.2", 242 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 243 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 244 | }, 245 | "request": { 246 | "version": "2.88.0", 247 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", 248 | "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", 249 | "requires": { 250 | "aws-sign2": "~0.7.0", 251 | "aws4": "^1.8.0", 252 | "caseless": "~0.12.0", 253 | "combined-stream": "~1.0.6", 254 | "extend": "~3.0.2", 255 | "forever-agent": "~0.6.1", 256 | "form-data": "~2.3.2", 257 | "har-validator": "~5.1.0", 258 | "http-signature": "~1.2.0", 259 | "is-typedarray": "~1.0.0", 260 | "isstream": "~0.1.2", 261 | "json-stringify-safe": "~5.0.1", 262 | "mime-types": "~2.1.19", 263 | "oauth-sign": "~0.9.0", 264 | "performance-now": "^2.1.0", 265 | "qs": "~6.5.2", 266 | "safe-buffer": "^5.1.2", 267 | "tough-cookie": "~2.4.3", 268 | "tunnel-agent": "^0.6.0", 269 | "uuid": "^3.3.2" 270 | } 271 | }, 272 | "safe-buffer": { 273 | "version": "5.2.0", 274 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", 275 | "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" 276 | }, 277 | "safer-buffer": { 278 | "version": "2.1.2", 279 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 280 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 281 | }, 282 | "sshpk": { 283 | "version": "1.16.1", 284 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", 285 | "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", 286 | "requires": { 287 | "asn1": "~0.2.3", 288 | "assert-plus": "^1.0.0", 289 | "bcrypt-pbkdf": "^1.0.0", 290 | "dashdash": "^1.12.0", 291 | "ecc-jsbn": "~0.1.1", 292 | "getpass": "^0.1.1", 293 | "jsbn": "~0.1.0", 294 | "safer-buffer": "^2.0.2", 295 | "tweetnacl": "~0.14.0" 296 | } 297 | }, 298 | "tough-cookie": { 299 | "version": "2.4.3", 300 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", 301 | "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", 302 | "requires": { 303 | "psl": "^1.1.24", 304 | "punycode": "^1.4.1" 305 | }, 306 | "dependencies": { 307 | "punycode": { 308 | "version": "1.4.1", 309 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 310 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 311 | } 312 | } 313 | }, 314 | "tunnel-agent": { 315 | "version": "0.6.0", 316 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 317 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 318 | "requires": { 319 | "safe-buffer": "^5.0.1" 320 | } 321 | }, 322 | "tweetnacl": { 323 | "version": "0.14.5", 324 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 325 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" 326 | }, 327 | "uri-js": { 328 | "version": "4.2.2", 329 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", 330 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", 331 | "requires": { 332 | "punycode": "^2.1.0" 333 | } 334 | }, 335 | "uuid": { 336 | "version": "3.3.3", 337 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", 338 | "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" 339 | }, 340 | "verror": { 341 | "version": "1.10.0", 342 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 343 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 344 | "requires": { 345 | "assert-plus": "^1.0.0", 346 | "core-util-is": "1.0.2", 347 | "extsprintf": "^1.2.0" 348 | } 349 | }, 350 | "ws": { 351 | "version": "7.1.2", 352 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.1.2.tgz", 353 | "integrity": "sha512-gftXq3XI81cJCgkUiAVixA0raD9IVmXqsylCrjRygw4+UOOGzPoxnQ6r/CnVL9i+mDncJo94tSkyrtuuQVBmrg==", 354 | "requires": { 355 | "async-limiter": "^1.0.0" 356 | } 357 | } 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pxtrn/bybit-api", 3 | "version": "1.1.5", 4 | "description": "An unofficial node.js lowlevel wrapper for the Bybit Cryptocurrency Derivative exchange API", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/pixtron/bybit-api.git" 12 | }, 13 | "keywords": [ 14 | "bybit", 15 | "api", 16 | "websocket", 17 | "rest" 18 | ], 19 | "author": "Stefan Aebischer (https://pixtron.ch)", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/pixtron/bybit-api/issues" 23 | }, 24 | "homepage": "https://github.com/pixtron/bybit-api#readme", 25 | "dependencies": { 26 | "request": "^2.88.0", 27 | "ws": "^7.1.2" 28 | } 29 | } 30 | --------------------------------------------------------------------------------