diff --git a/.github/workflows/npmpublish.yml b/.github/workflows/npmpublish.yml index f2af8e7..b0b36fc 100644 --- a/.github/workflows/npmpublish.yml +++ b/.github/workflows/npmpublish.yml @@ -42,6 +42,8 @@ jobs: - run: npm ci if: steps.version-updated.outputs.has-updated + - run: npm run clean + if: steps.version-updated.outputs.has-updated - run: npm run build if: steps.version-updated.outputs.has-updated - run: npm publish diff --git a/README.md b/README.md index 854b870..c13d7c5 100644 --- a/README.md +++ b/README.md @@ -39,13 +39,10 @@ Build a bundle using webpack: The bundle can be found in `dist/`. Altough usage should be largely consistent, smaller differences will exist. Documentation is still TODO. ### Inverse Contracts -#### Rest client -```javascript -const { RestClient } = require('bybit-api'); +Since inverse and linear (USDT) contracts don't use the exact same APIs, the REST abstractions are split into two modules. To use the inverse REST APIs, import the `InverseClient`: -const API_KEY = 'xxx'; -const PRIVATE_KEY = 'yyy'; -const useLivenet = false; +```javascript +const { InverseClient } = require('bybit-api'); const restInverseOptions = { // override the max size of the request window (in ms) @@ -68,7 +65,11 @@ const restInverseOptions = { parse_exceptions?: boolean; }; -const client = new RestClient( +const API_KEY = 'xxx'; +const PRIVATE_KEY = 'yyy'; +const useLivenet = false; + +const client = new InverseClient( API_KEY, PRIVATE_KEY, @@ -88,9 +89,67 @@ client.changeUserLeverage({leverage: 4, symbol: 'ETHUSD'}) }); ``` -See inverse [rest-client.ts](./src/rest-client.ts) for further information. +See inverse [inverse-client.ts](./src/inverse-client.ts) for further information. + +### Linear Contracts +To use the Linear (USDT) REST APIs, import the `LinearClient`: + +```javascript +const { LinearClient } = require('bybit-api'); + +const restInverseOptions = { + // override the max size of the request window (in ms) + recv_window?: number; + + // how often to sync time drift with bybit servers + sync_interval_ms?: number | string; + + // Default: false. Disable above sync mechanism if true. + disable_time_sync?: boolean; + + // Default: false. If true, we'll throw errors if any params are undefined + strict_param_validation?: boolean; + + // Optionally override API protocol + domain + // e.g 'https://api.bytick.com' + baseUrl?: string; + + // Default: true. whether to try and post-process request exceptions. + parse_exceptions?: boolean; +}; + +const API_KEY = 'xxx'; +const PRIVATE_KEY = 'yyy'; +const useLivenet = false; + +const client = new LinearClient( + API_KEY, + PRIVATE_KEY, + + // optional, uses testnet by default. Set to 'true' to use livenet. + useLivenet, + + // restInverseOptions, + // requestLibraryOptions +); + +client.changeUserLeverage({leverage: 4, symbol: 'ETHUSD'}) + .then(result => { + console.log(result); + }) + .catch(err => { + console.error(err); + }); +``` + +### WebSockets + +Inverse & linear WebSockets can be used via a shared `WebsocketClient`. + +Note: to use the linear websockets, pass "linear: true" in the constructor options when instancing the `WebsocketClient`. + +To connect to both linear and inverse websockets, make two instances of the WebsocketClient: -#### Websocket client ```javascript const { WebsocketClient } = require('bybit-api'); @@ -123,7 +182,7 @@ const wsConfig = { // config options sent to RestClient (used for time sync). See RestClient docs. // restOptions: { }, - // config for axios to pass to RestClient. E.g for proxy support + // config for axios used for HTTP requests. E.g for proxy support // requestOptions: { } // override which URL to use for websocket connections diff --git a/package.json b/package.json index 47ac72c..98ff433 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,11 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "clean": "rm -rf lib dist", - "prebuild": "npm run clean", "build": "tsc", + "build:clean": "npm run clean && npm run build", + "build:watch": "npm run clean && tsc --watch", "pack": "webpack --config webpack/webpack.config.js", - "prepublish": "npm run build", + "prepublish": "npm run build:clean", "betapublish": "npm publish --tag beta" }, "author": "Tiago Siebler (https://github.com/tiagosiebler)", diff --git a/src/inverse-client.ts b/src/inverse-client.ts index a22e327..405c4b1 100644 --- a/src/inverse-client.ts +++ b/src/inverse-client.ts @@ -1,5 +1,5 @@ import { AxiosRequestConfig } from 'axios'; -import { GenericAPIResponse, getBaseRESTInverseUrl, RestClientInverseOptions } from './util/requestUtils'; +import { GenericAPIResponse, getRestBaseUrl, RestClientOptions } from './util/requestUtils'; import RequestWrapper from './util/requestWrapper'; import SharedEndpoints from './shared-endpoints'; @@ -12,23 +12,23 @@ export class InverseClient extends SharedEndpoints { * @param {string} key - your API key * @param {string} secret - your API secret * @param {boolean} [useLivenet=false] - * @param {RestClientInverseOptions} [restInverseOptions={}] options to configure REST API connectivity + * @param {RestClientOptions} [restInverseOptions={}] options to configure REST API connectivity * @param {AxiosRequestConfig} [requestOptions={}] HTTP networking options for axios */ constructor( key?: string | undefined, secret?: string | undefined, useLivenet?: boolean, - restInverseOptions: RestClientInverseOptions = {}, - httpOptions: AxiosRequestConfig = {} + restInverseOptions: RestClientOptions = {}, + requestOptions: AxiosRequestConfig = {} ) { super() this.requestWrapper = new RequestWrapper( key, secret, - getBaseRESTInverseUrl(useLivenet), + getRestBaseUrl(useLivenet), restInverseOptions, - httpOptions + requestOptions ); return this; } @@ -47,7 +47,7 @@ export class InverseClient extends SharedEndpoints { }): GenericAPIResponse { return this.requestWrapper.get('v2/public/kline/list', params); } - + /** * @deprecated use getTickers() instead */ @@ -56,7 +56,7 @@ export class InverseClient extends SharedEndpoints { }): GenericAPIResponse { return this.getTickers(params); } - + /** * @deprecated use getTrades() instead */ @@ -75,7 +75,7 @@ export class InverseClient extends SharedEndpoints { }): GenericAPIResponse { return this.requestWrapper.get('v2/public/trading-records', params); } - + /** * @deprecated use getLiquidations() instead */ @@ -97,7 +97,7 @@ export class InverseClient extends SharedEndpoints { }): GenericAPIResponse { return this.requestWrapper.get('v2/public/mark-price-kline', params); } - + getIndexPriceKline(params: { symbol: string; interval: string; @@ -106,7 +106,7 @@ export class InverseClient extends SharedEndpoints { }): GenericAPIResponse { return this.requestWrapper.get('v2/public/index-price-kline', params); } - + getPremiumIndexKline(params: { symbol: string; interval: string; @@ -121,7 +121,7 @@ export class InverseClient extends SharedEndpoints { * Account Data Endpoints * */ - + /** * Active orders */ @@ -183,7 +183,7 @@ export class InverseClient extends SharedEndpoints { }): GenericAPIResponse { return this.requestWrapper.get('v2/private/order', params); } - + /** * Conditional orders */ @@ -246,7 +246,7 @@ export class InverseClient extends SharedEndpoints { }): GenericAPIResponse { return this.requestWrapper.get('v2/private/stop-order', params); } - + /** * Position */ @@ -328,7 +328,7 @@ export class InverseClient extends SharedEndpoints { /** * Risk Limit */ - + getRiskLimitList(): GenericAPIResponse { return this.requestWrapper.get('open-api/wallet/risk-limit/list'); } @@ -339,7 +339,7 @@ export class InverseClient extends SharedEndpoints { }): GenericAPIResponse { return this.requestWrapper.post('open-api/wallet/risk-limit', params); } - + /** * Funding */ @@ -361,7 +361,7 @@ export class InverseClient extends SharedEndpoints { }): GenericAPIResponse { return this.requestWrapper.get('v2/private/funding/predicted-funding', params); } - + /** * LCP Info */ diff --git a/src/linear-client.ts b/src/linear-client.ts index e62f9e0..59f7f52 100644 --- a/src/linear-client.ts +++ b/src/linear-client.ts @@ -1,5 +1,5 @@ import { AxiosRequestConfig } from 'axios'; -import { GenericAPIResponse, getBaseRESTInverseUrl, RestClientInverseOptions } from './util/requestUtils'; +import { GenericAPIResponse, getRestBaseUrl, RestClientOptions } from './util/requestUtils'; import RequestWrapper from './util/requestWrapper'; import SharedEndpoints from './shared-endpoints'; @@ -11,23 +11,22 @@ export class LinearClient extends SharedEndpoints { * * @param {string} key - your API key * @param {string} secret - your API secret - * @param {boolean} [livenet=false] - * @param {RestClientInverseOptions} [restInverseOptions={}] options to configure REST API connectivity + * @param {boolean} [useLivenet=false] + * @param {RestClientOptions} [restInverseOptions={}] options to configure REST API connectivity * @param {AxiosRequestConfig} [requestOptions={}] HTTP networking options for axios */ - constructor( key?: string | undefined, secret?: string | undefined, - livenet?: boolean, - restInverseOptions:RestClientInverseOptions = {}, // TODO: Rename this type to be more general. + useLivenet?: boolean, + restInverseOptions: RestClientOptions = {}, requestOptions: AxiosRequestConfig = {} ) { super() this.requestWrapper = new RequestWrapper( key, secret, - getBaseRESTInverseUrl(livenet), + getRestBaseUrl(useLivenet), restInverseOptions, requestOptions ); @@ -46,23 +45,11 @@ export class LinearClient extends SharedEndpoints { from: number; limit?: number; }): GenericAPIResponse { - return this.requestWrapper.get('/public/linear/kline', params); - } - - /** - * @deprecated use getTrades() instead - */ - getPublicTradingRecords(params: { - symbol: string; - from?: number; - limit?: number; - }): GenericAPIResponse { - return this.getTrades(params); + return this.requestWrapper.get('public/linear/kline', params); } getTrades(params: { symbol: string; - //from?: number; limit?: number; }): GenericAPIResponse { return this.requestWrapper.get('public/linear/recent-trading-records', params); @@ -82,7 +69,7 @@ export class LinearClient extends SharedEndpoints { }): GenericAPIResponse { return this.requestWrapper.get('public/linear/mark-price-kline', params); } - + getIndexPriceKline(params: { symbol: string; interval: string; @@ -105,10 +92,6 @@ export class LinearClient extends SharedEndpoints { * * Account Data Endpoints * - */ - - /** - * Active orders */ placeActiveOrder(params: { @@ -137,7 +120,6 @@ export class LinearClient extends SharedEndpoints { page?: number; limit?: number; order_status?: string; - }): GenericAPIResponse { return this.requestWrapper.get('private/linear/order/list', params); } @@ -345,7 +327,15 @@ export class LinearClient extends SharedEndpoints { getRiskLimitList(params: { symbol: string; }): GenericAPIResponse { - return this.requestWrapper.get('public/linear/risk-limit'); + return this.requestWrapper.get('public/linear/risk-limit', params); + } + + setRiskLimit(params: { + symbol: string; + side: string; + risk_id: string; + }): GenericAPIResponse { + return this.requestWrapper.get('private/linear/position/set-risk', params); } /** @@ -355,13 +345,12 @@ export class LinearClient extends SharedEndpoints { getPredictedFundingFee(params: { symbol: string; }): GenericAPIResponse { - return this.requestWrapper.get('private/linear/funding/predicted-funding'); + return this.requestWrapper.get('private/linear/funding/predicted-funding', params); } getLastFundingFee(params: { symbol: string; }): GenericAPIResponse { - return this.requestWrapper.get('private/linear/funding/prev-funding'); + return this.requestWrapper.get('private/linear/funding/prev-funding', params); } - } diff --git a/src/shared-endpoints.ts b/src/shared-endpoints.ts index 744c4e0..c65a2ef 100644 --- a/src/shared-endpoints.ts +++ b/src/shared-endpoints.ts @@ -37,6 +37,12 @@ export default class SharedEndpoints { return this.requestWrapper.get('v2/public/liq-records', params); } + /** + * + * Market Data : Advanced + * + */ + getOpenInterest(params: { symbol: string; period: string; @@ -113,6 +119,14 @@ export default class SharedEndpoints { return this.requestWrapper.get('v2/private/exchange-order/list', params); } + getAssetExchangeRecords(params?: { + limit?: number; + from?: number; + direction?: string; + }): GenericAPIResponse { + return this.requestWrapper.get('v2/private/exchange-order/list', params); + } + /** * * API Data Endpoints diff --git a/src/util/requestUtils.ts b/src/util/requestUtils.ts index f75f106..903a7a2 100644 --- a/src/util/requestUtils.ts +++ b/src/util/requestUtils.ts @@ -1,6 +1,6 @@ import { createHmac } from 'crypto'; -export interface RestClientInverseOptions { +export interface RestClientOptions { // override the max size of the request window (in ms) recv_window?: number; @@ -42,7 +42,7 @@ export function serializeParams(params: object = {}, strict_validation = false): .join('&'); }; -export function getBaseRESTInverseUrl(useLivenet?: boolean, restInverseOptions?: RestClientInverseOptions) { +export function getRestBaseUrl(useLivenet?: boolean, restInverseOptions?: RestClientOptions) { const baseUrlsInverse = { livenet: 'https://api.bybit.com', testnet: 'https://api-testnet.bybit.com' diff --git a/src/util/requestWrapper.ts b/src/util/requestWrapper.ts index 7645061..e19f8f3 100644 --- a/src/util/requestWrapper.ts +++ b/src/util/requestWrapper.ts @@ -1,11 +1,11 @@ import axios, { AxiosRequestConfig, AxiosResponse, Method } from 'axios'; -import { signMessage, serializeParams, RestClientInverseOptions, GenericAPIResponse, isPublicEndpoint } from './requestUtils'; +import { signMessage, serializeParams, RestClientOptions, GenericAPIResponse, isPublicEndpoint } from './requestUtils'; export default class RequestUtil { private timeOffset: number | null; private syncTimePromise: null | Promise; - private options: RestClientInverseOptions; + private options: RestClientOptions; private baseUrl: string; private globalRequestOptions: AxiosRequestConfig; private key: string | undefined; @@ -15,7 +15,7 @@ export default class RequestUtil { key: string | undefined, secret: string | undefined, baseUrl: string, - options: RestClientInverseOptions = {}, + options: RestClientOptions = {}, requestOptions: AxiosRequestConfig = {} ) { this.timeOffset = null; diff --git a/src/websocket-client.ts b/src/websocket-client.ts index b81520b..27d9825 100644 --- a/src/websocket-client.ts +++ b/src/websocket-client.ts @@ -75,6 +75,16 @@ const getLinearWsKeyForTopic = (topic: string) => { return wsKeyLinearPublic; } +export declare interface WebsocketClient { + on(event: 'open', listener: ({ wsKey: string, event: any }) => void): this; + on(event: 'reconnected', listener: ({ wsKey: string, event: any }) => void): this; + on(event: 'reconnect', listener: () => void): this; + on(event: 'close', listener: () => void): this; + on(event: 'response', listener: (response: any) => void): this; + on(event: 'update', listener: (response: any) => void): this; + on(event: 'error', listener: (response: any) => void): this; +} + export class WebsocketClient extends EventEmitter { private logger: typeof DefaultLogger; private restClient: InverseClient | LinearClient;