diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bdff037..21eccfed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,17 @@ # Changelog + +## 3.6.0 - 2024-12-19 + +### Added +- A new optional parameter `timeUnit` can be used to select the time unit. Available options are `MILLISECOND`, `MICROSECOND`, `millisecond` and `microsecond`. + +### Changed +- Use signed requests for the following endpoints: + - `POST /sapi/v1/lending/auto-invest/plan/edit-status` + - `GET /sapi/v1/lending/auto-invest/plan/list` + - `GET /sapi/v1/lending/auto-invest/plan/id` + - `GET /sapi/v1/lending/auto-invest/history/list` + ## 3.5.0 - 2024-10-02 ### Added - Add GiftCard endpoint: diff --git a/README.md b/README.md index 33e1f01e..0c881e32 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,24 @@ const apiSecret = '' const client = new Spot(apiKey, apiSecret) client.account({ recvWindow: 2000 }).then(response => client.logger.log(response.data)) +``` + +### Time Unit + +The API supports different time units for timestamp values. By default, timestamp values are provided in milliseconds. You can specify the time unit in the request parameters: +```javascript +const { Spot, TimeUnit } = require('@binance/connector') + +const apiKey = '' +const apiSecret = '' +const client = new Spot(apiKey, apiSecret) + +// Using milliseconds (default) +client.exchangeInfo({ timeUnit: TimeUnit.MILLISECOND }).then(response => client.logger.log(response.data)) + +// Using microseconds +client.exchangeInfo({ timeUnit: TimeUnit.MICROSECOND }).then(response => client.logger.log(response.data)) ``` ### Timeout @@ -255,12 +272,12 @@ const callbacks = { message: data => logger.info(data) } -const websocketStreamClient = new WebsocketStream({ logger, callbacks }) +// initialize websocket stream with microseconds as the preferred time unit +const websocketStreamClient = new WebsocketStream({ logger, callbacks, timeUnit: TimeUnit.MICROSECOND }) // subscribe ticker stream websocketStreamClient.ticker('bnbusdt') // close websocket stream setTimeout(() => websocketStreamClient.disconnect(), 6000) - ``` ### Unsubscribe Websocket Stream @@ -273,7 +290,7 @@ websocketStreamClient.unsubscribe('bnbusdt@kline_1m') ### WebSocket API ```javascript -const { WebsocketAPI } = require('@binance/connector') +const { WebsocketAPI, TimeUnit } = require('@binance/connector') const logger = new Console({ stdout: process.stdout, stderr: process.stderr }) // callbacks for different events @@ -288,7 +305,8 @@ const callbacks = { message: data => logger.info(data) } -const websocketAPIClient = new WebsocketAPI(null, null, { logger, callbacks }) +// initialize WebsocketAPI client with microseconds as the preferred time unit +const websocketAPIClient = new WebsocketAPI(null, null, { logger, callbacks, timeUnit: TimeUnit.MICROSECOND }) // disconnect the connection setTimeout(() => websocketAPIClient.disconnect(), 20000) diff --git a/__tests__/helpers/utils.test.js b/__tests__/helpers/utils.test.js index d73605b5..d759a0f2 100644 --- a/__tests__/helpers/utils.test.js +++ b/__tests__/helpers/utils.test.js @@ -1,5 +1,5 @@ /* global describe, it, expect */ -const { isEmptyValue, removeEmptyValue, buildQueryString, flowRight, sortObject } = require('../../src/helpers/utils') +const { isEmptyValue, removeEmptyValue, removeTimeUnit, buildQueryString, flowRight, sortObject } = require('../../src/helpers/utils') describe('#isEmptyValue', () => { it.each([ @@ -45,6 +45,23 @@ describe('#removeEmptyValue', () => { }) }) +describe('#removeTimeUnit', () => { + it('should be the same without a timeUnit field', () => { + const obj = { foo: 'bar' } + expect(removeTimeUnit(obj)).toBe(obj) + }) + + it.each( + [ + [{ key1: 'value1', timeUnit: 'MICROSECOND' }, { key1: 'value1' }], + [{ timeUnit: 'MICROSECOND' }, { }], + [{ key1: true, timeUnit: 'MICROSECOND', key3: false }, { key1: true, key3: false }] + ] + )('should remove the timeUnit field', (obj, expectedObj) => { + expect(removeTimeUnit(obj)).toStrictEqual(expectedObj) + }) +}) + describe('#buildQueryString', () => { it.each` inputObj | output diff --git a/__tests__/helpers/validation.test.js b/__tests__/helpers/validation.test.js index 39dd6f1f..4da37491 100644 --- a/__tests__/helpers/validation.test.js +++ b/__tests__/helpers/validation.test.js @@ -1,6 +1,7 @@ /* global describe, it, expect */ -const { validateRequiredParameters, hasOneOfParameters } = require('../../src/helpers/validation') +const { validateRequiredParameters, hasOneOfParameters, validateTimeUnit } = require('../../src/helpers/validation') const MissingParameterError = require('../../src/error/missingParameterError') +const ConnectorClientError = require('../../src/error/connectorClientError') describe('#validateRequiredParameters', () => { it('should throw error without parameter', () => { @@ -74,3 +75,28 @@ describe('#hasOneOfParameters', () => { }).not.toThrow(MissingParameterError) }) }) + +describe('#validateTimeUnit', () => { + it('should not throw an error if timeUnit is not defined', () => { + expect(() => { + validateTimeUnit() + }).not.toThrow(ConnectorClientError) + }) + + it.each([ + 'MICROSECOND', + 'MILLISECOND' + ])('should not throw an error when timeUnit is valid', (timeUnit) => { + expect(() => { + validateTimeUnit(timeUnit) + }).not.toThrow(ConnectorClientError) + }) + + it.each([ + 'random string' + ])('should throw an error when timeUnit is invalid', (timeUnit) => { + expect(() => { + validateTimeUnit(timeUnit) + }).toThrow(ConnectorClientError) + }) +}) diff --git a/__tests__/spot/margin/isolatedMarginAccountLimit.test.js b/__tests__/spot/margin/isolatedMarginAccountLimit.test.js index 48ec340b..69ed426a 100644 --- a/__tests__/spot/margin/isolatedMarginAccountLimit.test.js +++ b/__tests__/spot/margin/isolatedMarginAccountLimit.test.js @@ -11,5 +11,4 @@ describe('#isolatedMarginAccountLimit', () => { expect(response.data).toEqual(mockResponse) }) }) -} -) +}) diff --git a/__tests__/spot/margin/isolatedMarginAllSymbols.test.js b/__tests__/spot/margin/isolatedMarginAllSymbols.test.js index b3eb4564..323c01ee 100644 --- a/__tests__/spot/margin/isolatedMarginAllSymbols.test.js +++ b/__tests__/spot/margin/isolatedMarginAllSymbols.test.js @@ -11,5 +11,4 @@ describe('#isolatedMarginAllSymbols', () => { expect(response.data).toEqual(mockResponse) }) }) -} -) +}) diff --git a/__tests__/spot/market/aggTrades.test.js b/__tests__/spot/market/aggTrades.test.js index 91f3b866..c327ba3e 100644 --- a/__tests__/spot/market/aggTrades.test.js +++ b/__tests__/spot/market/aggTrades.test.js @@ -1,7 +1,7 @@ /* global describe, it, expect */ const MissingParameterError = require('../../../src/error/missingParameterError') const { nockMock, SpotClient } = require('../../testUtils/testSetup') -const { mockResponse } = require('../../testUtils/mockData') +const { mockResponse, timeUnit } = require('../../testUtils/mockData') describe('#aggTrades', () => { describe('throw MissingParameterError', () => { @@ -16,6 +16,16 @@ describe('#aggTrades', () => { const symbol = 'BTCUSDT' nockMock(`/api/v3/aggTrades?symbol=${symbol}`)(mockResponse) + return SpotClient.aggTrades(symbol, { timeUnit }).then(response => { + expect(response).toBeDefined() + expect(response.data).toEqual(mockResponse) + }) + }) + + it('should return agg trades without optional parameters', () => { + const symbol = 'BTCUSDT' + nockMock(`/api/v3/aggTrades?symbol=${symbol}`)(mockResponse) + return SpotClient.aggTrades(symbol).then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) diff --git a/__tests__/spot/market/avgPrice.test.js b/__tests__/spot/market/avgPrice.test.js index e25b52e9..ec50817b 100644 --- a/__tests__/spot/market/avgPrice.test.js +++ b/__tests__/spot/market/avgPrice.test.js @@ -1,7 +1,7 @@ /* global describe, it, expect */ const MissingParameterError = require('../../../src/error/missingParameterError') const { nockMock, SpotClient } = require('../../testUtils/testSetup') -const { mockResponse } = require('../../testUtils/mockData') +const { mockResponse, timeUnit } = require('../../testUtils/mockData') describe('#avgPrice', () => { describe('throw MissingParameterError', () => { @@ -16,6 +16,16 @@ describe('#avgPrice', () => { const symbol = 'BTCUSDT' nockMock(`/api/v3/avgPrice?symbol=${symbol}`)(mockResponse) + return SpotClient.avgPrice(symbol, { timeUnit }).then(response => { + expect(response).toBeDefined() + expect(response.data).toEqual(mockResponse) + }) + }) + + it('should return avg price without optional parameters', () => { + const symbol = 'BTCUSDT' + nockMock(`/api/v3/avgPrice?symbol=${symbol}`)(mockResponse) + return SpotClient.avgPrice(symbol).then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) diff --git a/__tests__/spot/market/exchangeInfo.test.js b/__tests__/spot/market/exchangeInfo.test.js index 53c86ef2..63929efc 100644 --- a/__tests__/spot/market/exchangeInfo.test.js +++ b/__tests__/spot/market/exchangeInfo.test.js @@ -1,6 +1,6 @@ /* global describe, it, expect */ const { nockMock, buildQueryString, SpotClient } = require('../../testUtils/testSetup') -const { mockResponse } = require('../../testUtils/mockData') +const { mockResponse, timeUnit } = require('../../testUtils/mockData') describe('#exchangeInfo', () => { it('should return exchange info', () => { @@ -12,6 +12,15 @@ describe('#exchangeInfo', () => { }) }) + it('should return exchange info with timeUnit specified', () => { + nockMock('/api/v3/exchangeInfo')(mockResponse) + + return SpotClient.exchangeInfo({ timeUnit }).then(response => { + expect(response).toBeDefined() + expect(response.data).toEqual(mockResponse) + }) + }) + it('should return exchange info with symbol specified', () => { const symbol = 'BTCUSDT' nockMock(`/api/v3/exchangeInfo?symbol=${symbol}`)(mockResponse) diff --git a/__tests__/spot/market/historicalTrades.test.js b/__tests__/spot/market/historicalTrades.test.js index b4b250ba..d7fb2b33 100644 --- a/__tests__/spot/market/historicalTrades.test.js +++ b/__tests__/spot/market/historicalTrades.test.js @@ -1,7 +1,7 @@ /* global describe, it, expect */ const MissingParameterError = require('../../../src/error/missingParameterError') const { nockMock, SpotClient } = require('../../testUtils/testSetup') -const { mockResponse } = require('../../testUtils/mockData') +const { mockResponse, timeUnit } = require('../../testUtils/mockData') describe('#historicalTrades', () => { describe('throw MissingParameterError', () => { @@ -16,6 +16,16 @@ describe('#historicalTrades', () => { const symbol = 'BTCUSDT' nockMock(`/api/v3/historicalTrades?symbol=${symbol}`)(mockResponse) + return SpotClient.historicalTrades(symbol, { timeUnit }).then(response => { + expect(response).toBeDefined() + expect(response.data).toEqual(mockResponse) + }) + }) + + it('should return historical trades without optional parameters', () => { + const symbol = 'BTCUSDT' + nockMock(`/api/v3/historicalTrades?symbol=${symbol}`)(mockResponse) + return SpotClient.historicalTrades(symbol).then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) diff --git a/__tests__/spot/market/klines.test.js b/__tests__/spot/market/klines.test.js index a4c6635e..e4042999 100644 --- a/__tests__/spot/market/klines.test.js +++ b/__tests__/spot/market/klines.test.js @@ -1,7 +1,7 @@ /* global describe, it, expect */ const MissingParameterError = require('../../../src/error/missingParameterError') const { nockMock, SpotClient } = require('../../testUtils/testSetup') -const { mockResponse } = require('../../testUtils/mockData') +const { mockResponse, timeUnit } = require('../../testUtils/mockData') describe('#klines', () => { describe('throw MissingParameterError', () => { @@ -23,6 +23,17 @@ describe('#klines', () => { const interval = '1m' nockMock(`/api/v3/klines?symbol=${symbol}&interval=${interval}`)(mockResponse) + return SpotClient.klines(symbol, interval, { timeUnit }).then(response => { + expect(response).toBeDefined() + expect(response.data).toEqual(mockResponse) + }) + }) + + it('should return klines without optional parameters', () => { + const symbol = 'BTCUSDT' + const interval = '1m' + nockMock(`/api/v3/klines?symbol=${symbol}&interval=${interval}`)(mockResponse) + return SpotClient.klines(symbol, interval).then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) diff --git a/__tests__/spot/market/rollingWindowTicker.test.js b/__tests__/spot/market/rollingWindowTicker.test.js index fd2ff1d9..abb040e6 100644 --- a/__tests__/spot/market/rollingWindowTicker.test.js +++ b/__tests__/spot/market/rollingWindowTicker.test.js @@ -1,6 +1,6 @@ /* global describe, it, expect */ const { nockMock, buildQueryString, SpotClient } = require('../../testUtils/testSetup') -const { mockResponse } = require('../../testUtils/mockData') +const { mockResponse, timeUnit } = require('../../testUtils/mockData') describe('#rollingWindowTicker', () => { it('should return ticker for selective pairs', () => { @@ -33,4 +33,14 @@ describe('#rollingWindowTicker', () => { expect(response.data).toEqual(mockResponse) }) }) + + it('should return ticker price for specific unit of time', () => { + const symbol = 'BTCUSDT' + nockMock(`/api/v3/ticker?symbol=${symbol}`)(mockResponse) + + return SpotClient.rollingWindowTicker(symbol, [], { timeUnit }).then(response => { + expect(response).toBeDefined() + expect(response.data).toEqual(mockResponse) + }) + }) }) diff --git a/__tests__/spot/market/ticker24hr.test.js b/__tests__/spot/market/ticker24hr.test.js index 43456a65..9518e2ac 100644 --- a/__tests__/spot/market/ticker24hr.test.js +++ b/__tests__/spot/market/ticker24hr.test.js @@ -1,6 +1,6 @@ /* global describe, it, expect */ const { nockMock, buildQueryString, SpotClient } = require('../../testUtils/testSetup') -const { mockResponse } = require('../../testUtils/mockData') +const { mockResponse, timeUnit } = require('../../testUtils/mockData') const DEFAULT_TYPE = 'FULL' describe('#ticker24hr', () => { @@ -13,6 +13,15 @@ describe('#ticker24hr', () => { }) }) + it('should return 24hr price for all pairs with optional parameters', () => { + nockMock(`/api/v3/ticker/24hr?type=${DEFAULT_TYPE}`)(mockResponse) + + return SpotClient.ticker24hr('', [], 'FULL', { timeUnit }).then(response => { + expect(response).toBeDefined() + expect(response.data).toEqual(mockResponse) + }) + }) + it('should return 24hr price for selective pairs', () => { const symbols = ['BTCUSDT', 'BNBUSDT'] nockMock(`/api/v3/ticker/24hr?${buildQueryString({ symbols, type: DEFAULT_TYPE })}`)(mockResponse) diff --git a/__tests__/spot/market/time.test.js b/__tests__/spot/market/time.test.js index c8f64491..1181abbc 100644 --- a/__tests__/spot/market/time.test.js +++ b/__tests__/spot/market/time.test.js @@ -1,11 +1,20 @@ /* global describe, it, expect */ const { nockMock, SpotClient } = require('../../testUtils/testSetup') -const { mockResponse } = require('../../testUtils/mockData') +const { mockResponse, timeUnit } = require('../../testUtils/mockData') describe('#time', () => { it('should return server time', () => { nockMock('/api/v3/time')(mockResponse) + return SpotClient.time({ timeUnit }).then(response => { + expect(response).toBeDefined() + expect(response.data).toEqual(mockResponse) + }) + }) + + it('should return server time without optional parameters', () => { + nockMock('/api/v3/time')(mockResponse) + return SpotClient.time().then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) diff --git a/__tests__/spot/market/trades.test.js b/__tests__/spot/market/trades.test.js index ea65f1b6..634a7140 100644 --- a/__tests__/spot/market/trades.test.js +++ b/__tests__/spot/market/trades.test.js @@ -1,7 +1,7 @@ /* global describe, it, expect */ const MissingParameterError = require('../../../src/error/missingParameterError') const { nockMock, SpotClient } = require('../../testUtils/testSetup') -const { mockResponse } = require('../../testUtils/mockData') +const { mockResponse, timeUnit } = require('../../testUtils/mockData') describe('#trades', () => { describe('throw MissingParameterError', () => { @@ -16,6 +16,16 @@ describe('#trades', () => { const symbol = 'BTCUSDT' nockMock(`/api/v3/trades?symbol=${symbol}`)(mockResponse) + return SpotClient.trades(symbol, { timeUnit }).then(response => { + expect(response).toBeDefined() + expect(response.data).toEqual(mockResponse) + }) + }) + + it('should return trades without optional parameters', () => { + const symbol = 'BTCUSDT' + nockMock(`/api/v3/trades?symbol=${symbol}`)(mockResponse) + return SpotClient.trades(symbol).then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) diff --git a/__tests__/spot/market/uiklines.test.js b/__tests__/spot/market/uiklines.test.js index 75d21d81..7f2f0a8e 100644 --- a/__tests__/spot/market/uiklines.test.js +++ b/__tests__/spot/market/uiklines.test.js @@ -1,7 +1,7 @@ /* global describe, it, expect */ const MissingParameterError = require('../../../src/error/missingParameterError') const { nockMock, SpotClient } = require('../../testUtils/testSetup') -const { mockResponse } = require('../../testUtils/mockData') +const { mockResponse, timeUnit } = require('../../testUtils/mockData') describe('#uiklines', () => { describe('throw MissingParameterError', () => { @@ -23,6 +23,17 @@ describe('#uiklines', () => { const interval = '1m' nockMock(`/api/v3/uiKlines?symbol=${symbol}&interval=${interval}`)(mockResponse) + return SpotClient.uiklines(symbol, interval, { timeUnit }).then(response => { + expect(response).toBeDefined() + expect(response.data).toEqual(mockResponse) + }) + }) + + it('should return uiklines without optional parameters', () => { + const symbol = 'BTCUSDT' + const interval = '1m' + nockMock(`/api/v3/uiKlines?symbol=${symbol}&interval=${interval}`)(mockResponse) + return SpotClient.uiklines(symbol, interval).then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) diff --git a/__tests__/spot/stream/closeListenKey.test.js b/__tests__/spot/stream/closeListenKey.test.js index 3ac1a344..a2832cbb 100644 --- a/__tests__/spot/stream/closeListenKey.test.js +++ b/__tests__/spot/stream/closeListenKey.test.js @@ -1,7 +1,7 @@ /* global describe, it, expect */ const MissingParameterError = require('../../../src/error/missingParameterError') const { nockDeleteMock, SpotClient } = require('../../testUtils/testSetup') -const { mockResponse } = require('../../testUtils/mockData') +const { mockResponse, timeUnit } = require('../../testUtils/mockData') describe('#closeListenKey', () => { it('missing listenKey', () => { @@ -13,6 +13,15 @@ describe('#closeListenKey', () => { it('should result of delete listen key', () => { nockDeleteMock('/api/v3/userDataStream?listenKey=aaa')(mockResponse) + return SpotClient.closeListenKey('aaa', { timeUnit }).then(response => { + expect(response).toBeDefined() + expect(response.data).toEqual(mockResponse) + }) + }) + + it('should result of delete listen key without optional parameters', () => { + nockDeleteMock('/api/v3/userDataStream?listenKey=aaa')(mockResponse) + return SpotClient.closeListenKey('aaa').then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) diff --git a/__tests__/spot/stream/createListenKey.test.js b/__tests__/spot/stream/createListenKey.test.js index b1db0842..28052538 100644 --- a/__tests__/spot/stream/createListenKey.test.js +++ b/__tests__/spot/stream/createListenKey.test.js @@ -1,11 +1,20 @@ /* global describe, it, expect */ const { nockPostMock, SpotClient } = require('../../testUtils/testSetup') -const { mockResponse } = require('../../testUtils/mockData') +const { mockResponse, timeUnit } = require('../../testUtils/mockData') describe('#createListenKey', () => { it('should return listen key', () => { nockPostMock('/api/v3/userDataStream')(mockResponse) + return SpotClient.createListenKey({ timeUnit }).then(response => { + expect(response).toBeDefined() + expect(response.data).toEqual(mockResponse) + }) + }) + + it('should return listen key without optional parameters', () => { + nockPostMock('/api/v3/userDataStream')(mockResponse) + return SpotClient.createListenKey().then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) diff --git a/__tests__/spot/stream/renewListenKey.test.js b/__tests__/spot/stream/renewListenKey.test.js index 604e1872..d22d5999 100644 --- a/__tests__/spot/stream/renewListenKey.test.js +++ b/__tests__/spot/stream/renewListenKey.test.js @@ -1,7 +1,7 @@ /* global describe, it, expect */ const MissingParameterError = require('../../../src/error/missingParameterError') const { nockPutMock, SpotClient } = require('../../testUtils/testSetup') -const { mockResponse } = require('../../testUtils/mockData') +const { mockResponse, timeUnit } = require('../../testUtils/mockData') describe('#renewListenKey', () => { it('missing listenKey', () => { @@ -13,6 +13,15 @@ describe('#renewListenKey', () => { it('should renew listen key', () => { nockPutMock('/api/v3/userDataStream?listenKey=aaa')(mockResponse) + return SpotClient.renewListenKey('aaa', { timeUnit }).then(response => { + expect(response).toBeDefined() + expect(response.data).toEqual(mockResponse) + }) + }) + + it('should renew listen key without optional parameters', () => { + nockPutMock('/api/v3/userDataStream?listenKey=aaa')(mockResponse) + return SpotClient.renewListenKey('aaa').then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) diff --git a/__tests__/spot/trade/account.test.js b/__tests__/spot/trade/account.test.js index ab0f4cda..b36a9ca0 100644 --- a/__tests__/spot/trade/account.test.js +++ b/__tests__/spot/trade/account.test.js @@ -1,11 +1,20 @@ /* global describe, it, expect */ const { nockMock, SpotClient } = require('../../testUtils/testSetup') -const { mockResponse } = require('../../testUtils/mockData') +const { mockResponse, timeUnit } = require('../../testUtils/mockData') describe('#account', () => { it('should return account info', () => { nockMock('/api/v3/account')(mockResponse) + return SpotClient.account({ timeUnit }).then(response => { + expect(response).toBeDefined() + expect(response.data).toEqual(mockResponse) + }) + }) + + it('should return account info without optional parameters', () => { + nockMock('/api/v3/account')(mockResponse) + return SpotClient.account().then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) diff --git a/__tests__/spot/trade/allOrders.test.js b/__tests__/spot/trade/allOrders.test.js index 12c4bc3b..6db39d74 100644 --- a/__tests__/spot/trade/allOrders.test.js +++ b/__tests__/spot/trade/allOrders.test.js @@ -9,7 +9,8 @@ const { const { mockResponse, symbol, - orderId + orderId, + timeUnit } = require('../../testUtils/mockData') describe('#allOrders', () => { @@ -25,7 +26,7 @@ describe('#allOrders', () => { } nockMock(`/api/v3/allOrders?${buildQueryString({ symbol, ...parameters })}`)(mockResponse) - return SpotClient.allOrders(symbol, parameters).then(response => { + return SpotClient.allOrders(symbol, { ...parameters, timeUnit }).then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) }) diff --git a/__tests__/spot/trade/cancelAndReplace.test.js b/__tests__/spot/trade/cancelAndReplace.test.js index 3e79c8c5..3cd6a127 100644 --- a/__tests__/spot/trade/cancelAndReplace.test.js +++ b/__tests__/spot/trade/cancelAndReplace.test.js @@ -12,7 +12,8 @@ const { side, type, quantity, - price + price, + timeUnit } = require('../../testUtils/mockData') const cancelReplaceMode = 'STOP_ON_FAILURE' @@ -53,7 +54,7 @@ describe('#cancelAndReplace', () => { } nockPostMock(`/api/v3/order/cancelReplace?${buildQueryString({ symbol, side, type, cancelReplaceMode, ...parameters })}`)(mockResponse) - return SpotClient.cancelAndReplace(symbol, side, type, cancelReplaceMode, parameters).then(response => { + return SpotClient.cancelAndReplace(symbol, side, type, cancelReplaceMode, { ...parameters, timeUnit }).then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) }) diff --git a/__tests__/spot/trade/cancelOCOOrder.test.js b/__tests__/spot/trade/cancelOCOOrder.test.js index e1bb585f..0c491d18 100644 --- a/__tests__/spot/trade/cancelOCOOrder.test.js +++ b/__tests__/spot/trade/cancelOCOOrder.test.js @@ -9,7 +9,8 @@ const { const { mockResponse, symbol, - recvWindow + recvWindow, + timeUnit } = require('../../testUtils/mockData') describe('#cancelOCOOrder', () => { @@ -26,7 +27,7 @@ describe('#cancelOCOOrder', () => { } nockDeleteMock(`/api/v3/orderList?${buildQueryString({ symbol, ...parameters })}`)(mockResponse) - return SpotClient.cancelOCOOrder(symbol, parameters).then(response => { + return SpotClient.cancelOCOOrder(symbol, { ...parameters, timeUnit }).then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) }) diff --git a/__tests__/spot/trade/cancelOpenOrders.test.js b/__tests__/spot/trade/cancelOpenOrders.test.js index 4460c62e..b6884094 100644 --- a/__tests__/spot/trade/cancelOpenOrders.test.js +++ b/__tests__/spot/trade/cancelOpenOrders.test.js @@ -9,7 +9,8 @@ const { const { mockResponse, symbol, - recvWindow + recvWindow, + timeUnit } = require('../../testUtils/mockData') describe('#cancelOpenOrders', () => { @@ -25,7 +26,7 @@ describe('#cancelOpenOrders', () => { } nockDeleteMock(`/api/v3/openOrders?${buildQueryString({ symbol, ...parameters })}`)(mockResponse) - return SpotClient.cancelOpenOrders(symbol, parameters).then(response => { + return SpotClient.cancelOpenOrders(symbol, { ...parameters, timeUnit }).then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) }) diff --git a/__tests__/spot/trade/cancelOrder.test.js b/__tests__/spot/trade/cancelOrder.test.js index f23390a6..2c363dfa 100644 --- a/__tests__/spot/trade/cancelOrder.test.js +++ b/__tests__/spot/trade/cancelOrder.test.js @@ -10,7 +10,8 @@ const { mockResponse, symbol, orderId, - recvWindow + recvWindow, + timeUnit } = require('../../testUtils/mockData') describe('#cancelOrder', () => { @@ -27,7 +28,7 @@ describe('#cancelOrder', () => { } nockDeleteMock(`/api/v3/order?${buildQueryString({ symbol, ...parameters })}`)(mockResponse) - return SpotClient.cancelOrder(symbol, parameters).then(response => { + return SpotClient.cancelOrder(symbol, { ...parameters, timeUnit }).then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) }) diff --git a/__tests__/spot/trade/getOCOOrder.test.js b/__tests__/spot/trade/getOCOOrder.test.js index e66f568a..c282f1af 100644 --- a/__tests__/spot/trade/getOCOOrder.test.js +++ b/__tests__/spot/trade/getOCOOrder.test.js @@ -7,7 +7,8 @@ const { const { mockResponse, - recvWindow + recvWindow, + timeUnit } = require('../../testUtils/mockData') describe('#getOCOOrder', () => { @@ -27,7 +28,7 @@ describe('#getOCOOrder', () => { } nockMock(`/api/v3/orderList?${buildQueryString(parameters)}`)(mockResponse) - return SpotClient.getOCOOrder(parameters).then(response => { + return SpotClient.getOCOOrder({ ...parameters, timeUnit }).then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) }) diff --git a/__tests__/spot/trade/getOCOOrders.test.js b/__tests__/spot/trade/getOCOOrders.test.js index b56cd9c6..70692bc6 100644 --- a/__tests__/spot/trade/getOCOOrders.test.js +++ b/__tests__/spot/trade/getOCOOrders.test.js @@ -7,7 +7,8 @@ const { const { mockResponse, - recvWindow + recvWindow, + timeUnit } = require('../../testUtils/mockData') describe('#getOCOOrders', () => { @@ -27,7 +28,7 @@ describe('#getOCOOrders', () => { } nockMock(`/api/v3/allOrderList?${buildQueryString(parameters)}`)(mockResponse) - return SpotClient.getOCOOrders(parameters).then(response => { + return SpotClient.getOCOOrders({ ...parameters, timeUnit }).then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) }) diff --git a/__tests__/spot/trade/getOpenOCOOrders.test.js b/__tests__/spot/trade/getOpenOCOOrders.test.js index 1326dec6..4448b418 100644 --- a/__tests__/spot/trade/getOpenOCOOrders.test.js +++ b/__tests__/spot/trade/getOpenOCOOrders.test.js @@ -1,11 +1,20 @@ /* global describe, it, expect */ const { nockMock, SpotClient } = require('../../testUtils/testSetup') -const { mockResponse } = require('../../testUtils/mockData') +const { mockResponse, timeUnit } = require('../../testUtils/mockData') describe('#getOpenOCOOrders', () => { it('should return open oco order list', () => { nockMock('/api/v3/openOrderList')(mockResponse) + return SpotClient.getOpenOCOOrders({ timeUnit }).then(response => { + expect(response).toBeDefined() + expect(response.data).toEqual(mockResponse) + }) + }) + + it('should return open oco order list without optional parameters', () => { + nockMock('/api/v3/openOrderList')(mockResponse) + return SpotClient.getOpenOCOOrders().then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) diff --git a/__tests__/spot/trade/getOrder.test.js b/__tests__/spot/trade/getOrder.test.js index ddb306ae..9cfb2086 100644 --- a/__tests__/spot/trade/getOrder.test.js +++ b/__tests__/spot/trade/getOrder.test.js @@ -9,7 +9,8 @@ const { const { mockResponse, symbol, - orderId + orderId, + timeUnit } = require('../../testUtils/mockData') describe('#getOrder', () => { @@ -25,7 +26,7 @@ describe('#getOrder', () => { } nockMock(`/api/v3/order?${buildQueryString({ symbol, ...parameters })}`)(mockResponse) - return SpotClient.getOrder(symbol, parameters).then(response => { + return SpotClient.getOrder(symbol, { ...parameters, timeUnit }).then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) }) diff --git a/__tests__/spot/trade/myTrades.test.js b/__tests__/spot/trade/myTrades.test.js index 615ea92b..ca31243d 100644 --- a/__tests__/spot/trade/myTrades.test.js +++ b/__tests__/spot/trade/myTrades.test.js @@ -11,7 +11,8 @@ const { startTime, endTime, fromId, - limit + limit, + timeUnit } = require('../../testUtils/mockData') describe('#myTrades', () => { @@ -36,7 +37,7 @@ describe('#myTrades', () => { } nockMock(`/api/v3/myTrades?${buildQueryString({ ...parameters, symbol: 'BTCUSDT' })}`)(mockResponse) - return SpotClient.myTrades('BTCUSDT', parameters).then(response => { + return SpotClient.myTrades('BTCUSDT', { ...parameters, timeUnit }).then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) }) diff --git a/__tests__/spot/trade/newOCOOrder.test.js b/__tests__/spot/trade/newOCOOrder.test.js index df3202db..6eb0bbf5 100644 --- a/__tests__/spot/trade/newOCOOrder.test.js +++ b/__tests__/spot/trade/newOCOOrder.test.js @@ -12,7 +12,8 @@ const { side, quantity, aboveType, - belowType + belowType, + timeUnit } = require('../../testUtils/mockData') describe('#newOCOOrder', () => { @@ -58,7 +59,7 @@ describe('#newOCOOrder', () => { } nockPostMock(`/api/v3/orderList/oco?${buildQueryString({ symbol, side, quantity, aboveType, belowType, ...parameters })}`)(mockResponse) - return SpotClient.newOCOOrder(symbol, side, quantity, aboveType, belowType, parameters).then(response => { + return SpotClient.newOCOOrder(symbol, side, quantity, aboveType, belowType, { ...parameters, timeUnit }).then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) }) diff --git a/__tests__/spot/trade/newOrder.test.js b/__tests__/spot/trade/newOrder.test.js index 474640b7..9f414539 100644 --- a/__tests__/spot/trade/newOrder.test.js +++ b/__tests__/spot/trade/newOrder.test.js @@ -12,7 +12,8 @@ const { side, type, quantity, - price + price, + timeUnit } = require('../../testUtils/mockData') describe('#newOrder', () => { @@ -44,7 +45,7 @@ describe('#newOrder', () => { } nockPostMock(`/api/v3/order?${buildQueryString({ symbol, side, type, ...parameters })}`)(mockResponse) - return SpotClient.newOrder(symbol, side, type, parameters).then(response => { + return SpotClient.newOrder(symbol, side, type, { ...parameters, timeUnit }).then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) }) diff --git a/__tests__/spot/trade/newOrderTest.test.js b/__tests__/spot/trade/newOrderTest.test.js index 5e7eb2e1..845d4ab4 100644 --- a/__tests__/spot/trade/newOrderTest.test.js +++ b/__tests__/spot/trade/newOrderTest.test.js @@ -12,7 +12,8 @@ const { side, type, quantity, - price + price, + timeUnit } = require('../../testUtils/mockData') describe('#newOrderTest', () => { @@ -44,7 +45,7 @@ describe('#newOrderTest', () => { } nockPostMock(`/api/v3/order/test?${buildQueryString({ symbol, side, type, ...parameters })}`)(mockResponse) - return SpotClient.newOrderTest(symbol, side, type, parameters).then(response => { + return SpotClient.newOrderTest(symbol, side, type, { ...parameters, timeUnit }).then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) }) diff --git a/__tests__/spot/trade/openOrders.test.js b/__tests__/spot/trade/openOrders.test.js index 83b2b4fd..abe4bef9 100644 --- a/__tests__/spot/trade/openOrders.test.js +++ b/__tests__/spot/trade/openOrders.test.js @@ -8,7 +8,8 @@ const { const { mockResponse, symbol, - recvWindow + recvWindow, + timeUnit } = require('../../testUtils/mockData') describe('#openOrders', () => { @@ -28,7 +29,7 @@ describe('#openOrders', () => { } nockMock(`/api/v3/openOrders?${buildQueryString({ ...parameters })}`)(mockResponse) - return SpotClient.openOrders(parameters).then(response => { + return SpotClient.openOrders({ ...parameters, timeUnit }).then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) }) diff --git a/__tests__/spot/trade/orderCount.test.js b/__tests__/spot/trade/orderCount.test.js index 76ee19a1..7972b807 100644 --- a/__tests__/spot/trade/orderCount.test.js +++ b/__tests__/spot/trade/orderCount.test.js @@ -1,11 +1,20 @@ /* global describe, it, expect */ const { nockMock, SpotClient } = require('../../testUtils/testSetup') -const { mockResponse } = require('../../testUtils/mockData') +const { mockResponse, timeUnit } = require('../../testUtils/mockData') describe('#orderCount', () => { it('should return order count usage', () => { nockMock('/api/v3/rateLimit/order')(mockResponse) + return SpotClient.orderCount({ timeUnit }).then(response => { + expect(response).toBeDefined() + expect(response.data).toEqual(mockResponse) + }) + }) + + it('should return order count usage without optional parameters', () => { + nockMock('/api/v3/rateLimit/order')(mockResponse) + return SpotClient.orderCount().then(response => { expect(response).toBeDefined() expect(response.data).toEqual(mockResponse) diff --git a/__tests__/testUtils/mockData.js b/__tests__/testUtils/mockData.js index dabcdadf..603f65e8 100644 --- a/__tests__/testUtils/mockData.js +++ b/__tests__/testUtils/mockData.js @@ -1,3 +1,5 @@ +const TimeUnit = require('../../src/helpers/timeUnit') + const mockResponse = { key: 'value', foo: 'bar' } @@ -28,5 +30,6 @@ module.exports = { status: 'enabled', stopPrice: 1001.02, symbol: 'BNBUSDT', + timeUnit: TimeUnit.MICROSECOND, type: 'LIMIT' } diff --git a/examples/websocketAPI/spot/market/aggTrades.js b/examples/websocketAPI/spot/market/aggTrades.js index 4a9a1429..49156eeb 100644 --- a/examples/websocketAPI/spot/market/aggTrades.js +++ b/examples/websocketAPI/spot/market/aggTrades.js @@ -2,6 +2,7 @@ const { Console } = require('console') const WebsocketAPI = require('../../../../src/websocketAPI') +const TimeUnit = require('../../../../src/helpers/timeUnit') const logger = new Console({ stdout: process.stdout, stderr: process.stderr }) @@ -14,7 +15,7 @@ const callbacks = { message: data => logger.info(data) } -const websocketAPIClient = new WebsocketAPI(null, null, { logger, callbacks }) +const websocketAPIClient = new WebsocketAPI(null, null, { logger, callbacks, timeUnit: TimeUnit.MICROSECOND }) // disconnect after 20 seconds setTimeout(() => websocketAPIClient.disconnect(), 20000) diff --git a/examples/websocketAPI/spot/trade/cancelOCOOrder.js b/examples/websocketAPI/spot/trade/cancelOCOOrder.js index 728e1c66..87ab50b3 100644 --- a/examples/websocketAPI/spot/trade/cancelOCOOrder.js +++ b/examples/websocketAPI/spot/trade/cancelOCOOrder.js @@ -2,6 +2,7 @@ const { Console } = require('console') const WebsocketAPI = require('../../../../src/websocketAPI') +const TimeUnit = require('../../../../src/helpers/timeUnit') const logger = new Console({ stdout: process.stdout, stderr: process.stderr }) @@ -20,7 +21,7 @@ const callbacks = { message: data => logger.info(data) } -const websocketAPIClient = new WebsocketAPI(apiKey, apiSecret, { logger, callbacks, wsURL }) +const websocketAPIClient = new WebsocketAPI(apiKey, apiSecret, { logger, callbacks, wsURL, timeUnit: TimeUnit.MICROSECOND }) // disconnect after 20 seconds setTimeout(() => websocketAPIClient.disconnect(), 20000) diff --git a/examples/websocketAPI/spot/trade/cancelOpenOrders.js b/examples/websocketAPI/spot/trade/cancelOpenOrders.js index 0c62c783..53fbd7fd 100644 --- a/examples/websocketAPI/spot/trade/cancelOpenOrders.js +++ b/examples/websocketAPI/spot/trade/cancelOpenOrders.js @@ -2,6 +2,7 @@ const { Console } = require('console') const WebsocketAPI = require('../../../../src/websocketAPI') +const TimeUnit = require('../../../../src/helpers/timeUnit') const logger = new Console({ stdout: process.stdout, stderr: process.stderr }) @@ -18,7 +19,7 @@ const callbacks = { message: data => logger.info(data) } -const websocketAPIClient = new WebsocketAPI(apiKey, apiSecret, { logger, callbacks, wsURL }) +const websocketAPIClient = new WebsocketAPI(apiKey, apiSecret, { logger, callbacks, wsURL, timeUnit: TimeUnit.MICROSECOND }) // disconnect after 20 seconds setTimeout(() => websocketAPIClient.disconnect(), 20000) diff --git a/examples/websocketAPI/spot/trade/cancelOrder.js b/examples/websocketAPI/spot/trade/cancelOrder.js index e48c8edd..b840d8af 100644 --- a/examples/websocketAPI/spot/trade/cancelOrder.js +++ b/examples/websocketAPI/spot/trade/cancelOrder.js @@ -2,6 +2,7 @@ const { Console } = require('console') const WebsocketAPI = require('../../../../src/websocketAPI') +const TimeUnit = require('../../../../src/helpers/timeUnit') const logger = new Console({ stdout: process.stdout, stderr: process.stderr }) @@ -21,7 +22,7 @@ const callbacks = { message: data => logger.info(data) } -const websocketAPIClient = new WebsocketAPI(apiKey, apiSecret, { logger, callbacks, wsURL }) +const websocketAPIClient = new WebsocketAPI(apiKey, apiSecret, { logger, callbacks, wsURL, timeUnit: TimeUnit.MICROSECOND }) // disconnect after 20 seconds setTimeout(() => websocketAPIClient.disconnect(), 20000) diff --git a/examples/websocketAPI/spot/trade/cancelReplaceOrder.js b/examples/websocketAPI/spot/trade/cancelReplaceOrder.js index e394937d..72ca1e38 100644 --- a/examples/websocketAPI/spot/trade/cancelReplaceOrder.js +++ b/examples/websocketAPI/spot/trade/cancelReplaceOrder.js @@ -2,6 +2,7 @@ const { Console } = require('console') const WebsocketAPI = require('../../../../src/websocketAPI') +const TimeUnit = require('../../../../src/helpers/timeUnit') const logger = new Console({ stdout: process.stdout, stderr: process.stderr }) @@ -23,7 +24,7 @@ const callbacks = { message: data => logger.info(data) } -const websocketAPIClient = new WebsocketAPI(apiKey, apiSecret, { logger, callbacks, wsURL }) +const websocketAPIClient = new WebsocketAPI(apiKey, apiSecret, { logger, callbacks, wsURL, timeUnit: TimeUnit.MICROSECOND }) // disconnect after 20 seconds setTimeout(() => websocketAPIClient.disconnect(), 20000) diff --git a/examples/websocketStream/spot/aggTrade.js b/examples/websocketStream/spot/aggTrade.js index f1ab59a3..836e47f4 100644 --- a/examples/websocketStream/spot/aggTrade.js +++ b/examples/websocketStream/spot/aggTrade.js @@ -2,6 +2,7 @@ const { Console } = require('console') const WebsocketStream = require('../../../src/websocketStream') +const TimeUnit = require('../../../src/helpers/timeUnit') const logger = new Console({ stdout: process.stdout, stderr: process.stderr }) const callbacks = { @@ -10,7 +11,7 @@ const callbacks = { message: data => logger.info(data) } -const websocketStreamClient = new WebsocketStream({ logger, callbacks }) +const websocketStreamClient = new WebsocketStream({ logger, callbacks, timeUnit: TimeUnit.MICROSECOND }) websocketStreamClient.aggTrade('bnbusdt', callbacks) diff --git a/package-lock.json b/package-lock.json index 3ee21f0c..d3a73e0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@binance/connector", - "version": "3.5.0", + "version": "3.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@binance/connector", - "version": "3.5.0", + "version": "3.6.0", "license": "MIT", "dependencies": { "axios": "^1.7.4", @@ -2048,10 +2048,11 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", diff --git a/package.json b/package.json index 71c75cc4..20f724c6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@binance/connector", - "version": "3.5.0", + "version": "3.6.0", "description": "This is a lightweight library that works as a connector to the Binance public API.", "main": "src/index.js", "scripts": { diff --git a/src/APIBase.js b/src/APIBase.js index 929d8bd5..2a1faa83 100644 --- a/src/APIBase.js +++ b/src/APIBase.js @@ -1,7 +1,8 @@ 'use strict' const crypto = require('crypto') -const { removeEmptyValue, buildQueryString, createRequest, defaultLogger } = require('./helpers/utils') +const { removeEmptyValue, removeTimeUnit, buildQueryString, createRequest, defaultLogger } = require('./helpers/utils') +const { validateTimeUnit } = require('./helpers/validation') const ConnectorClientError = require('./error/connectorClientError') const PrivateKeyAlgo = require('./helpers/privateKeyAlgo') @@ -24,6 +25,9 @@ class APIBase { } publicRequest (method, path, params = {}) { + const timeUnit = params?.timeUnit + validateTimeUnit(timeUnit) + params = removeTimeUnit(params) params = removeEmptyValue(params) params = buildQueryString(params) if (params !== '') { @@ -36,11 +40,15 @@ class APIBase { apiKey: this.apiKey, timeout: this.timeout, proxy: this.proxy, - httpsAgent: this.httpsAgent + httpsAgent: this.httpsAgent, + timeUnit }) } signRequest (method, path, params = {}) { + const timeUnit = params?.timeUnit + validateTimeUnit(timeUnit) + params = removeTimeUnit(params) params = removeEmptyValue(params) const timestamp = Date.now() const queryString = buildQueryString({ ...params, timestamp }) @@ -76,7 +84,8 @@ class APIBase { apiKey: this.apiKey, timeout: this.timeout, proxy: this.proxy, - httpsAgent: this.httpsAgent + httpsAgent: this.httpsAgent, + timeUnit }) } } diff --git a/src/helpers/timeUnit.js b/src/helpers/timeUnit.js new file mode 100644 index 00000000..ace0b901 --- /dev/null +++ b/src/helpers/timeUnit.js @@ -0,0 +1,8 @@ +const TimeUnit = { + MILLISECOND: 'MILLISECOND', + millisecond: 'millisecond', + MICROSECOND: 'MICROSECOND', + microsecond: 'microsecond' +} + +module.exports = TimeUnit diff --git a/src/helpers/utils.js b/src/helpers/utils.js index a31d9066..b3590277 100644 --- a/src/helpers/utils.js +++ b/src/helpers/utils.js @@ -13,6 +13,12 @@ const removeEmptyValue = obj => { return obj } +const removeTimeUnit = obj => { + if (!(obj instanceof Object)) return {} + if ('timeUnit' in obj) delete obj.timeUnit + return obj +} + const isEmptyValue = input => { /** * Scope of empty value: falsy value (except for false and 0), @@ -47,8 +53,8 @@ const getRequestInstance = (config) => { } const createRequest = (config) => { - const { baseURL, apiKey, method, url, timeout, proxy, httpsAgent } = config - return getRequestInstance({ + const { baseURL, apiKey, method, url, timeout, proxy, httpsAgent, timeUnit } = config + const requestInstance = { baseURL, timeout, proxy, @@ -58,10 +64,15 @@ const createRequest = (config) => { 'X-MBX-APIKEY': apiKey, 'User-Agent': `${constants.appName}/${constants.appVersion}` } - }).request({ - method, - url - }) + } + if (timeUnit) { + requestInstance.headers['X-MBX-TIME-UNIT'] = timeUnit + } + return getRequestInstance(requestInstance) + .request({ + method, + url + }) } const flowRight = (...functions) => input => functions.reduceRight( @@ -82,6 +93,7 @@ const sortObject = obj => Object.keys(obj).sort().reduce((res, key) => { module.exports = { isEmptyValue, removeEmptyValue, + removeTimeUnit, buildQueryString, createRequest, flowRight, diff --git a/src/helpers/validation.js b/src/helpers/validation.js index a9928bcd..02d480f1 100644 --- a/src/helpers/validation.js +++ b/src/helpers/validation.js @@ -1,7 +1,9 @@ 'use strict' const { isEmptyValue } = require('./utils') +const TimeUnit = require('../helpers/timeUnit') const MissingParameterError = require('../error/missingParameterError') +const ConnectorClientError = require('../error/connectorClientError') const validateRequiredParameters = paramObject => { if (!paramObject || isEmptyValue(paramObject)) { throw new MissingParameterError() } @@ -22,7 +24,20 @@ const hasOneOfParameters = paramObject => { } } +const validateTimeUnit = timeUnit => { + if (!timeUnit) return + if ( + timeUnit !== TimeUnit.MILLISECOND && + timeUnit !== TimeUnit.MICROSECOND && + timeUnit !== TimeUnit.millisecond && + timeUnit !== TimeUnit.microsecond + ) { + throw new ConnectorClientError("timeUnit must be either 'MILLISECOND' or 'MICROSECOND'") + } +} + module.exports = { validateRequiredParameters, - hasOneOfParameters + hasOneOfParameters, + validateTimeUnit } diff --git a/src/index.js b/src/index.js index 81ba3b48..4638e2dd 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,7 @@ 'use strict' module.exports.PrivateKeyAlgo = require('./helpers/privateKeyAlgo') +module.exports.TimeUnit = require('./helpers/timeUnit') module.exports.Spot = require('./spot') module.exports.WebsocketStream = require('./websocketStream') module.exports.WebsocketAPI = require('./websocketAPI') diff --git a/src/modules/restful/autoInvest.js b/src/modules/restful/autoInvest.js index 8439692f..eaaf0605 100644 --- a/src/modules/restful/autoInvest.js +++ b/src/modules/restful/autoInvest.js @@ -110,7 +110,7 @@ const AutoInvest = superclass => class extends superclass { */ changePlanStatus (planId, status, options = {}) { validateRequiredParameters({ planId, status }) - return this.publicRequest( + return this.signRequest( 'POST', '/sapi/v1/lending/auto-invest/plan/edit-status', Object.assign(options, { @@ -133,7 +133,7 @@ const AutoInvest = superclass => class extends superclass { */ getListOfPlans (planType, options = {}) { validateRequiredParameters({ planType }) - return this.publicRequest( + return this.signRequest( 'GET', '/sapi/v1/lending/auto-invest/plan/list', Object.assign(options, { @@ -155,7 +155,7 @@ const AutoInvest = superclass => class extends superclass { * @param {number} [options.recvWindow] - The value cannot be greater than 60000 */ queryHoldingDetailsOfThePlan (options = {}) { - return this.publicRequest( + return this.signRequest( 'GET', '/sapi/v1/lending/auto-invest/plan/id', options @@ -180,7 +180,7 @@ const AutoInvest = superclass => class extends superclass { * @param {number} [options.recvWindow] - The value cannot be greater than 60000 */ querySubscriptionTransactionHistory (options = {}) { - return this.publicRequest( + return this.signRequest( 'GET', '/sapi/v1/lending/auto-invest/history/list', options diff --git a/src/modules/restful/market.js b/src/modules/restful/market.js index 82d63286..0da892ca 100644 --- a/src/modules/restful/market.js +++ b/src/modules/restful/market.js @@ -28,9 +28,12 @@ const Market = superclass => class extends superclass { * Test connectivity to the Rest API and get the current server time.
* {@link https://developers.binance.com/docs/binance-spot-api-docs/rest-api#check-server-time} * + * @param {object} [options] + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' + * */ - time () { - return this.publicRequest('GET', '/api/v3/time') + time (options = {}) { + return this.publicRequest('GET', '/api/v3/time', options) } /** @@ -44,6 +47,7 @@ const Market = superclass => class extends superclass { * @param {object} [options] * @param {string} [options.symbol] - symbol * @param {Array} [options.symbols] - an array of symbols + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' * */ exchangeInfo (options = {}) { @@ -95,6 +99,7 @@ const Market = superclass => class extends superclass { * @param {string} symbol * @param {object} [options] * @param {number} [options.limit] - Default 500; max 1000. + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ trades (symbol, options = {}) { validateRequiredParameters({ symbol }) @@ -120,6 +125,7 @@ const Market = superclass => class extends superclass { * @param {object} [options] * @param {number} [options.limit] - Default 500; max 1000. * @param {number} [options.fromId] - Trade id to fetch from. Default gets most recent trades. + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ historicalTrades (symbol, options = {}) { validateRequiredParameters({ symbol }) @@ -146,6 +152,7 @@ const Market = superclass => class extends superclass { * @param {number} [options.startTime] * @param {number} [options.endTime] * @param {number} [options.limit] - Default 500; max 1000. + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ aggTrades (symbol, options = {}) { validateRequiredParameters({ symbol }) @@ -172,6 +179,7 @@ const Market = superclass => class extends superclass { * @param {number} [options.startTime] * @param {number} [options.endTime] * @param {number} [options.limit] - Default 500; max 1000. + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ klines (symbol, interval, options = {}) { validateRequiredParameters({ symbol, interval }) @@ -198,6 +206,7 @@ const Market = superclass => class extends superclass { * @param {number} [options.startTime] * @param {number} [options.endTime] * @param {number} [options.limit] - Default 500; max 1000. + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ uiklines (symbol, interval, options = {}) { validateRequiredParameters({ symbol, interval }) @@ -220,13 +229,18 @@ const Market = superclass => class extends superclass { * {@link https://developers.binance.com/docs/binance-spot-api-docs/rest-api#current-average-price} * * @param {string} symbol + * @param {object} [options] + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ - avgPrice (symbol) { + avgPrice (symbol, options = {}) { validateRequiredParameters({ symbol }) return this.publicRequest( 'GET', - '/api/v3/avgPrice', { symbol: symbol.toUpperCase() } + '/api/v3/avgPrice', + Object.assign(options, { + symbol: symbol.toUpperCase() + }) ) } @@ -240,13 +254,20 @@ const Market = superclass => class extends superclass { * @param {string} [symbol] * @param {Array} [symbols] - an array of symbols * @param {string} [type] - "MINI" or "FULL" + * @param {object} [options] + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ - ticker24hr (symbol = '', symbols = [], type = 'FULL') { + ticker24hr (symbol = '', symbols = [], type = 'FULL', options = {}) { symbols = symbols.map(symbol => symbol.toUpperCase()) return this.publicRequest( 'GET', - '/api/v3/ticker/24hr', { symbol: symbol.toUpperCase(), symbols, type } + '/api/v3/ticker/24hr', + Object.assign(options, { + symbol: symbol.toUpperCase(), + symbols, + type + }) ) } @@ -260,12 +281,16 @@ const Market = superclass => class extends superclass { * @param {string} [symbol] * @param {Array} [symbols] - an array of symbols */ - tickerPrice (symbol = '', symbols = []) { + tickerPrice (symbol = '', symbols = [], options = {}) { symbols = symbols.map(symbol => symbol.toUpperCase()) return this.publicRequest( 'GET', - '/api/v3/ticker/price', { symbol: symbol.toUpperCase(), symbols } + '/api/v3/ticker/price', + Object.assign(options, { + symbol: symbol.toUpperCase(), + symbols + }) ) } @@ -285,7 +310,11 @@ const Market = superclass => class extends superclass { return this.publicRequest( 'GET', - '/api/v3/ticker/bookTicker', { symbol: symbol.toUpperCase(), symbols } + '/api/v3/ticker/bookTicker', + { + symbol: symbol.toUpperCase(), + symbols + } ) } @@ -311,6 +340,7 @@ const Market = superclass => class extends superclass { * @param {object} [options] * @param {string} [options.type] Supported values: FULL or MINI. * @param {number} [options.windowSize] - Defaults to 1d if no parameter provided. + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ rollingWindowTicker (symbol = '', symbols = [], options = {}) { symbols = symbols.map(symbol => symbol.toUpperCase()) diff --git a/src/modules/restful/stream.js b/src/modules/restful/stream.js index 50acaf63..a07f4fbc 100644 --- a/src/modules/restful/stream.js +++ b/src/modules/restful/stream.js @@ -14,11 +14,15 @@ const Stream = superclass => class extends superclass { * POST /api/v3/userDataStream
* * {@link https://developers.binance.com/docs/binance-spot-api-docs/rest-api#start-user-data-stream-user_stream} + * + * @param {object} [options] + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ - createListenKey () { + createListenKey (options = {}) { return this.publicRequest( 'POST', - '/api/v3/userDataStream' + '/api/v3/userDataStream', + options ) } @@ -30,13 +34,17 @@ const Stream = superclass => class extends superclass { * {@link https://developers.binance.com/docs/binance-spot-api-docs/rest-api#keepalive-user-data-stream-user_stream} * * @param {string} listenKey + * @param {object} [options] + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ - renewListenKey (listenKey) { + renewListenKey (listenKey, options = {}) { validateRequiredParameters({ listenKey }) return this.publicRequest( 'PUT', '/api/v3/userDataStream', - { listenKey } + Object.assign(options, { + listenKey + }) ) } @@ -48,13 +56,17 @@ const Stream = superclass => class extends superclass { * {@link https://developers.binance.com/docs/binance-spot-api-docs/rest-api#close-user-data-stream-user_stream} * * @param {string} listenKey + * @param {object} [options] + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ - closeListenKey (listenKey) { + closeListenKey (listenKey, options = {}) { validateRequiredParameters({ listenKey }) return this.publicRequest( 'DELETE', '/api/v3/userDataStream', - { listenKey } + Object.assign(options, { + listenKey + }) ) } diff --git a/src/modules/restful/subAccount.js b/src/modules/restful/subAccount.js index e729ef9c..54479033 100644 --- a/src/modules/restful/subAccount.js +++ b/src/modules/restful/subAccount.js @@ -344,7 +344,6 @@ const SubAccount = superclass => class extends superclass { *
2: transfer from subaccount's margin account to its spot account * @param {object} [options] * @param {number} [options.recvWindow] - The value cannot be greater than 60000 - */ subAccountMarginTransfer (email, asset, amount, type, options = {}) { validateRequiredParameters({ email, asset, amount, type }) @@ -427,7 +426,6 @@ const SubAccount = superclass => class extends superclass { * @param {number} [options.endTime] * @param {number} [options.limit] - Default 500 * @param {number} [options.recvWindow] - The value cannot be greater than 60000 - */ subAccountTransferSubAccountHistory (options = {}) { return this.signRequest( @@ -479,7 +477,6 @@ const SubAccount = superclass => class extends superclass { * @param {number} amount * @param {object} [options] * @param {number} [options.recvWindow] - The value cannot be greater than 60000 - */ subAccountFuturesAssetTransfer (fromEmail, toEmail, futuresType, asset, amount, options = {}) { validateRequiredParameters({ fromEmail, toEmail, futuresType, asset, amount }) diff --git a/src/modules/restful/trade.js b/src/modules/restful/trade.js index fec0158d..27108379 100644 --- a/src/modules/restful/trade.js +++ b/src/modules/restful/trade.js @@ -32,6 +32,7 @@ const Trade = superclass => class extends superclass { * @param {string} [options.newOrderRespType] - Set the response JSON. ACK, RESULT, or FULL; * MARKET and LIMIT order types default to FULL, all other orders default to ACK. * @param {number} [options.recvWindow] - The value cannot be greater than 60000 + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ newOrderTest (symbol, side, type, options = {}) { validateRequiredParameters({ symbol, side, type }) @@ -70,6 +71,7 @@ const Trade = superclass => class extends superclass { * @param {number} [options.icebergQty] * @param {string} [options.newOrderRespType] * @param {number} [options.recvWindow] - The value cannot be greater than 60000 + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ newOrder (symbol, side, type, options = {}) { validateRequiredParameters({ symbol, side, type }) @@ -98,6 +100,7 @@ const Trade = superclass => class extends superclass { * @param {string} [options.origClientOrderId] * @param {string} [options.newClientOrderId] * @param {number} [options.recvWindow] - The value cannot be greater than 60000 + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ cancelOrder (symbol, options = {}) { validateRequiredParameters({ symbol }) @@ -120,6 +123,7 @@ const Trade = superclass => class extends superclass { * @param {string} symbol * @param {object} [options] * @param {number} [options.recvWindow] - The value cannot be greater than 60000 + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ cancelOpenOrders (symbol, options = {}) { validateRequiredParameters({ symbol }) @@ -145,6 +149,7 @@ const Trade = superclass => class extends superclass { * @param {number} [options.orderId] * @param {string} [options.origClientOrderId] * @param {number} [options.recvWindow] - The value cannot be greater than 60000 + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ getOrder (symbol, options = {}) { validateRequiredParameters({ symbol }) @@ -190,6 +195,7 @@ const Trade = superclass => class extends superclass { * @param {number} [options.icebergQty] * @param {string} [options.newOrderRespType] * @param {number} [options.recvWindow] - The value cannot be greater than 60000 + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ cancelAndReplace (symbol, side, type, cancelReplaceMode, options = {}) { validateRequiredParameters({ symbol, side, type, cancelReplaceMode }) @@ -216,6 +222,7 @@ const Trade = superclass => class extends superclass { * @param {object} [options] * @param {string} [options.symbol] * @param {number} [options.recvWindow] - The value cannot be greater than 60000 + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ openOrders (options = {}) { return this.signRequest( @@ -239,6 +246,7 @@ const Trade = superclass => class extends superclass { * @param {number} [options.endTime] * @param {number} [options.limit] * @param {string} [options.recvWindow] - The value cannot be greater than 60000 + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ allOrders (symbol, options = {}) { validateRequiredParameters({ symbol }) @@ -284,6 +292,7 @@ const Trade = superclass => class extends superclass { * @param {string} [options.newOrderRespType] * @param {string} [options.selfTradePreventionMode] * @param {number} [options.recvWindow] - The value cannot be greater than 60000 + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ newOCOOrder (symbol, side, quantity, aboveType, belowType, options = {}) { validateRequiredParameters({ symbol, side, quantity, aboveType, belowType }) @@ -314,6 +323,7 @@ const Trade = superclass => class extends superclass { * @param {string} [options.listClientOrderId] * @param {string} [options.newClientOrderId] * @param {number} [options.recvWindow] - The value cannot be greater than 60000 + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ cancelOCOOrder (symbol, options = {}) { validateRequiredParameters({ symbol }) @@ -338,6 +348,7 @@ const Trade = superclass => class extends superclass { * @param {number} [options.orderListId] * @param {string} [options.origClientOrderId] * @param {number} [options.recvWindow] - The value cannot be greater than 60000 + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ getOCOOrder (options = {}) { return this.signRequest( @@ -360,6 +371,7 @@ const Trade = superclass => class extends superclass { * @param {number} [options.endTime] * @param {number} [options.limit] * @param {number} [options.recvWindow] - The value cannot be greater than 60000 + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ getOCOOrders (options = {}) { return this.signRequest( @@ -378,6 +390,7 @@ const Trade = superclass => class extends superclass { * * @param {object} [options] * @param {number} [options.recvWindow] - The value cannot be greater than 60000 + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ getOpenOCOOrders (options = {}) { return this.signRequest( @@ -396,6 +409,7 @@ const Trade = superclass => class extends superclass { * * @param {object} [options] * @param {number} [options.recvWindow] - The value cannot be greater than 60000 + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ account (options = {}) { return this.signRequest( @@ -420,6 +434,7 @@ const Trade = superclass => class extends superclass { * @param {number} [options.fromId] * @param {number} [options.limit] * @param {number} [options.recvWindow] - The value cannot be greater than 60000 + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ myTrades (symbol, options = {}) { validateRequiredParameters({ symbol }) @@ -442,6 +457,7 @@ const Trade = superclass => class extends superclass { * * @param {object} [options] * @param {number} [options.recvWindow] - The value cannot be greater than 60000 + * @param {string} [options.timeUnit] - The preferred time unit for time and timestamp fields. It can be either 'MILLISECOND' or 'MICROSECOND' */ orderCount (options = {}) { return this.signRequest( diff --git a/src/modules/restful/wallet.js b/src/modules/restful/wallet.js index b78c34ac..43745551 100644 --- a/src/modules/restful/wallet.js +++ b/src/modules/restful/wallet.js @@ -16,7 +16,10 @@ const Wallet = superclass => class extends superclass { * {@link https://developers.binance.com/docs/wallet/others/system-status} */ systemStatus () { - return this.publicRequest('GET', '/sapi/v1/system/status') + return this.publicRequest( + 'GET', + '/sapi/v1/system/status' + ) } /** diff --git a/src/websocketAPI.js b/src/websocketAPI.js index 55417bb3..e63098f5 100644 --- a/src/websocketAPI.js +++ b/src/websocketAPI.js @@ -3,18 +3,32 @@ const WebsocketBase = require('./websocketBase') const crypto = require('crypto') const WebSocketAPIModules = require('./modules/websocket/api') -const { randomString, removeEmptyValue, buildQueryString, sortObject } = require('./helpers/utils') -const { flowRight } = require('./helpers/utils') +const { randomString, removeEmptyValue, buildQueryString, sortObject, flowRight } = require('./helpers/utils') +const { validateTimeUnit } = require('./helpers/validation') class WebsocketAPI extends flowRight(...Object.values(WebSocketAPIModules))(WebsocketBase) { constructor (apiKey = '', apiSecret = '', options = {}) { super(options) this.wsURL = options.wsURL || 'wss://ws-api.binance.com:443/ws-api/v3' - this.initConnect(this.wsURL) + this.timeUnit = options.timeUnit + this.initConnect(this._prepareURL(this.wsURL)) this.apiKey = apiKey this.apiSecret = apiSecret } + _prepareURL (wsUrl) { + let url = wsUrl + if (this.timeUnit) { + try { + validateTimeUnit(this.timeUnit) + url = `${url}?timeUnit=${this.timeUnit}` + } catch (err) { + this.logger.error(err) + } + } + return url + } + sendMessageWithAPIKey (method, options = {}) { if (!this.isConnected()) { this.logger.error('Not connected') diff --git a/src/websocketStream.js b/src/websocketStream.js index a25a2402..e411d26b 100644 --- a/src/websocketStream.js +++ b/src/websocketStream.js @@ -2,12 +2,14 @@ const WebsocketBase = require('./websocketBase') const Stream = require('./modules/websocket/stream') +const { validateTimeUnit } = require('./helpers/validation') class WebsocketStream extends (Stream)(WebsocketBase) { constructor (options = {}) { super(options) this.wsURL = options.wsURL || 'wss://stream.binance.com:9443' this.combinedStreams = options.combinedStreams || false + this.timeUnit = options.timeUnit } _prepareURL (stream) { @@ -15,6 +17,14 @@ class WebsocketStream extends (Stream)(WebsocketBase) { if (this.combinedStreams) { url = `${this.wsURL}/stream?streams=${stream}` } + if (this.timeUnit) { + try { + validateTimeUnit(this.timeUnit) + url = `${url}${url.includes('?') ? '&' : '?'}timeUnit=${this.timeUnit}` + } catch (err) { + this.logger.error(err) + } + } return url }