From a7aaedac41436fd4e0273bcbe6a27dd2698db6b9 Mon Sep 17 00:00:00 2001 From: tiagosiebler Date: Thu, 5 May 2022 22:45:38 +0100 Subject: [PATCH] bootstrap public spot test --- src/spot-client.ts | 45 ++++++++++++++++++++++++++++++++-------- src/types/shared.ts | 6 +++--- src/types/spot.ts | 17 +++++++++++++++ src/util/WsStore.ts | 33 +++++++++++++++++++---------- test/response.util.ts | 29 ++++++++++---------------- test/spot/public.test.ts | 37 +++++++++++++++++++++++++++++++++ 6 files changed, 126 insertions(+), 41 deletions(-) create mode 100644 test/spot/public.test.ts diff --git a/src/spot-client.ts b/src/spot-client.ts index 4a9b266..647ac47 100644 --- a/src/spot-client.ts +++ b/src/spot-client.ts @@ -1,10 +1,11 @@ import { AxiosRequestConfig } from 'axios'; -import { KlineInterval } from './types/shared'; +import { APIResponse, KlineInterval } from './types/shared'; import { NewSpotOrder, OrderSide, OrderTypeSpot, SpotOrderQueryById, + SpotSymbolInfo, } from './types/spot'; import BaseRestClient from './util/BaseRestClient'; import { getRestBaseUrl, RestClientOptions } from './util/requestUtils'; @@ -55,18 +56,22 @@ export class SpotClient extends BaseRestClient { * **/ - getSymbols() { + getSymbols(): Promise> { return this.get('/spot/v1/symbols'); } - getOrderBook(symbol: string, limit?: number) { + getOrderBook(symbol: string, limit?: number): Promise> { return this.get('/spot/quote/v1/depth', { symbol, limit, }); } - getMergedOrderBook(symbol: string, scale?: number, limit?: number) { + getMergedOrderBook( + symbol: string, + scale?: number, + limit?: number + ): Promise> { return this.get('/spot/quote/v1/depth/merged', { symbol, scale, @@ -74,7 +79,7 @@ export class SpotClient extends BaseRestClient { }); } - getTrades(symbol: string, limit?: number) { + getTrades(symbol: string, limit?: number): Promise> { return this.get('/spot/v1/trades', { symbol, limit, @@ -87,7 +92,7 @@ export class SpotClient extends BaseRestClient { limit?: number, startTime?: number, endTime?: number - ) { + ): Promise> { return this.get('/spot/quote/v1/kline', { symbol, interval, @@ -97,15 +102,15 @@ export class SpotClient extends BaseRestClient { }); } - get24hrTicker(symbol?: string) { + get24hrTicker(symbol?: string): Promise> { return this.get('/spot/quote/v1/ticker/24hr', { symbol }); } - getLastTradedPrice(symbol?: string) { + getLastTradedPrice(symbol?: string): Promise> { return this.get('/spot/quote/v1/ticker/price', { symbol }); } - getBestBidAskPrice(symbol?: string) { + getBestBidAskPrice(symbol?: string): Promise> { return this.get('/spot/quote/v1/ticker/book_ticker', { symbol }); } @@ -171,4 +176,26 @@ export class SpotClient extends BaseRestClient { getBalances() { return this.getPrivate('/spot/v1/account'); } + + /** + * Leveraged Token Endpoints + */ + + getLeveragedTokenAssetInfo( + leverageTokenCode: string, + timestamp?: number + ): Promise> { + return this.get('/spot/lt/v1/info', { + ltCode: leverageTokenCode, + timestamp, + }); + } + + getLeveragedTokenMarketInfo( + leverageTokenCode: string + ): Promise> { + return this.get('/spot/lt/v1/reference', { + ltCode: leverageTokenCode, + }); + } } diff --git a/src/types/shared.ts b/src/types/shared.ts index 68baf69..7bc7e9d 100644 --- a/src/types/shared.ts +++ b/src/types/shared.ts @@ -18,14 +18,14 @@ export type numberInString = string; export interface APIResponse { ret_code: number; ret_msg: 'OK' | string; - ext_code: string; - ext_info: string; + ext_code: string | null; + ext_info: string | null; result: T; } export interface APIResponseWithTime extends APIResponse { /** UTC timestamp */ - time_now: string; + time_now: numberInString; } /** diff --git a/src/types/spot.ts b/src/types/spot.ts index 9e92081..a514686 100644 --- a/src/types/spot.ts +++ b/src/types/spot.ts @@ -1,3 +1,5 @@ +import { numberInString } from './shared'; + export type OrderSide = 'Buy' | 'Sell'; export type OrderTypeSpot = 'LIMIT' | 'MARKET' | 'LIMIT_MAKER'; export type OrderTimeInForce = 'GTC' | 'FOK' | 'IOC'; @@ -16,3 +18,18 @@ export interface SpotOrderQueryById { orderId?: string; orderLinkId?: string; } + +export interface SpotSymbolInfo { + name: string; + alias: string; + baseCurrency: string; + quoteCurrency: string; + basePrecision: numberInString; + quotePrecision: numberInString; + minTradeQuantity: numberInString; + minTradeAmount: numberInString; + minPricePrecision: numberInString; + maxTradeQuantity: numberInString; + maxTradeAmount: numberInString; + category: numberInString; +} diff --git a/src/util/WsStore.ts b/src/util/WsStore.ts index 8ebbd2c..995106c 100644 --- a/src/util/WsStore.ts +++ b/src/util/WsStore.ts @@ -9,14 +9,13 @@ type WsTopicList = Set; interface WsStoredState { ws?: WebSocket; connectionState?: WsConnectionState; - activePingTimer?: NodeJS.Timeout | undefined; - activePongTimer?: NodeJS.Timeout | undefined; + activePingTimer?: ReturnType | undefined; + activePongTimer?: ReturnType | undefined; subscribedTopics: WsTopicList; -}; - +} export default class WsStore { - private wsState: Record + private wsState: Record; private logger: typeof DefaultLogger; constructor(logger: typeof DefaultLogger) { @@ -40,11 +39,14 @@ export default class WsStore { create(key: string): WsStoredState | undefined { if (this.hasExistingActiveConnection(key)) { - this.logger.warning('WsStore setConnection() overwriting existing open connection: ', this.getWs(key)); + this.logger.warning( + 'WsStore setConnection() overwriting existing open connection: ', + this.getWs(key) + ); } this.wsState[key] = { subscribedTopics: new Set(), - connectionState: WsConnectionState.READY_STATE_INITIAL + connectionState: WsConnectionState.READY_STATE_INITIAL, }; return this.get(key); } @@ -52,7 +54,10 @@ export default class WsStore { delete(key: string) { if (this.hasExistingActiveConnection(key)) { const ws = this.getWs(key); - this.logger.warning('WsStore deleting state for connection still open: ', ws); + this.logger.warning( + 'WsStore deleting state for connection still open: ', + ws + ); ws?.close(); } delete this.wsState[key]; @@ -70,7 +75,10 @@ export default class WsStore { setWs(key: string, wsConnection: WebSocket): WebSocket { if (this.isWsOpen(key)) { - this.logger.warning('WsStore setConnection() overwriting existing open connection: ', this.getWs(key)); + this.logger.warning( + 'WsStore setConnection() overwriting existing open connection: ', + this.getWs(key) + ); } this.get(key, true)!.ws = wsConnection; return wsConnection; @@ -80,7 +88,10 @@ export default class WsStore { isWsOpen(key: string): boolean { const existingConnection = this.getWs(key); - return !!existingConnection && existingConnection.readyState === existingConnection.OPEN; + return ( + !!existingConnection && + existingConnection.readyState === existingConnection.OPEN + ); } getConnectionState(key: string): WsConnectionState { @@ -102,7 +113,7 @@ export default class WsStore { } getTopicsByKey(): Record { - const result = {}; + const result = {}; for (const refKey in this.wsState) { result[refKey] = this.getTopics(refKey); } diff --git a/test/response.util.ts b/test/response.util.ts index 6bc3974..312a67f 100644 --- a/test/response.util.ts +++ b/test/response.util.ts @@ -1,26 +1,19 @@ - -export function successResponseList() { +export function successResponseList(successMsg: string | null = 'OK') { return { - "ext_code": "", - "ext_info": "", - "result": expect.any(Array), - "ret_code": 0, - "ret_msg": "OK", - "time_now": expect.any(String), + result: expect.any(Array), + ret_code: 0, + ret_msg: successMsg, }; -}; +} -export function successResponseObject() { +export function successResponseObject(successMsg: string | null = 'OK') { return { - "ext_code": "", - "ext_info": "", - "result": expect.any(Object), - "ret_code": 0, - "ret_msg": "OK", - "time_now": expect.any(String), + result: expect.any(Object), + ret_code: 0, + ret_msg: successMsg, }; -}; +} export function notAuthenticatedError() { return new Error('Private endpoints require api and private keys set'); -}; +} diff --git a/test/spot/public.test.ts b/test/spot/public.test.ts new file mode 100644 index 0000000..ae8e2f3 --- /dev/null +++ b/test/spot/public.test.ts @@ -0,0 +1,37 @@ +import { SpotClient } from '../../src'; +import { + notAuthenticatedError, + successResponseList, + successResponseObject, +} from '../response.util'; + +describe('Public Spot REST API Endpoints', () => { + const useLivenet = true; + const api = new SpotClient(undefined, undefined, useLivenet, { + disable_time_sync: true, + }); + + const symbol = 'BTCUSDT'; + const interval = '15'; + const timestampOneHourAgo = new Date().getTime() / 1000 - 1000 * 60 * 60; + const from = Number(timestampOneHourAgo.toFixed(0)); + + it('should throw for unauthenticated private calls', async () => { + expect(() => api.getOpenOrders()).rejects.toMatchObject( + notAuthenticatedError() + ); + expect(() => api.getBalances()).rejects.toMatchObject( + notAuthenticatedError() + ); + }); + + it('getSymbols()', async () => { + expect(await api.getSymbols()).toMatchObject(successResponseList('')); + }); + + it('getOrderBook()', async () => { + expect(await api.getOrderBook(symbol)).toMatchObject( + successResponseObject(null) + ); + }); +});