bootstrap public spot test

This commit is contained in:
tiagosiebler
2022-05-05 22:45:38 +01:00
parent 0b8bed8faf
commit a7aaedac41
6 changed files with 126 additions and 41 deletions

View File

@@ -1,10 +1,11 @@
import { AxiosRequestConfig } from 'axios'; import { AxiosRequestConfig } from 'axios';
import { KlineInterval } from './types/shared'; import { APIResponse, KlineInterval } from './types/shared';
import { import {
NewSpotOrder, NewSpotOrder,
OrderSide, OrderSide,
OrderTypeSpot, OrderTypeSpot,
SpotOrderQueryById, SpotOrderQueryById,
SpotSymbolInfo,
} from './types/spot'; } from './types/spot';
import BaseRestClient from './util/BaseRestClient'; import BaseRestClient from './util/BaseRestClient';
import { getRestBaseUrl, RestClientOptions } from './util/requestUtils'; import { getRestBaseUrl, RestClientOptions } from './util/requestUtils';
@@ -55,18 +56,22 @@ export class SpotClient extends BaseRestClient {
* *
**/ **/
getSymbols() { getSymbols(): Promise<APIResponse<SpotSymbolInfo[]>> {
return this.get('/spot/v1/symbols'); return this.get('/spot/v1/symbols');
} }
getOrderBook(symbol: string, limit?: number) { getOrderBook(symbol: string, limit?: number): Promise<APIResponse<any>> {
return this.get('/spot/quote/v1/depth', { return this.get('/spot/quote/v1/depth', {
symbol, symbol,
limit, limit,
}); });
} }
getMergedOrderBook(symbol: string, scale?: number, limit?: number) { getMergedOrderBook(
symbol: string,
scale?: number,
limit?: number
): Promise<APIResponse<any>> {
return this.get('/spot/quote/v1/depth/merged', { return this.get('/spot/quote/v1/depth/merged', {
symbol, symbol,
scale, scale,
@@ -74,7 +79,7 @@ export class SpotClient extends BaseRestClient {
}); });
} }
getTrades(symbol: string, limit?: number) { getTrades(symbol: string, limit?: number): Promise<APIResponse<any[]>> {
return this.get('/spot/v1/trades', { return this.get('/spot/v1/trades', {
symbol, symbol,
limit, limit,
@@ -87,7 +92,7 @@ export class SpotClient extends BaseRestClient {
limit?: number, limit?: number,
startTime?: number, startTime?: number,
endTime?: number endTime?: number
) { ): Promise<APIResponse<any[]>> {
return this.get('/spot/quote/v1/kline', { return this.get('/spot/quote/v1/kline', {
symbol, symbol,
interval, interval,
@@ -97,15 +102,15 @@ export class SpotClient extends BaseRestClient {
}); });
} }
get24hrTicker(symbol?: string) { get24hrTicker(symbol?: string): Promise<APIResponse<any>> {
return this.get('/spot/quote/v1/ticker/24hr', { symbol }); return this.get('/spot/quote/v1/ticker/24hr', { symbol });
} }
getLastTradedPrice(symbol?: string) { getLastTradedPrice(symbol?: string): Promise<APIResponse<any>> {
return this.get('/spot/quote/v1/ticker/price', { symbol }); return this.get('/spot/quote/v1/ticker/price', { symbol });
} }
getBestBidAskPrice(symbol?: string) { getBestBidAskPrice(symbol?: string): Promise<APIResponse<any>> {
return this.get('/spot/quote/v1/ticker/book_ticker', { symbol }); return this.get('/spot/quote/v1/ticker/book_ticker', { symbol });
} }
@@ -171,4 +176,26 @@ export class SpotClient extends BaseRestClient {
getBalances() { getBalances() {
return this.getPrivate('/spot/v1/account'); return this.getPrivate('/spot/v1/account');
} }
/**
* Leveraged Token Endpoints
*/
getLeveragedTokenAssetInfo(
leverageTokenCode: string,
timestamp?: number
): Promise<APIResponse<any>> {
return this.get('/spot/lt/v1/info', {
ltCode: leverageTokenCode,
timestamp,
});
}
getLeveragedTokenMarketInfo(
leverageTokenCode: string
): Promise<APIResponse<any>> {
return this.get('/spot/lt/v1/reference', {
ltCode: leverageTokenCode,
});
}
} }

View File

@@ -18,14 +18,14 @@ export type numberInString = string;
export interface APIResponse<T> { export interface APIResponse<T> {
ret_code: number; ret_code: number;
ret_msg: 'OK' | string; ret_msg: 'OK' | string;
ext_code: string; ext_code: string | null;
ext_info: string; ext_info: string | null;
result: T; result: T;
} }
export interface APIResponseWithTime<T> extends APIResponse<T> { export interface APIResponseWithTime<T> extends APIResponse<T> {
/** UTC timestamp */ /** UTC timestamp */
time_now: string; time_now: numberInString;
} }
/** /**

View File

@@ -1,3 +1,5 @@
import { numberInString } from './shared';
export type OrderSide = 'Buy' | 'Sell'; export type OrderSide = 'Buy' | 'Sell';
export type OrderTypeSpot = 'LIMIT' | 'MARKET' | 'LIMIT_MAKER'; export type OrderTypeSpot = 'LIMIT' | 'MARKET' | 'LIMIT_MAKER';
export type OrderTimeInForce = 'GTC' | 'FOK' | 'IOC'; export type OrderTimeInForce = 'GTC' | 'FOK' | 'IOC';
@@ -16,3 +18,18 @@ export interface SpotOrderQueryById {
orderId?: string; orderId?: string;
orderLinkId?: 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;
}

View File

@@ -9,14 +9,13 @@ type WsTopicList = Set<WsTopic>;
interface WsStoredState { interface WsStoredState {
ws?: WebSocket; ws?: WebSocket;
connectionState?: WsConnectionState; connectionState?: WsConnectionState;
activePingTimer?: NodeJS.Timeout | undefined; activePingTimer?: ReturnType<typeof setTimeout> | undefined;
activePongTimer?: NodeJS.Timeout | undefined; activePongTimer?: ReturnType<typeof setTimeout> | undefined;
subscribedTopics: WsTopicList; subscribedTopics: WsTopicList;
}; }
export default class WsStore { export default class WsStore {
private wsState: Record<string, WsStoredState> private wsState: Record<string, WsStoredState>;
private logger: typeof DefaultLogger; private logger: typeof DefaultLogger;
constructor(logger: typeof DefaultLogger) { constructor(logger: typeof DefaultLogger) {
@@ -40,11 +39,14 @@ export default class WsStore {
create(key: string): WsStoredState | undefined { create(key: string): WsStoredState | undefined {
if (this.hasExistingActiveConnection(key)) { 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] = { this.wsState[key] = {
subscribedTopics: new Set(), subscribedTopics: new Set(),
connectionState: WsConnectionState.READY_STATE_INITIAL connectionState: WsConnectionState.READY_STATE_INITIAL,
}; };
return this.get(key); return this.get(key);
} }
@@ -52,7 +54,10 @@ export default class WsStore {
delete(key: string) { delete(key: string) {
if (this.hasExistingActiveConnection(key)) { if (this.hasExistingActiveConnection(key)) {
const ws = this.getWs(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(); ws?.close();
} }
delete this.wsState[key]; delete this.wsState[key];
@@ -70,7 +75,10 @@ export default class WsStore {
setWs(key: string, wsConnection: WebSocket): WebSocket { setWs(key: string, wsConnection: WebSocket): WebSocket {
if (this.isWsOpen(key)) { 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; this.get(key, true)!.ws = wsConnection;
return wsConnection; return wsConnection;
@@ -80,7 +88,10 @@ export default class WsStore {
isWsOpen(key: string): boolean { isWsOpen(key: string): boolean {
const existingConnection = this.getWs(key); const existingConnection = this.getWs(key);
return !!existingConnection && existingConnection.readyState === existingConnection.OPEN; return (
!!existingConnection &&
existingConnection.readyState === existingConnection.OPEN
);
} }
getConnectionState(key: string): WsConnectionState { getConnectionState(key: string): WsConnectionState {
@@ -102,7 +113,7 @@ export default class WsStore {
} }
getTopicsByKey(): Record<string, WsTopicList> { getTopicsByKey(): Record<string, WsTopicList> {
const result = {}; const result = {};
for (const refKey in this.wsState) { for (const refKey in this.wsState) {
result[refKey] = this.getTopics(refKey); result[refKey] = this.getTopics(refKey);
} }

View File

@@ -1,26 +1,19 @@
export function successResponseList(successMsg: string | null = 'OK') {
export function successResponseList() {
return { return {
"ext_code": "", result: expect.any(Array),
"ext_info": "", ret_code: 0,
"result": expect.any(Array), ret_msg: successMsg,
"ret_code": 0,
"ret_msg": "OK",
"time_now": expect.any(String),
}; };
}; }
export function successResponseObject() { export function successResponseObject(successMsg: string | null = 'OK') {
return { return {
"ext_code": "", result: expect.any(Object),
"ext_info": "", ret_code: 0,
"result": expect.any(Object), ret_msg: successMsg,
"ret_code": 0,
"ret_msg": "OK",
"time_now": expect.any(String),
}; };
}; }
export function notAuthenticatedError() { export function notAuthenticatedError() {
return new Error('Private endpoints require api and private keys set'); return new Error('Private endpoints require api and private keys set');
}; }

37
test/spot/public.test.ts Normal file
View File

@@ -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)
);
});
});