diff --git a/README.md b/README.md index 2b0602a..035831d 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Node.js SDK for the Bybit APIs and WebSockets: - Complete integration with all Bybit APIs. - TypeScript support (with type declarations for most API requests & responses). -- Over 300 end-to-end tests making real API calls & WebSocket connections, validating any changes before they reach npm. +- Over 450 end-to-end tests making real API calls & WebSocket connections, validating any changes before they reach npm. - Robust WebSocket integration with configurable connection heartbeats & automatic reconnect then resubscribe workflows. - Browser support (via webpack bundle - see "Browser Usage" below). diff --git a/src/constants/enum.ts b/src/constants/enum.ts index 61bdd42..95d363f 100644 --- a/src/constants/enum.ts +++ b/src/constants/enum.ts @@ -44,9 +44,20 @@ export const API_ERROR_CODE = { POSITION_MODE_NOT_MODIFIED: 30083, ISOLATED_NOT_MODIFIED: 30084, RISK_LIMIT_NOT_EXISTS: 30090, + SUB_USER_ALREADY_EXISTS: 31005, LEVERAGE_NOT_MODIFIED: 34036, SAME_SLTP_MODE: 37002, COPY_TRADE_NOT_OPEN_ORDER: 39426, + V5_ORDER_NOT_FOUND: 110001, + V5_INSUFFICIENT_BALANCE: 110007, + V5_API_KEY_PERMISSION_DENIED: 10005, + V5_CROSS_ISOLATED_MARGIN_NOT_CHANGED: 110026, + V5_LEVERAGE_NOT_CHANGED: 110043, + V5_MARGIN_MODE_NOT_CHANGED: 110073, + V5_TPSL_NOT_CHANGED: 10001, + V5_RISK_ID_NOT_CHANGED: 10001, + V5_AUTO_ADD_MARGIN_NOT_CHANGED: 10001, + V5_TPSL_ERROR_NO_POSITION: 10001, QTY_EXCEEDS_MAX_LIMIT: 130006, ORDER_NOT_FOUND_OR_TOO_LATE_LINEAR: 130010, ORDER_COST_NOT_AVAILABLE: 130021, @@ -56,6 +67,7 @@ export const API_ERROR_CODE = { AUTO_ADD_MARGIN_NOT_MODIFIED: 130060, INSUFFICIENT_BALANCE_FOR_ORDER_COST_LINEAR: 130080, SAME_SLTP_MODE_LINEAR: 130150, + TRANSFER_ID_EXISTS: 131214, RISK_ID_NOT_MODIFIED: 134026, CONTRACT_ORDER_NOT_EXISTS: 140001, CONTRACT_INSUFFICIENT_BALANCE: 140007, @@ -63,7 +75,10 @@ export const API_ERROR_CODE = { CONTRACT_MARGIN_MODE_NOT_MODIFIED: 140026, CONTRACT_RISK_LIMIT_INFO_NOT_EXISTS: 140031, CONTRACT_SET_LEVERAGE_NOT_MODIFIED: 140043, + SUB_USER_NOT_FOUND: 141009, SPOT_LEVERAGE_TOKEN_ORDER_NOT_FOUND: 175007, + SPOT_MARGIN_NOT_ENABLED: 176008, + SPOT_MARGIN_QUESTIONNAIRE_NOT_SUBMIT: 176037, /** E.g. USDC Options trading, trying to access a symbol that is no longer active */ CONTRACT_NAME_NOT_EXIST: 3100111, ORDER_NOT_EXIST: 3100136, diff --git a/src/rest-client-v5.ts b/src/rest-client-v5.ts index e3a4577..4063e44 100644 --- a/src/rest-client-v5.ts +++ b/src/rest-client-v5.ts @@ -489,6 +489,8 @@ export class RestClientV5 extends BaseRestClient { /** * Query real-time position data, such as position size, cumulative realizedPNL. * + * 0: cross margin. 1: isolated margin + * * Unified account covers: Linear contract / Options * * Normal account covers: USDT perpetual / Inverse perpetual / Inverse futures @@ -516,6 +518,7 @@ export class RestClientV5 extends BaseRestClient { /** * Select cross margin mode or isolated margin mode. + * 0: cross margin. 1: isolated margin * * Covers: USDT perpetual (Normal account) / Inverse contract (Normal account). * diff --git a/src/types/request/v5-asset.ts b/src/types/request/v5-asset.ts index 8867c03..bc4d271 100644 --- a/src/types/request/v5-asset.ts +++ b/src/types/request/v5-asset.ts @@ -71,18 +71,6 @@ export interface GetUniversalTransferRecordsParamsV5 { cursor?: string; } -export interface UniversalTransferRecordV5 { - transferId: string; - coin: string; - amount: string; - fromMemberId: string; - toMemberId: string; - fromAccountType: AccountTypeV5; - toAccountType: AccountTypeV5; - timestamp: string; - status: string; -} - export interface GetAllowedDepositCoinInfoParamsV5 { coin?: string; chain?: string; @@ -123,7 +111,6 @@ export interface WithdrawParamsV5 { address: string; tag?: string; amount: string; - timestamp: number; forceChain?: number; accountType?: 'SPOT' | 'FUND'; } diff --git a/src/types/request/v5-position.ts b/src/types/request/v5-position.ts index ae9f923..6b22a7e 100644 --- a/src/types/request/v5-position.ts +++ b/src/types/request/v5-position.ts @@ -1,4 +1,10 @@ -import { CategoryV5, ExecTypeV5, PositionIdx, TPSLModeV5 } from '../v5-shared'; +import { + CategoryV5, + ExecTypeV5, + OrderTriggerByV5, + PositionIdx, + TPSLModeV5, +} from '../v5-shared'; export interface PositionInfoParamsV5 { category: CategoryV5; @@ -50,8 +56,8 @@ export interface SetTradingStopParamsV5 { takeProfit?: string; stopLoss?: string; trailingStop?: string; - tpTriggerBy?: string; - slTriggerBy?: string; + tpTriggerBy?: OrderTriggerByV5; + slTriggerBy?: OrderTriggerByV5; activePrice?: string; tpSize?: string; slSize?: string; diff --git a/src/types/response/v5-asset.ts b/src/types/response/v5-asset.ts index a56d166..51a9b03 100644 --- a/src/types/response/v5-asset.ts +++ b/src/types/response/v5-asset.ts @@ -79,6 +79,18 @@ export interface InternalTransferRecordV5 { status: string; } +export interface UniversalTransferRecordV5 { + transferId: string; + coin: string; + amount: string; + fromMemberId: string; + toMemberId: string; + fromAccountType: AccountTypeV5; + toAccountType: AccountTypeV5; + timestamp: string; + status: string; +} + export interface AllowedDepositCoinInfoV5 { coin: string; chain: string; diff --git a/test/contract/private.write.test.ts b/test/contract/private.write.test.ts index 2145a58..c917f29 100644 --- a/test/contract/private.write.test.ts +++ b/test/contract/private.write.test.ts @@ -89,8 +89,8 @@ describe('Private Contract REST API POST Endpoints', () => { await api.setMarginSwitch({ symbol, tradeMode: 1, - buyLeverage: '5', - sellLeverage: '5', + buyLeverage: '10', + sellLeverage: '10', }) ).toMatchObject({ retCode: API_ERROR_CODE.CONTRACT_MARGIN_MODE_NOT_MODIFIED, @@ -116,7 +116,7 @@ describe('Private Contract REST API POST Endpoints', () => { }); it('setLeverage()', async () => { - expect(await api.setLeverage(symbol, '5', '5')).toMatchObject({ + expect(await api.setLeverage(symbol, '10', '10')).toMatchObject({ retCode: API_ERROR_CODE.CONTRACT_SET_LEVERAGE_NOT_MODIFIED, }); }); diff --git a/test/linear/private.write.test.ts b/test/linear/private.write.test.ts index 1fce947..e0acba8 100644 --- a/test/linear/private.write.test.ts +++ b/test/linear/private.write.test.ts @@ -138,8 +138,8 @@ describe('Private Linear REST API POST Endpoints', () => { await api.setMarginSwitch({ symbol, is_isolated: true, - buy_leverage: 5, - sell_leverage: 5, + buy_leverage: 10, + sell_leverage: 10, }) ).toMatchObject({ ret_code: API_ERROR_CODE.ISOLATED_NOT_MODIFIED_LINEAR, @@ -186,8 +186,8 @@ describe('Private Linear REST API POST Endpoints', () => { expect( await api.setUserLeverage({ symbol, - buy_leverage: 5, - sell_leverage: 5, + buy_leverage: 10, + sell_leverage: 10, }) ).toMatchObject({ ret_code: API_ERROR_CODE.LEVERAGE_NOT_MODIFIED, @@ -206,7 +206,7 @@ describe('Private Linear REST API POST Endpoints', () => { }); }); - it('setRiskLimit()', async () => { + it.skip('setRiskLimit()', async () => { expect( await api.setRiskLimit({ symbol, diff --git a/test/rest-client-v5/private.read.test.ts b/test/rest-client-v5/private.read.test.ts index d6009a8..c2d5cb6 100644 --- a/test/rest-client-v5/private.read.test.ts +++ b/test/rest-client-v5/private.read.test.ts @@ -1,7 +1,7 @@ import { API_ERROR_CODE, RestClientV5 } from '../../src'; -import { SUCCESS_MSG_REGEX, successResponseObjectV3 } from '../response.util'; +import { successResponseObjectV3 } from '../response.util'; -describe('Private V5 REST API Endpoints', () => { +describe('Private READ V5 REST API Endpoints', () => { const API_KEY = process.env.API_KEY_COM; const API_SECRET = process.env.API_SECRET_COM; diff --git a/test/rest-client-v5/private.write.test.ts b/test/rest-client-v5/private.write.test.ts new file mode 100644 index 0000000..efeb8cb --- /dev/null +++ b/test/rest-client-v5/private.write.test.ts @@ -0,0 +1,448 @@ +import { + API_ERROR_CODE, + OrderSideV5, + OrderTypeV5, + RestClientV5, +} from '../../src'; +import { successResponseObjectV3 } from '../response.util'; + +describe('Private WRITE V5 REST API Endpoints', () => { + const API_KEY = process.env.API_KEY_COM; + const API_SECRET = process.env.API_SECRET_COM; + + it('should have api credentials to test with', () => { + expect(API_KEY).toStrictEqual(expect.any(String)); + expect(API_SECRET).toStrictEqual(expect.any(String)); + }); + + const api = new RestClientV5({ + key: API_KEY, + secret: API_SECRET, + testnet: false, + }); + + const settleCoin = 'USDT'; + const linearSymbol = 'BTCUSDT'; + const orderType: OrderTypeV5 = 'Market'; + const orderSide: OrderSideV5 = 'Buy'; + const fakeOrderId = 'fakeOrderId'; + + const fakeTransferId = '42c0cfb0-6bca-c242-bc76-4e6df6cbcb16'; + + describe('misc endpoints', () => { + it('fetchServerTime()', async () => { + expect(await api.fetchServerTime()).toEqual(expect.any(Number)); + }); + }); + + describe('Trade APIs', () => { + it('submitOrder()', async () => { + expect( + await api.submitOrder({ + category: 'linear', + symbol: linearSymbol, + orderType: orderType, + side: orderSide, + qty: '1', + positionIdx: 1, + }) + ).toMatchObject({ + // ...successResponseObjectV3(), + // retMsg: '', + retCode: API_ERROR_CODE.V5_INSUFFICIENT_BALANCE, + }); + }); + + it('amendOrder()', async () => { + expect( + await api.amendOrder({ + category: 'linear', + symbol: linearSymbol, + qty: '2', + orderId: fakeOrderId, + }) + ).toMatchObject({ + // ...successResponseObjectV3(), + // retMsg: '', + retCode: API_ERROR_CODE.V5_ORDER_NOT_FOUND, + }); + }); + + it('cancelOrder()', async () => { + expect( + await api.cancelOrder({ + category: 'linear', + symbol: linearSymbol, + orderId: fakeOrderId, + }) + ).toMatchObject({ + // ...successResponseObjectV3(), + // retMsg: '', + retCode: API_ERROR_CODE.V5_ORDER_NOT_FOUND, + }); + }); + + it('cancelAllOrders()', async () => { + expect( + await api.cancelAllOrders({ + category: 'linear', + settleCoin: settleCoin, + }) + ).toMatchObject({ + ...successResponseObjectV3(), + }); + }); + + it('batchSubmitOrders()', async () => { + expect( + await api.batchSubmitOrders('option', [ + { + orderLinkId: 'customOrderId1', + orderType: orderType, + qty: '1', + side: orderSide, + symbol: linearSymbol, + }, + { + orderLinkId: 'customOrderId2', + orderType: orderType, + qty: '2', + side: orderSide, + symbol: linearSymbol, + }, + ]) + ).toMatchObject({ + ...successResponseObjectV3(), + }); + }); + + it('batchAmendOrders()', async () => { + expect( + await api.batchAmendOrders('option', [ + { + orderLinkId: 'customOrderId1', + qty: '3', + symbol: linearSymbol, + }, + { + orderLinkId: 'customOrderId2', + qty: '4', + symbol: linearSymbol, + }, + ]) + ).toMatchObject({ + ...successResponseObjectV3(), + }); + }); + + it('batchCancelOrders()', async () => { + expect( + await api.batchCancelOrders('option', [ + { + orderLinkId: 'customOrderId1', + symbol: linearSymbol, + }, + { + orderLinkId: 'customOrderId2', + symbol: linearSymbol, + }, + ]) + ).toMatchObject({ + ...successResponseObjectV3(), + }); + }); + + it('setDisconnectCancelAllWindow()', async () => { + expect(await api.setDisconnectCancelAllWindow('option', 5)).toMatchObject( + { + // ...successResponseObjectV3(), + // retMsg: '', + retCode: API_ERROR_CODE.V5_API_KEY_PERMISSION_DENIED, + } + ); + }); + }); + + describe('Position APIs', () => { + it('setLeverage()', async () => { + expect( + await api.setLeverage({ + category: 'linear', + buyLeverage: '10', + sellLeverage: '10', + symbol: linearSymbol, + }) + ).toMatchObject({ + // ...successResponseObjectV3(), + // retMsg: '', + retCode: API_ERROR_CODE.V5_LEVERAGE_NOT_CHANGED, + }); + }); + + it('switchIsolatedMargin()', async () => { + expect( + await api.switchIsolatedMargin({ + category: 'linear', + buyLeverage: '10', + sellLeverage: '10', + symbol: linearSymbol, + // isolated + tradeMode: 1, + }) + ).toMatchObject({ + // ...successResponseObjectV3(), + // retMsg: '', + retCode: API_ERROR_CODE.V5_CROSS_ISOLATED_MARGIN_NOT_CHANGED, + }); + }); + + it('setTPSLMode()', async () => { + expect( + await api.setTPSLMode({ + category: 'linear', + symbol: linearSymbol, + tpSlMode: 'Full', + }) + ).toMatchObject({ + // ...successResponseObjectV3(), + // retMsg: '', + retCode: API_ERROR_CODE.V5_TPSL_NOT_CHANGED, + }); + }); + + it('switchPositionMode()', async () => { + expect( + await api.switchPositionMode({ + category: 'linear', + // both sides + mode: 3, + coin: settleCoin, + }) + ).toMatchObject({ + ...successResponseObjectV3(), + }); + }); + + it('setRiskLimit()', async () => { + expect( + await api.setRiskLimit({ + category: 'linear', + positionIdx: 1, + riskId: 1, + symbol: linearSymbol, + }) + ).toMatchObject({ + // ...successResponseObjectV3(), + // retMsg: '', + retCode: API_ERROR_CODE.V5_RISK_ID_NOT_CHANGED, + }); + }); + + it('setTradingStop()', async () => { + expect( + await api.setTradingStop({ + category: 'linear', + positionIdx: 1, + symbol: linearSymbol, + slSize: '100', + slTriggerBy: 'LastPrice', + stopLoss: '25000', + }) + ).toMatchObject({ + // ...successResponseObjectV3(), + // retMsg: '', + retCode: API_ERROR_CODE.V5_TPSL_ERROR_NO_POSITION, + }); + }); + + it('setAutoAddMargin()', async () => { + expect( + await api.setAutoAddMargin({ + category: 'linear', + autoAddMargin: 0, + symbol: linearSymbol, + positionIdx: 0, + }) + ).toMatchObject({ + // ...successResponseObjectV3(), + // retMsg: '', + retCode: API_ERROR_CODE.V5_AUTO_ADD_MARGIN_NOT_CHANGED, + }); + }); + }); + + describe('Account APIs', () => { + it('setMarginMode()', async () => { + expect(await api.setMarginMode('REGULAR_MARGIN')).toMatchObject({ + // ...successResponseObjectV3(), + // retMsg: '', + retCode: API_ERROR_CODE.V5_MARGIN_MODE_NOT_CHANGED, + }); + }); + + it('setMMP()', async () => { + expect( + await api.setMMP({ + baseCoin: settleCoin, + deltaLimit: '1', + frozenPeriod: '1', + qtyLimit: '1', + window: '1', + }) + ).toMatchObject({ + // ...successResponseObjectV3(), + // retMsg: '', + retCode: API_ERROR_CODE.INSTITION_MMP_PROFILE_NOT_FOUND, + }); + }); + + it.skip('resetMMP()', async () => { + expect(await api.resetMMP(settleCoin)).toMatchObject({ + ...successResponseObjectV3(), + retMsg: '', + retCode: 3500715, + // Contacted bybit for info + // + "retMsg": "Parameter window cannot be empty.", + }); + }); + }); + + describe('Asset APIs', () => { + it('createInternalTransfer()', async () => { + expect( + await api.createInternalTransfer( + fakeTransferId, + settleCoin, + '100', + 'SPOT', + 'CONTRACT' + ) + ).toMatchObject({ + // ...successResponseObjectV3(), + // retMsg: '', + retCode: API_ERROR_CODE.TRANSFER_ID_EXISTS, + }); + }); + + it('enableUniversalTransferForSubUIDs()', async () => { + expect(await api.enableUniversalTransferForSubUIDs([])).toMatchObject({ + ...successResponseObjectV3(), + }); + }); + + it('createUniversalTransfer()', async () => { + expect( + await api.createUniversalTransfer({ + amount: '100', + coin: settleCoin, + fromAccountType: 'SPOT', + fromMemberId: 1, + toAccountType: 'CONTRACT', + toMemberId: 2, + transferId: fakeTransferId, + }) + ).toMatchObject({ + // ...successResponseObjectV3(), + // retMsg: '', + retCode: API_ERROR_CODE.TRANSFER_ID_EXISTS, + }); + }); + + it('submitWithdrawal()', async () => { + expect( + await api.submitWithdrawal({ + address: '0x000000', + amount: '100', + chain: 'TRC20', + coin: settleCoin, + }) + ).toMatchObject({ + // ...successResponseObjectV3(), + // retMsg: '', + retCode: API_ERROR_CODE.INCORRECT_API_KEY_PERMISSIONS, + }); + }); + + it('cancelWithdrawal()', async () => { + expect(await api.cancelWithdrawal('fakeId')).toMatchObject({ + // ...successResponseObjectV3(), + // retMsg: '', + retCode: API_ERROR_CODE.INCORRECT_API_KEY_PERMISSIONS, + }); + }); + }); + + describe('User APIs', () => { + it('createSubMember()', async () => { + expect( + await api.createSubMember({ + memberType: 1, + username: 'sub1account', + switch: 1, + note: 'created via e2e test', + }) + ).toMatchObject({ + // ...successResponseObjectV3(), + // retMsg: '', + retCode: API_ERROR_CODE.SUB_USER_ALREADY_EXISTS, + }); + }); + + it('setSubUIDFrozenState()', async () => { + expect(await api.setSubUIDFrozenState(0, 1)).toMatchObject({ + // ...successResponseObjectV3(), + // retMsg: '', + retCode: API_ERROR_CODE.SUB_USER_NOT_FOUND, + }); + }); + }); + + // Not currently working. In touch with bybit. + describe.skip('Spot Leverage Token APIs', () => { + it('purchaseSpotLeveragedToken()', async () => { + expect( + await api.purchaseSpotLeveragedToken({ + ltAmount: '100', + ltCoin: 'EOS3L', + serialNo: 'purchase-001', + }) + ).toMatchObject({ + // ...successResponseObjectV3(), + retCode: 0, + retMsg: '', + }); + }); + + it('redeemSpotLeveragedToken()', async () => { + expect( + await api.redeemSpotLeveragedToken({ + quantity: '100', + ltCoin: 'EOS3L', + serialNo: 'redeem-001', + }) + ).toMatchObject({ + // ...successResponseObjectV3(), + retCode: 0, + retMsg: '', + }); + }); + }); + + describe('Spot Margin APIs', () => { + it('toggleSpotMarginTrade()', async () => { + expect(await api.toggleSpotMarginTrade('1')).toMatchObject({ + // ...successResponseObjectV3(), + // retMsg: '', + retCode: API_ERROR_CODE.SPOT_MARGIN_QUESTIONNAIRE_NOT_SUBMIT, + }); + }); + + it('setSpotMarginLeverage()', async () => { + expect(await api.setSpotMarginLeverage('2')).toMatchObject({ + // ...successResponseObjectV3(), + // retMsg: '', + retCode: API_ERROR_CODE.SPOT_MARGIN_NOT_ENABLED, + }); + }); + }); +});