diff --git a/src/coinm-client.ts b/src/coinm-client.ts index bbd580e8..938f52ba 100644 --- a/src/coinm-client.ts +++ b/src/coinm-client.ts @@ -91,9 +91,8 @@ export class CoinMClient extends BaseRestClient { constructor( restClientOptions: RestClientOptions = {}, requestOptions: AxiosRequestConfig = {}, - useTestnet?: boolean, ) { - const clientId = useTestnet ? 'coinmtest' : 'coinm'; + const clientId = restClientOptions.useTestnet ? 'coinmtest' : 'coinm'; super(clientId, restClientOptions, requestOptions); @@ -256,9 +255,7 @@ export class CoinMClient extends BaseRestClient { return this.getPrivate('dapi/v1/positionSide/dual'); } - submitNewOrder( - params: NewFuturesOrderParams, - ): Promise { + submitNewOrder(params: NewFuturesOrderParams): Promise { this.validateOrderId(params, 'newClientOrderId'); return this.postPrivate('dapi/v1/order', params); } diff --git a/src/main-client.ts b/src/main-client.ts index cd92b4b2..ea102754 100644 --- a/src/main-client.ts +++ b/src/main-client.ts @@ -207,7 +207,9 @@ export class MainClient extends BaseRestClient { restClientOptions: RestClientOptions = {}, requestOptions: AxiosRequestConfig = {}, ) { - super('spot1', restClientOptions, requestOptions); + const clientId = restClientOptions.useTestnet ? 'spottest' : 'spot1'; + + super(clientId, restClientOptions, requestOptions); return this; } diff --git a/src/types/shared.ts b/src/types/shared.ts index c336ef6b..24213d70 100644 --- a/src/types/shared.ts +++ b/src/types/shared.ts @@ -9,6 +9,7 @@ export type BooleanStringCapitalised = 'TRUE' | 'FALSE'; export type BinanceBaseUrlKey = | 'spot' + | 'spottest' | 'spot1' | 'spot2' | 'spot3' @@ -286,6 +287,15 @@ export interface SymbolMinNotionalFilter { avgPriceMins: number; } +export interface SymbolNotionalFilter { + filterType: 'NOTIONAL'; + minNotional: numberInString; + applyMinToMarket: boolean; + maxNotional: numberInString; + applyMaxToMarket: boolean; + avgPriceMins: number; +} + export interface SymbolIcebergPartsFilter { filterType: 'ICEBERG_PARTS'; limit: number; @@ -323,6 +333,7 @@ export type SymbolFilter = | SymbolPercentPriceFilter | SymbolLotSizeFilter | SymbolMinNotionalFilter + | SymbolNotionalFilter | SymbolIcebergPartsFilter | SymbolMarketLotSizeFilter | SymbolMaxOrdersFilter diff --git a/src/types/websockets.ts b/src/types/websockets.ts index 255819c4..018196e2 100644 --- a/src/types/websockets.ts +++ b/src/types/websockets.ts @@ -22,6 +22,7 @@ import { export type WsMarket = | 'spot' + | 'spotTestnet' | 'margin' | 'isolatedMargin' | 'usdm' diff --git a/src/usdm-client.ts b/src/usdm-client.ts index 2f54ec6d..78414a2e 100644 --- a/src/usdm-client.ts +++ b/src/usdm-client.ts @@ -86,9 +86,8 @@ export class USDMClient extends BaseRestClient { constructor( restClientOptions: RestClientOptions = {}, requestOptions: AxiosRequestConfig = {}, - useTestnet?: boolean, ) { - const clientId = useTestnet ? 'usdmtest' : 'usdm'; + const clientId = restClientOptions.useTestnet ? 'usdmtest' : 'usdm'; super(clientId, restClientOptions, requestOptions); this.clientId = clientId; @@ -272,22 +271,20 @@ export class USDMClient extends BaseRestClient { return this.getPrivate('fapi/v1/multiAssetsMargin'); } - submitNewOrder( - params: NewFuturesOrderParams, - ): Promise { + submitNewOrder(params: NewFuturesOrderParams): Promise { this.validateOrderId(params, 'newClientOrderId'); return this.postPrivate('fapi/v1/order', params); } - /** + /** * Order modify function, currently only LIMIT order modification is supported, modified orders will be reordered in the match queue */ - modifyOrder( - params: ModifyFuturesOrderParams, - ): Promise { - return this.putPrivate('fapi/v1/order', params); - } - + modifyOrder( + params: ModifyFuturesOrderParams, + ): Promise { + return this.putPrivate('fapi/v1/order', params); + } + /** * Warning: max 5 orders at a time! This method does not throw, instead it returns individual errors in the response array if any orders were rejected. * diff --git a/src/util/requestUtils.ts b/src/util/requestUtils.ts index dca4c9db..edaeb469 100644 --- a/src/util/requestUtils.ts +++ b/src/util/requestUtils.ts @@ -47,6 +47,9 @@ export interface RestClientOptions { //Defailt: false, if true will try to filter off undefined values from request params filterUndefinedParams?: boolean; + + //Default: false, if true it will use the testnet otherwise default to mainnet + useTestnet?: boolean; } export type GenericAPIResponse = Promise; @@ -54,6 +57,7 @@ export type GenericAPIResponse = Promise; export function getOrderIdPrefix(network: BinanceBaseUrlKey): string { switch (network) { case 'spot': + case 'spottest': case 'spot1': case 'spot2': case 'spot3': @@ -156,6 +160,7 @@ export async function getRequestSignature( const BINANCE_BASE_URLS: Record = { // spot/margin/savings/mining spot: 'https://api.binance.com', + spottest: 'https://testnet.binance.vision', spot1: 'https://api.binance.com', spot2: 'https://api1.binance.com', spot3: 'https://api2.binance.com', @@ -177,6 +182,7 @@ const BINANCE_BASE_URLS: Record = { export function getServerTimeEndpoint(urlKey: BinanceBaseUrlKey): string { switch (urlKey) { case 'spot': + case 'spottest': case 'spot1': case 'spot2': case 'spot3': diff --git a/src/websocket-client.ts b/src/websocket-client.ts index 7a693c91..c1bc5d67 100644 --- a/src/websocket-client.ts +++ b/src/websocket-client.ts @@ -29,10 +29,11 @@ import { CoinMClient } from './coinm-client'; const wsBaseEndpoints: Record = { spot: 'wss://stream.binance.com:9443', + spotTestnet: 'wss://testnet.binance.vision', margin: 'wss://stream.binance.com:9443', isolatedMargin: 'wss://stream.binance.com:9443', usdm: 'wss://fstream.binance.com', - usdmTestnet: 'wss://stream.binancefuture.com', + usdmTestnet: 'wss://fstream.binancefuture.com', coinm: 'wss://dstream.binance.com', coinmTestnet: 'wss://dstream.binancefuture.com', options: 'wss://vstream.binance.com', @@ -753,7 +754,7 @@ export class WebsocketClient extends EventEmitter { this.wsStore.setConnectionState(wsKey, state); } - private getSpotRestClient(): MainClient { + private getSpotRestClient(isTestnet?: boolean): MainClient { if (!this.restClients.spot) { this.restClients.spot = new MainClient( this.getRestClientOptions(), @@ -769,7 +770,6 @@ export class WebsocketClient extends EventEmitter { this.restClients.usdmFuturesTestnet = new USDMClient( this.getRestClientOptions(), this.options.requestOptions, - isTestnet, ); } return this.restClients.usdmFuturesTestnet; @@ -789,7 +789,6 @@ export class WebsocketClient extends EventEmitter { this.restClients.coinmFuturesTestnet = new CoinMClient( this.getRestClientOptions(), this.options.requestOptions, - isTestnet, ); } return this.restClients.coinmFuturesTestnet; @@ -944,6 +943,10 @@ export class WebsocketClient extends EventEmitter { return this.getSpotRestClient().keepAliveSpotUserDataListenKey( listenKey, ); + case 'spotTestnet': + return this.getSpotRestClient(true).keepAliveSpotUserDataListenKey( + listenKey, + ); case 'margin': return this.getSpotRestClient().keepAliveMarginUserDataListenKey( listenKey, @@ -1096,6 +1099,13 @@ export class WebsocketClient extends EventEmitter { isReconnecting, ); break; + case 'spotTestnet': + ws = await this.subscribeSpotUserDataStream( + forceNewConnection, + isReconnecting, + true, + ); + break; case 'margin': ws = await this.subscribeMarginUserDataStream( forceNewConnection, @@ -1197,7 +1207,7 @@ export class WebsocketClient extends EventEmitter { public subscribeEndpoint( endpoint: string, - market: 'spot' | 'usdm' | 'coinm', + market: 'spot' | 'spotTestnet' | 'usdm' | 'coinm', forceNewConnection?: boolean, ): WebSocket { const wsKey = getWsKeyWithContext(market, endpoint); @@ -1213,7 +1223,7 @@ export class WebsocketClient extends EventEmitter { */ public subscribeAggregateTrades( symbol: string, - market: 'spot' | 'usdm' | 'coinm', + market: 'spot' | 'spotTestnet' | 'usdm' | 'coinm', forceNewConnection?: boolean, ): WebSocket { const lowerCaseSymbol = symbol.toLowerCase(); @@ -1232,7 +1242,7 @@ export class WebsocketClient extends EventEmitter { */ public subscribeTrades( symbol: string, - market: 'spot' | 'usdm' | 'coinm', + market: 'spot' | 'spotTestnet' | 'usdm' | 'coinm', forceNewConnection?: boolean, ): WebSocket { const lowerCaseSymbol = symbol.toLowerCase(); @@ -1311,7 +1321,7 @@ export class WebsocketClient extends EventEmitter { public subscribeKlines( symbol: string, interval: KlineInterval, - market: 'spot' | 'usdm' | 'coinm', + market: 'spot' | 'spotTestnet' | 'usdm' | 'coinm', forceNewConnection?: boolean, ): WebSocket { const lowerCaseSymbol = symbol.toLowerCase(); @@ -1411,7 +1421,7 @@ export class WebsocketClient extends EventEmitter { */ public subscribeSymbolMini24hrTicker( symbol: string, - market: 'spot' | 'usdm' | 'coinm', + market: 'spot' | 'spotTestnet' | 'usdm' | 'coinm', forceNewConnection?: boolean, ): WebSocket { const lowerCaseSymbol = symbol.toLowerCase(); @@ -1428,7 +1438,7 @@ export class WebsocketClient extends EventEmitter { * Subscribe to mini 24hr mini ticker in market category. */ public subscribeAllMini24hrTickers( - market: 'spot' | 'usdm' | 'coinm', + market: 'spot' | 'spotTestnet' | 'usdm' | 'coinm', forceNewConnection?: boolean, ): WebSocket { const streamName = 'miniTicker'; @@ -1445,7 +1455,7 @@ export class WebsocketClient extends EventEmitter { */ public subscribeSymbol24hrTicker( symbol: string, - market: 'spot' | 'usdm' | 'coinm', + market: 'spot' | 'spotTestnet' | 'usdm' | 'coinm', forceNewConnection?: boolean, ): WebSocket { const lowerCaseSymbol = symbol.toLowerCase(); @@ -1462,7 +1472,7 @@ export class WebsocketClient extends EventEmitter { * Subscribe to 24hr ticker in any market. */ public subscribeAll24hrTickers( - market: 'spot' | 'usdm' | 'coinm', + market: 'spot' | 'spotTestnet' | 'usdm' | 'coinm', forceNewConnection?: boolean, ): WebSocket { const streamName = 'ticker'; @@ -1484,7 +1494,7 @@ export class WebsocketClient extends EventEmitter { * - Supported markets: spot */ public subscribeAllRollingWindowTickers( - market: 'spot', + market: 'spot' | 'spotTestnet', windowSize: '1h' | '4h' | '1d', forceNewConnection?: boolean, ): WebSocket { @@ -1501,7 +1511,7 @@ export class WebsocketClient extends EventEmitter { */ public subscribeSymbolBookTicker( symbol: string, - market: 'spot' | 'usdm' | 'coinm', + market: 'spot' | 'spotTestnet' | 'usdm' | 'coinm', forceNewConnection?: boolean, ): WebSocket { const lowerCaseSymbol = symbol.toLowerCase(); @@ -1518,7 +1528,7 @@ export class WebsocketClient extends EventEmitter { * Subscribe to best bid/ask for all symbols in spot markets. */ public subscribeAllBookTickers( - market: 'spot' | 'usdm' | 'coinm', + market: 'spot' | 'spotTestnet' | 'usdm' | 'coinm', forceNewConnection?: boolean, ): WebSocket { const streamName = 'bookTicker'; @@ -1577,7 +1587,7 @@ export class WebsocketClient extends EventEmitter { symbol: string, levels: 5 | 10 | 20, updateMs: 100 | 250 | 500 | 1000, - market: 'spot' | 'usdm' | 'coinm', + market: 'spot' | 'spotTestnet' | 'usdm' | 'coinm', forceNewConnection?: boolean, ): WebSocket { const lowerCaseSymbol = symbol.toLowerCase(); @@ -1607,7 +1617,7 @@ export class WebsocketClient extends EventEmitter { public subscribeDiffBookDepth( symbol: string, updateMs: 100 | 250 | 500 | 1000 = 100, - market: 'spot' | 'usdm' | 'coinm', + market: 'spot' | 'spotTestnet' | 'usdm' | 'coinm', forceNewConnection?: boolean, ): WebSocket { const lowerCaseSymbol = symbol.toLowerCase(); @@ -1641,8 +1651,10 @@ export class WebsocketClient extends EventEmitter { public subscribeSpotAggregateTrades( symbol: string, forceNewConnection?: boolean, + isTestnet?: boolean, ): WebSocket { - return this.subscribeAggregateTrades(symbol, 'spot', forceNewConnection); + const market: WsMarket = isTestnet ? 'spotTestnet' : 'spot'; + return this.subscribeAggregateTrades(symbol, market, forceNewConnection); } /** @@ -1651,8 +1663,10 @@ export class WebsocketClient extends EventEmitter { public subscribeSpotTrades( symbol: string, forceNewConnection?: boolean, + isTestnet?: boolean, ): WebSocket { - return this.subscribeTrades(symbol, 'spot', forceNewConnection); + const market: WsMarket = isTestnet ? 'spotTestnet' : 'spot'; + return this.subscribeTrades(symbol, market, forceNewConnection); } /** @@ -1662,8 +1676,10 @@ export class WebsocketClient extends EventEmitter { symbol: string, interval: KlineInterval, forceNewConnection?: boolean, + isTestnet?: boolean, ): WebSocket { - return this.subscribeKlines(symbol, interval, 'spot', forceNewConnection); + const market: WsMarket = isTestnet ? 'spotTestnet' : 'spot'; + return this.subscribeKlines(symbol, interval, market, forceNewConnection); } /** @@ -1672,10 +1688,12 @@ export class WebsocketClient extends EventEmitter { public subscribeSpotSymbolMini24hrTicker( symbol: string, forceNewConnection?: boolean, + isTestnet?: boolean, ): WebSocket { + const market: WsMarket = isTestnet ? 'spotTestnet' : 'spot'; return this.subscribeSymbolMini24hrTicker( symbol, - 'spot', + market, forceNewConnection, ); } @@ -1685,8 +1703,10 @@ export class WebsocketClient extends EventEmitter { */ public subscribeSpotAllMini24hrTickers( forceNewConnection?: boolean, + isTestnet?: boolean, ): WebSocket { - return this.subscribeAllMini24hrTickers('spot', forceNewConnection); + const market: WsMarket = isTestnet ? 'spotTestnet' : 'spot'; + return this.subscribeAllMini24hrTickers(market, forceNewConnection); } /** @@ -1695,15 +1715,21 @@ export class WebsocketClient extends EventEmitter { public subscribeSpotSymbol24hrTicker( symbol: string, forceNewConnection?: boolean, + isTestnet?: boolean, ): WebSocket { - return this.subscribeSymbol24hrTicker(symbol, 'spot', forceNewConnection); + const market: WsMarket = isTestnet ? 'spotTestnet' : 'spot'; + return this.subscribeSymbol24hrTicker(symbol, market, forceNewConnection); } /** * Subscribe to 24hr ticker in spot markets. */ - public subscribeSpotAll24hrTickers(forceNewConnection?: boolean): WebSocket { - return this.subscribeAll24hrTickers('spot', forceNewConnection); + public subscribeSpotAll24hrTickers( + forceNewConnection?: boolean, + isTestnet?: boolean, + ): WebSocket { + const market: WsMarket = isTestnet ? 'spotTestnet' : 'spot'; + return this.subscribeAll24hrTickers(market, forceNewConnection); } /** @@ -1712,15 +1738,21 @@ export class WebsocketClient extends EventEmitter { public subscribeSpotSymbolBookTicker( symbol: string, forceNewConnection?: boolean, + isTestnet?: boolean, ): WebSocket { - return this.subscribeSymbolBookTicker(symbol, 'spot', forceNewConnection); + const market: WsMarket = isTestnet ? 'spotTestnet' : 'spot'; + return this.subscribeSymbolBookTicker(symbol, market, forceNewConnection); } /** * Subscribe to best bid/ask for all symbols in spot markets. */ - public subscribeSpotAllBookTickers(forceNewConnection?: boolean): WebSocket { - return this.subscribeAllBookTickers('spot', forceNewConnection); + public subscribeSpotAllBookTickers( + forceNewConnection?: boolean, + isTestnet?: boolean, + ): WebSocket { + const market: WsMarket = isTestnet ? 'spotTestnet' : 'spot'; + return this.subscribeAllBookTickers(market, forceNewConnection); } /** @@ -1731,12 +1763,13 @@ export class WebsocketClient extends EventEmitter { levels: 5 | 10 | 20, updateMs: 1000 | 100 = 1000, forceNewConnection?: boolean, + isTestnet?: boolean, ): WebSocket { return this.subscribePartialBookDepths( symbol, levels, updateMs, - 'spot', + isTestnet ? 'spotTestnet' : 'spot', forceNewConnection, ); } @@ -1748,11 +1781,12 @@ export class WebsocketClient extends EventEmitter { symbol: string, updateMs: 1000 | 100 = 1000, forceNewConnection?: boolean, + isTestnet?: boolean, ): WebSocket { return this.subscribeDiffBookDepth( symbol, updateMs, - 'spot', + isTestnet ? 'spotTestnet' : 'spot', forceNewConnection, ); } @@ -1765,8 +1799,9 @@ export class WebsocketClient extends EventEmitter { listenKey: string, forceNewConnection?: boolean, isReconnecting?: boolean, + isTestnet?: boolean, ): WebSocket | undefined { - const market: WsMarket = 'spot'; + const market: WsMarket = isTestnet ? 'spotTestnet' : 'spot'; const wsKey = getWsKeyWithContext(market, 'userData', undefined, listenKey); if (!forceNewConnection && this.wsStore.isWsConnecting(wsKey)) { @@ -1789,7 +1824,14 @@ export class WebsocketClient extends EventEmitter { ); // Start & store timer to keep alive listen key (and handle expiration) - this.setKeepAliveListenKeyTimer(listenKey, market, ws, wsKey); + this.setKeepAliveListenKeyTimer( + listenKey, + market, + ws, + wsKey, + undefined, + isTestnet, + ); return ws; } @@ -1800,21 +1842,26 @@ export class WebsocketClient extends EventEmitter { public async subscribeSpotUserDataStream( forceNewConnection?: boolean, isReconnecting?: boolean, + isTestnet?: boolean, ): Promise { + const market: WsMarket = isTestnet ? 'spotTestnet' : 'spot'; + try { - const { listenKey } = - await this.getSpotRestClient().getSpotUserDataListenKey(); + const { listenKey } = await this.getSpotRestClient( + isTestnet, + ).getSpotUserDataListenKey(); return this.subscribeSpotUserDataStreamWithListenKey( listenKey, forceNewConnection, isReconnecting, + isTestnet, ); } catch (e) { this.logger.error(`Failed to connect to spot user data`, { ...loggerCategory, error: e, }); - this.emit('error', { wsKey: 'spot' + '_' + 'userData', error: e }); + this.emit('error', { wsKey: market + '_' + 'userData', error: e }); } }