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:
@@ -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 |
|
||||
|
||||
42
examples/rest-spot-tpsl.ts
Normal file
42
examples/rest-spot-tpsl.ts
Normal 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
16
package-lock.json
generated
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user