UNPKG

binance

Version:

Professional Node.js & JavaScript SDK for Binance REST APIs & WebSockets, with TypeScript & end-to-end tests.

415 lines 15.6 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getOrderIdPrefix = getOrderIdPrefix; exports.generateNewOrderId = generateNewOrderId; exports.getBaseURLKeyForWsKey = getBaseURLKeyForWsKey; exports.requiresWSAPINewClientOID = requiresWSAPINewClientOID; exports.validateWSAPINewClientOID = validateWSAPINewClientOID; exports.serialiseParams = serialiseParams; exports.getRESTRequestSignature = getRESTRequestSignature; exports.getServerTimeEndpoint = getServerTimeEndpoint; exports.getTestnetBaseUrlKey = getTestnetBaseUrlKey; exports.getRestBaseUrl = getRestBaseUrl; exports.isPublicEndpoint = isPublicEndpoint; exports.isWsPong = isWsPong; exports.logInvalidOrderId = logInvalidOrderId; exports.appendEventIfMissing = appendEventIfMissing; exports.asArray = asArray; const nanoid_1 = require("nanoid"); const node_support_1 = require("./node-support"); const typeGuards_1 = require("./typeGuards"); const websocket_util_1 = require("./websockets/websocket-util"); // function throwUnhandledSwitch(x: never, msg: string): never { // throw new Error(msg); // } function getOrderIdPrefix(network) { switch (network) { case 'spot': case 'spot1': case 'spot2': case 'spot3': case 'spot4': return 'U5D79M5B'; case 'usdm': case 'usdmtest': case 'coinm': case 'coinmtest': case 'papi': return '15PC4ZJy'; case 'voptions': case 'voptionstest': return ''; default: // throwUnhandledSwitch(network, `"${network}" unhandled`); return 'U5D79M5B'; } } function generateNewOrderId(network) { const id = (0, nanoid_1.nanoid)(22); // must pass ^[\.A-Z\:/a-z0-9_-]{1,32}$ with prefix const prefixedId = 'x-' + getOrderIdPrefix(network) + id; return prefixedId; } function getBaseURLKeyForWsKey(wsKey) { switch (wsKey) { case websocket_util_1.WS_KEY_MAP.mainWSAPI: case websocket_util_1.WS_KEY_MAP.mainWSAPI2: case websocket_util_1.WS_KEY_MAP.mainWSAPITestnet: { return 'spot'; } case websocket_util_1.WS_KEY_MAP.usdmWSAPI: case websocket_util_1.WS_KEY_MAP.usdmWSAPITestnet: { return 'usdm'; } default: { return 'spot'; } } } function getWSAPINewOrderIdProperties(operation, wsKey) { // switch (wsKey) { case websocket_util_1.WS_KEY_MAP.mainWSAPI: case websocket_util_1.WS_KEY_MAP.mainWSAPI2: case websocket_util_1.WS_KEY_MAP.mainWSAPITestnet: case websocket_util_1.WS_KEY_MAP.usdmWSAPI: case websocket_util_1.WS_KEY_MAP.usdmWSAPITestnet: case websocket_util_1.WS_KEY_MAP.coinmWSAPI: case websocket_util_1.WS_KEY_MAP.coinmWSAPITestnet: { if (['order.place', 'order.amend.keepPriority', 'sor.order.place'].includes(operation)) { return ['newClientOrderId']; } if (operation === 'orderList.place') { return ['listClientOrderId', 'limitClientOrderId', 'stopClientOrderId']; } if (operation === 'orderList.place.oco') { return [ 'listClientOrderId', 'aboveClientOrderId', 'belowClientOrderId', ]; } if (operation === 'orderList.place.oto') { return [ 'listClientOrderId', 'workingClientOrderId', 'pendingClientOrderId', ]; } if (operation === 'orderList.place.otoco') { return [ 'listClientOrderId', 'workingClientOrderId', 'pendingAboveClientOrderId', 'pendingBelowClientOrderId', ]; } if (operation === 'orderList.place.opo') { return [ 'listClientOrderId', 'workingClientOrderId', 'pendingClientOrderId', ]; } if (operation === 'orderList.place.opoco') { return [ 'listClientOrderId', 'workingClientOrderId', 'pendingAboveClientOrderId', 'pendingBelowClientOrderId', ]; } return []; } default: { return []; } } } function requiresWSAPINewClientOID(request, wsKey) { switch (wsKey) { case websocket_util_1.WS_KEY_MAP.mainWSAPI: case websocket_util_1.WS_KEY_MAP.mainWSAPI2: case websocket_util_1.WS_KEY_MAP.mainWSAPITestnet: case websocket_util_1.WS_KEY_MAP.usdmWSAPI: case websocket_util_1.WS_KEY_MAP.usdmWSAPITestnet: case websocket_util_1.WS_KEY_MAP.coinmWSAPI: case websocket_util_1.WS_KEY_MAP.coinmWSAPITestnet: { return [ 'order.place', 'order.amend.keepPriority', 'sor.order.place', 'orderList.place', 'orderList.place.oco', 'orderList.place.oto', 'orderList.place.otoco', 'orderList.place.opo', 'orderList.place.opoco', ].includes(request.method); } case websocket_util_1.WS_KEY_MAP.main: case websocket_util_1.WS_KEY_MAP.main2: case websocket_util_1.WS_KEY_MAP.main3: case websocket_util_1.WS_KEY_MAP.mainTestnetPublic: case websocket_util_1.WS_KEY_MAP.mainTestnetUserData: case websocket_util_1.WS_KEY_MAP.marginRiskUserData: case websocket_util_1.WS_KEY_MAP.usdm: case websocket_util_1.WS_KEY_MAP.usdmTestnet: case websocket_util_1.WS_KEY_MAP.coinm: case websocket_util_1.WS_KEY_MAP.coinm2: case websocket_util_1.WS_KEY_MAP.coinmTestnet: case websocket_util_1.WS_KEY_MAP.eoptions: case websocket_util_1.WS_KEY_MAP.portfolioMarginUserData: case websocket_util_1.WS_KEY_MAP.portfolioMarginProUserData: return false; default: { throw (0, typeGuards_1.neverGuard)(wsKey, `Unhandled WsKey "${wsKey}"`); } } } function validateWSAPINewClientOID(request, wsKey) { if (!requiresWSAPINewClientOID(request, wsKey) || !request.params) { return; } const newClientOIDProperties = getWSAPINewOrderIdProperties(request.method, wsKey); if (!newClientOIDProperties.length) { return; } const baseUrlKey = getBaseURLKeyForWsKey(wsKey); for (const orderIdProperty of newClientOIDProperties) { if (!request.params[orderIdProperty]) { request.params[orderIdProperty] = generateNewOrderId(baseUrlKey); continue; } const expectedOrderIdPrefix = `x-${getOrderIdPrefix(baseUrlKey)}`; if (!request.params[orderIdProperty].startsWith(expectedOrderIdPrefix)) { logInvalidOrderId(orderIdProperty, expectedOrderIdPrefix, request.params); } } } function serialiseParams(params = {}, strict_validation = false, encodeValues = false, filterUndefinedParams = false) { const paramKeys = !filterUndefinedParams ? Object.keys(params) : Object.keys(params).filter((key) => typeof params[key] !== 'undefined'); return paramKeys .map((key) => { const value = params[key]; if (strict_validation === true && typeof value === 'undefined') { throw new Error('Failed to sign API request due to undefined parameter'); } const encodedValue = encodeValues ? encodeURIComponent(value) : value; return `${key}=${encodedValue}`; }) .join('&'); } function getRESTRequestSignature(data, options, key, secret, timestamp) { return __awaiter(this, void 0, void 0, function* () { var _a, _b; const { recvWindow, strictParamValidation, filterUndefinedParams } = options; // Optional, set to 5000 by default. Increase if timestamp/recvWindow errors are seen. const requestRecvWindow = (_b = (_a = data === null || data === void 0 ? void 0 : data.recvWindow) !== null && _a !== void 0 ? _a : recvWindow) !== null && _b !== void 0 ? _b : 5000; if (key && secret) { const requestParams = Object.assign(Object.assign({}, data), { timestamp, recvWindow: requestRecvWindow }); const signMethod = 'hex'; const signAlgorithm = 'SHA-256'; const serialisedParams = serialiseParams(requestParams, strictParamValidation, true, filterUndefinedParams); let signature; if (typeof options.customSignMessageFn === 'function') { signature = yield options.customSignMessageFn(serialisedParams, secret); } else { signature = yield (0, node_support_1.signMessage)(serialisedParams, secret, signMethod, signAlgorithm); signature = encodeURIComponent(signature); } requestParams.signature = signature; return { requestBody: Object.assign({}, data), serialisedParams, timestamp: timestamp, signature: signature, recvWindow: requestRecvWindow, }; } return { requestBody: data, serialisedParams: undefined }; }); } const BINANCE_BASE_URLS = { // spot/margin/savings/mining spot: 'https://api.binance.com', spot1: 'https://api.binance.com', spot2: 'https://api1.binance.com', spot3: 'https://api2.binance.com', spot4: 'https://api3.binance.com', spottest: 'https://testnet.binance.vision', // USDM Futures usdm: 'https://fapi.binance.com', usdmtest: 'https://testnet.binancefuture.com', // COINM Futures coinm: 'https://dapi.binance.com', coinmtest: 'https://testnet.binancefuture.com', // Vanilla Options voptions: 'https://vapi.binance.com', voptionstest: 'https://testnet.binanceops.com', // Portfolio Margin papi: 'https://papi.binance.com', // www - for alpha www: 'https://www.binance.com', }; const BINANCE_MM_BASE_URLS = { // spot/margin/savings/mining - no MM endpoints spot: undefined, spot1: undefined, spot2: undefined, spot3: undefined, spot4: undefined, spottest: undefined, // USDM Futures MM endpoints usdm: 'https://fapi-mm.binance.com', usdmtest: undefined, // No MM endpoint for testnet // COINM Futures MM endpoints coinm: 'https://dapi-mm.binance.com', coinmtest: undefined, // No MM endpoint for testnet // Vanilla Options - no MM endpoints voptions: undefined, voptionstest: undefined, // Portfolio Margin - no MM endpoints papi: undefined, // www - no MM endpoints www: undefined, }; function getServerTimeEndpoint(urlKey) { switch (urlKey) { case 'spot': case 'spot1': case 'spot2': case 'spot3': case 'spot4': case 'spottest': default: return 'api/v3/time'; case 'usdm': case 'usdmtest': return 'fapi/v1/time'; case 'coinm': case 'coinmtest': return 'dapi/v1/time'; case 'voptions': case 'voptionstest': return 'vapi/v1/time'; } } function getTestnetBaseUrlKey(urlKey) { switch (urlKey) { case 'spot': case 'spot1': case 'spot2': case 'spot3': case 'spot4': case 'spottest': default: return 'spottest'; case 'usdm': case 'usdmtest': return 'usdmtest'; case 'coinm': case 'coinmtest': return 'coinmtest'; case 'voptions': case 'voptionstest': return 'voptionstest'; } } function getRestBaseUrl(clientType, restClientOptions) { if (restClientOptions.baseUrl) { return restClientOptions.baseUrl; } const urlKey = restClientOptions.baseUrlKey || clientType; // Use MM endpoints if requested and available if (restClientOptions.useMMSubdomain) { const mmUrl = BINANCE_MM_BASE_URLS[urlKey]; if (mmUrl) { return mmUrl; } } return BINANCE_BASE_URLS[urlKey]; } function isPublicEndpoint(endpoint) { if (endpoint.startsWith('v2/public')) { return true; } if (endpoint.startsWith('public/linear')) { return true; } return false; } function isWsPong(response) { return (response.request && response.request.op === 'ping' && response.ret_msg === 'pong' && response.success === true); } function logInvalidOrderId(orderIdProperty, expectedOrderIdPrefix, params) { console.warn(`WARNING: '${orderIdProperty}' invalid - it should be prefixed with ${expectedOrderIdPrefix}. Use the 'client.generateNewOrderId()' REST client utility method to generate a fresh order ID on demand. Original request: ${JSON.stringify(params)}`); } /** * For some topics, the received event does not include any information on the topic the event is for (e.g. book tickers). * * This method extracts this using available context, to add an "eventType" property if missing. * * - For the old WebsocketClient, this is extracted using the WsKey. * - For the new multiplex Websocketclient, this is extracted using the "stream" parameter. */ function appendEventIfMissing(wsMsg, wsKey, eventType) { if (wsMsg.e) { return; } if (eventType) { if (!Array.isArray(wsMsg)) { wsMsg.e = eventType; return; } for (const key in wsMsg) { wsMsg[key].e = eventType; } return; } // Multiplex websockets include the eventType as the stream name if (wsMsg.stream && wsMsg.data) { const eventType = (0, websocket_util_1.parseEventTypeFromMessage)(wsKey, wsMsg); if (eventType) { if (Array.isArray(wsMsg.data)) { for (const key in wsMsg.data) { wsMsg.data[key].streamName = wsMsg.stream; wsMsg.data[key].e = eventType; } return; } } } if (wsKey.indexOf('bookTicker') !== -1) { wsMsg.e = 'bookTicker'; return; } if (wsKey.indexOf('diffBookDepth') !== -1) { wsMsg.e = 'diffBookDepth'; return; } if (wsKey.indexOf('partialBookDepth') !== -1 || wsKey.indexOf('depth') !== -1) { wsMsg.e = 'partialBookDepth'; return; } // console.warn('couldnt derive event type: ', wsKey); } function asArray(el) { return Array.isArray(el) ? el : [el]; } //# sourceMappingURL=requestUtils.js.map