Merge pull request #190 from tiagosiebler/spotv3sort

v3.1.3: fix(#187) rare signature failure for spotv3 GET with parameters
This commit is contained in:
Tiago
2022-10-24 19:52:36 +01:00
committed by GitHub
9 changed files with 105 additions and 20 deletions

View File

@@ -53,7 +53,7 @@ The version on npm is the output from the `build` command and can be used in pro
Each REST API group has a dedicated REST client. To avoid confusion, here are the available REST clients and the corresponding API groups:
| Class | Description |
|:------------------------------------------------------------------: |:----------------------------------------------------------------------------------------------------------------------------: |
| [ **Derivatives v3** ] | The Derivatves v3 APIs (successor to the Futures V2 APIs) |
| [ **Derivatives v3** ] | The Derivatives v3 APIs (successor to the Futures V2 APIs) |
| [UnifiedMarginClient](src/unified-margin-client.ts) | [Derivatives (v3) Unified Margin APIs](https://bybit-exchange.github.io/docs/derivativesV3/unified_margin/#t-introduction) |
| [ContractClient](src/contract-client.ts) | [Derivatives (v3) Contract APIs](https://bybit-exchange.github.io/docs/derivativesV3/contract). |
| [ **Futures v2** ] | The Futures v2 APIs |

View File

@@ -0,0 +1,42 @@
import { SpotClientV3 } from '../src/index';
// or
// import { SpotClientV3 } from 'bybit-api';
const symbol = 'BTCUSDT';
const key = process.env.API_KEY_COM;
const secret = process.env.API_SECRET_COM;
const client = new SpotClientV3({
key,
secret,
strict_param_validation: true,
});
(async () => {
try {
const orderId = undefined;
const ordersPerPage = undefined;
const orders = await client.getOpenOrders(symbol);
console.log('orders 1:', orders);
const normalOrders = await client.getOpenOrders(
symbol,
orderId,
ordersPerPage,
0
);
console.log('normal orders:', normalOrders);
const tpSlOrders = await client.getOpenOrders(
symbol,
orderId,
ordersPerPage,
1
);
console.log('tpSlOrders:', tpSlOrders);
} catch (e) {
console.error('request failed: ', e);
}
})();

16
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "bybit-api",
"version": "3.1.1",
"version": "3.1.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "bybit-api",
"version": "3.1.1",
"version": "3.1.3",
"license": "MIT",
"dependencies": {
"axios": "^0.21.0",
@@ -5897,9 +5897,9 @@
}
},
"node_modules/minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
@@ -12313,9 +12313,9 @@
"dev": true
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"

View File

@@ -1,6 +1,6 @@
{
"name": "bybit-api",
"version": "3.1.2",
"version": "3.1.3",
"description": "Complete & robust node.js SDK for Bybit's REST APIs and WebSockets, with TypeScript & integration tests.",
"main": "lib/index.js",
"types": "lib/index.d.ts",

View File

@@ -17,6 +17,7 @@ export const API_ERROR_CODE = {
/** This could mean bad request, incorrect value types or even incorrect/missing values */
PARAMS_MISSING_OR_WRONG: 10001,
INVALID_API_KEY_OR_PERMISSIONS: 10003,
SIGNATURE_NOT_VALID: 10004,
INCORRECT_API_KEY_PERMISSIONS: 10005,
INCORRECT_PRIVATE_OPERATIONS: 3303001,
/** Account not unified margin, update required */

View File

@@ -108,7 +108,7 @@ export default abstract class BaseRestClient {
}
}
private isSpotClient() {
private isSpotV1Client() {
return this.clientType === REST_CLIENT_TYPE_ENUM.spot;
}
@@ -233,7 +233,7 @@ export default abstract class BaseRestClient {
isPublicApi
);
if (method === 'GET' || this.isSpotClient()) {
if (method === 'GET' || this.isSpotV1Client()) {
return {
...options,
params: signResult.paramsWithSign,
@@ -343,13 +343,20 @@ export default abstract class BaseRestClient {
// usdc is different for some reason
if (signMethod === 'usdc') {
const sortProperties = false;
const signRequestParams =
method === 'GET'
? serializeParams(res.originalParams, strictParamValidation)
? serializeParams(
res.originalParams,
strictParamValidation,
sortProperties
)
: JSON.stringify(res.originalParams);
const paramsStr = timestamp + key + recvWindow + signRequestParams;
res.sign = await signMessage(paramsStr, this.secret);
// console.log('sign req: ', paramsStr);
return res;
}
@@ -360,16 +367,18 @@ export default abstract class BaseRestClient {
// Optional, set to 5000 by default. Increase if timestamp/recv_window errors are seen.
if (recvWindow) {
if (this.isSpotClient()) {
if (this.isSpotV1Client()) {
res.originalParams.recvWindow = recvWindow;
} else {
res.originalParams.recv_window = recvWindow;
}
}
const sortProperties = true;
res.serializedParams = serializeParams(
res.originalParams,
strictParamValidation
strictParamValidation,
sortProperties
);
res.sign = await signMessage(res.serializedParams, this.secret);
res.paramsWithSign = {

View File

@@ -30,12 +30,23 @@ export interface RestClientOptions {
parse_exceptions?: boolean;
}
/**
* Serialise a (flat) object into a query string
* @param params the object to serialise
* @param strict_validation throw if any properties are undefined
* @param sortProperties sort properties alphabetically before building a query string
* @returns the params object as a serialised string key1=value1&key2=value2&etc
*/
export function serializeParams(
params: object = {},
strict_validation = false
strict_validation = false,
sortProperties = true
): string {
return Object.keys(params)
.sort()
const properties = sortProperties
? Object.keys(params).sort()
: Object.keys(params);
return properties
.map((key) => {
const value = params[key];
if (strict_validation === true && typeof value === 'undefined') {

View File

@@ -12,10 +12,10 @@ export function successResponseList(successMsg: string | null = 'OK') {
export function successResponseListV3() {
return {
...successEmptyResponseObjectV3(),
result: {
list: expect.any(Array),
},
...successEmptyResponseObjectV3(),
};
}
@@ -36,8 +36,8 @@ export function successResponseObjectV3() {
export function successEmptyResponseObjectV3() {
return {
retCode: API_ERROR_CODE.SUCCESS,
retMsg: expect.stringMatching(SUCCESS_MSG_REGEX),
retCode: API_ERROR_CODE.SUCCESS,
};
}

View File

@@ -35,6 +35,28 @@ describe('Private Spot REST API GET Endpoints', () => {
expect(await api.getOpenOrders()).toMatchObject(successResponseListV3());
});
it('getOpenOrders() with symbol', async () => {
expect(await api.getOpenOrders(symbol)).toMatchObject(
successResponseListV3()
);
});
it('getOpenOrders() with order category', async () => {
const orderId = undefined;
const ordersPerPage = undefined;
// all these should succeed
expect(
await api.getOpenOrders(symbol, orderId, ordersPerPage)
).toMatchObject(successResponseListV3());
expect(
await api.getOpenOrders(symbol, orderId, ordersPerPage, 0)
).toMatchObject(successResponseListV3());
expect(
await api.getOpenOrders(symbol, orderId, ordersPerPage, 1)
).toMatchObject(successResponseListV3());
});
it('getPastOrders()', async () => {
expect(await api.getPastOrders()).toMatchObject(successResponseListV3());
});