UNPKG

bitmart-api

Version:

Complete & robust Node.js SDK for BitMart's REST APIs and WebSockets, with TypeScript declarations.

1,705 lines (1,627 loc) 131 kB
This file is a merged representation of a subset of the codebase, containing files not matching ignore patterns, combined into a single document by Repomix. The content has been processed where content has been compressed (code blocks are separated by ⋮---- delimiter). ================================================================ File Summary ================================================================ Purpose: -------- This file contains a packed representation of a subset of the repository's contents that is considered the most important context. It is designed to be easily consumable by AI systems for analysis, code review, or other automated processes. File Format: ------------ The content is organized as follows: 1. This summary section 2. Repository information 3. Directory structure 4. Repository files (if enabled) 5. Multiple file entries, each consisting of: a. A separator line (================) b. The file path (File: path/to/file) c. Another separator line d. The full contents of the file e. A blank line Usage Guidelines: ----------------- - This file should be treated as read-only. Any changes should be made to the original repository files, not this packed version. - When processing this file, use the file path to distinguish between different files in the repository. - Be aware that this file may contain sensitive information. Handle it with the same level of security as you would the original repository. Notes: ------ - Some files may have been excluded based on .gitignore rules and Repomix's configuration - Binary files are not included in this packed representation. Please refer to the Repository Structure section for a complete list of file paths, including binary files - Files matching these patterns are excluded: .github/, examples/apidoc/, docs/images/, docs/endpointFunctionList.md, test/, src/util/ - Files matching patterns in .gitignore are excluded - Files matching default ignore patterns are excluded - Content has been compressed - code blocks are separated by ⋮---- delimiter - Files are sorted by Git change count (files with more changes are at the bottom) ================================================================ Directory Structure ================================================================ examples/ fasterHmacSign.ts futures-get-balances.ts futures-get-klines.ts futures-get-tickers.ts futures-submit-order.ts README.md spot-get-balances.ts spot-get-klines.ts spot-get-symbols.ts spot-submit-order.ts tsconfig.examples.json ws-custom-logger.ts ws-futures-private.ts ws-futures-public.ts ws-spot-private.ts ws-spot-public.ts src/ lib/ websocket/ websocket-util.ts WsStore.ts WsStore.types.ts BaseRestClient.ts BaseWSClient.ts logger.ts misc-util.ts requestUtils.ts webCryptoAPI.ts types/ request/ futures.types.ts spot.types.ts response/ futures.types.ts shared.types.ts spot.types.ts websockets/ client.ts events.ts requests.ts FuturesClientV2.ts index.ts RestClient.ts WebsocketClient.ts .eslintrc.cjs .gitignore .nvmrc .prettierrc jest.config.ts package.json postBuild.sh README.md tea.yaml tsconfig.cjs.json tsconfig.esm.json tsconfig.json tsconfig.linting.json ================================================================ Files ================================================================ ================ File: examples/README.md ================ # Bitmart API Examples Node.js Some examples written in Node.js/typescript showing how to use some of Bitmart's common API functionality, such as fetching prices, submitting orders, etc. ## Usage Most of these examples can just be executed (e.g. using `ts-node` or `tsx`). Any "private" examples that perform actions on an account (such as checking balance or submitting orders) will require an api key, secret and memo (provided by bitmart when you create an API key). These can either be hardcoded or you can pass them as env vars to test the functionality. For example on macOS or unix, using `ts-node` to execute a typescript file directly: ```bash API_KEY="apiKeyHere" API_SECRET="secretHere" API_MEMO="memoHere" ts-node examples/futures-get-balances.ts ``` ================ File: examples/tsconfig.examples.json ================ { "extends": "../tsconfig.json", "compilerOptions": { "module": "commonjs", "outDir": "dist/cjs", "target": "esnext", "rootDir": "../" }, "include": ["../src/**/*.*", "../examples/**/*.*"] } ================ File: src/lib/websocket/WsStore.types.ts ================ import WebSocket from 'isomorphic-ws'; ⋮---- export enum WsConnectionStateEnum { INITIAL = 0, CONNECTING = 1, CONNECTED = 2, CLOSING = 3, RECONNECTING = 4, // ERROR = 5, } ⋮---- // ERROR = 5, ⋮---- export interface WsStoredState<TWSTopicSubscribeEvent extends string | object> { /** The currently active websocket connection */ ws?: WebSocket; /** The current lifecycle state of the connection (enum) */ connectionState?: WsConnectionStateEnum; /** A timer that will send an upstream heartbeat (ping) when it expires */ activePingTimer?: ReturnType<typeof setTimeout> | undefined; /** A timer tracking that an upstream heartbeat was sent, expecting a reply before it expires */ activePongTimer?: ReturnType<typeof setTimeout> | undefined; /** If a reconnection is in progress, this will have the timer for the delayed reconnect */ activeReconnectTimer?: ReturnType<typeof setTimeout> | undefined; /** * All the topics we are expected to be subscribed to on this connection (and we automatically resubscribe to if the connection drops) * * A "Set" and a deep-object-match are used to ensure we only subscribe to a topic once (tracking a list of unique topics we're expected to be connected to) */ subscribedTopics: Set<TWSTopicSubscribeEvent>; /** Whether this connection has completed authentication (only applies to private connections) */ isAuthenticated?: boolean; } ⋮---- /** The currently active websocket connection */ ⋮---- /** The current lifecycle state of the connection (enum) */ ⋮---- /** A timer that will send an upstream heartbeat (ping) when it expires */ ⋮---- /** A timer tracking that an upstream heartbeat was sent, expecting a reply before it expires */ ⋮---- /** If a reconnection is in progress, this will have the timer for the delayed reconnect */ ⋮---- /** * All the topics we are expected to be subscribed to on this connection (and we automatically resubscribe to if the connection drops) * * A "Set" and a deep-object-match are used to ensure we only subscribe to a topic once (tracking a list of unique topics we're expected to be connected to) */ ⋮---- /** Whether this connection has completed authentication (only applies to private connections) */ ================ File: src/lib/logger.ts ================ export type LogParams = null | any; ⋮---- // eslint-disable-next-line @typescript-eslint/no-unused-vars ⋮---- // console.log(_params); ================ File: src/lib/misc-util.ts ================ export function neverGuard(x: never, msg: string): Error ================ File: src/lib/webCryptoAPI.ts ================ import { neverGuard } from './misc-util.js'; ⋮---- function bufferToB64(buffer: ArrayBuffer): string ⋮---- /** * Sign a message, with a secret, using the Web Crypto API */ export async function signMessage( message: string, secret: string, method: 'hex' | 'base64', ): Promise<string> ================ File: src/types/websockets/client.ts ================ /** * Event args for subscribing/unsubscribing */ ⋮---- // export type WsTopicSubscribePrivateArgsV2 = // | WsTopicSubscribePrivateInstIdArgsV2 // | WsTopicSubscribePrivateCoinArgsV2; ⋮---- // export type WsTopicSubscribeEventArgsV2 = // | WsTopicSubscribePublicArgsV2 // | WsTopicSubscribePrivateArgsV2; ⋮---- /** General configuration for the WebsocketClient */ export interface WSClientConfigurableOptions { /** Your API key */ apiKey?: string; /** Your API secret */ apiSecret?: string; /** Your API memo (can be anything) that you included when creating this API key */ apiMemo?: string; /** Define a recv window when preparing a private websocket signature. This is in milliseconds, so 5000 == 5 seconds */ recvWindow?: number; /** How often to check if the connection is alive */ pingInterval?: number; /** How long to wait for a pong (heartbeat reply) before assuming the connection is dead */ pongTimeout?: number; /** Delay in milliseconds before respawning the connection */ reconnectTimeout?: number; requestOptions?: {}; wsUrl?: string; /** * Allows you to provide a custom "signMessage" function, e.g. to use node's much faster createHmac method * * Look in the examples folder for a demonstration on using node's createHmac instead. */ customSignMessageFn?: (message: string, secret: string) => Promise<string>; } ⋮---- /** Your API key */ ⋮---- /** Your API secret */ ⋮---- /** Your API memo (can be anything) that you included when creating this API key */ ⋮---- /** Define a recv window when preparing a private websocket signature. This is in milliseconds, so 5000 == 5 seconds */ ⋮---- /** How often to check if the connection is alive */ ⋮---- /** How long to wait for a pong (heartbeat reply) before assuming the connection is dead */ ⋮---- /** Delay in milliseconds before respawning the connection */ ⋮---- /** * Allows you to provide a custom "signMessage" function, e.g. to use node's much faster createHmac method * * Look in the examples folder for a demonstration on using node's createHmac instead. */ ⋮---- /** * WS configuration that's always defined, regardless of user configuration * (usually comes from defaults if there's no user-provided values) */ export interface WebsocketClientOptions extends WSClientConfigurableOptions { pingInterval: number; pongTimeout: number; reconnectTimeout: number; recvWindow: number; } ⋮---- export type WsMarket = 'spot' | 'futures'; ================ File: src/types/websockets/events.ts ================ export interface WsDataEvent<TData = any, TWSKey = string> { data: TData; table: string; wsKey: TWSKey; } ================ File: src/types/websockets/requests.ts ================ export type WsOperation = | 'subscribe' | 'unsubscribe' | 'login' | 'access' | 'request'; ⋮---- export interface WsSpotOperation<TWSTopic extends string = string> { op: WsOperation; args: TWSTopic[]; } ⋮---- export interface WsFuturesOperation<TWSTopic extends string> { action: WsOperation; args: TWSTopic[]; } ⋮---- export type WsRequestOperation<TWSTopic extends string> = | WsSpotOperation<TWSTopic> | WsFuturesOperation<TWSTopic>; ================ File: .prettierrc ================ { "tabWidth": 2, "singleQuote": true, "trailingComma": "all" } ================ File: jest.config.ts ================ /** * For a detailed explanation regarding each configuration property, visit: * https://jestjs.io/docs/configuration */ ⋮---- import type { Config } from 'jest'; ⋮---- // All imported modules in your tests should be mocked automatically // automock: false, ⋮---- // Stop running tests after `n` failures // bail: 0, bail: false, // enable to stop test when an error occur, ⋮---- // The directory where Jest should store its cached dependency information // cacheDirectory: "/private/var/folders/kf/2k3sz4px6c9cbyzj1h_b192h0000gn/T/jest_dx", ⋮---- // Automatically clear mock calls, instances, contexts and results before every test ⋮---- // Indicates whether the coverage information should be collected while executing the test ⋮---- // An array of glob patterns indicating a set of files for which coverage information should be collected ⋮---- // The directory where Jest should output its coverage files ⋮---- // An array of regexp pattern strings used to skip coverage collection // coveragePathIgnorePatterns: [ // "/node_modules/" // ], ⋮---- // Indicates which provider should be used to instrument code for coverage ⋮---- // A list of reporter names that Jest uses when writing coverage reports // coverageReporters: [ // "json", // "text", // "lcov", // "clover" // ], ⋮---- // An object that configures minimum threshold enforcement for coverage results // coverageThreshold: undefined, ⋮---- // A path to a custom dependency extractor // dependencyExtractor: undefined, ⋮---- // Make calling deprecated APIs throw helpful error messages // errorOnDeprecated: false, ⋮---- // The default configuration for fake timers // fakeTimers: { // "enableGlobally": false // }, ⋮---- // Force coverage collection from ignored files using an array of glob patterns // forceCoverageMatch: [], ⋮---- // A path to a module which exports an async function that is triggered once before all test suites // globalSetup: undefined, ⋮---- // A path to a module which exports an async function that is triggered once after all test suites // globalTeardown: undefined, ⋮---- // A set of global variables that need to be available in all test environments // globals: {}, ⋮---- // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. // maxWorkers: "50%", ⋮---- // An array of directory names to be searched recursively up from the requiring module's location // moduleDirectories: [ // "node_modules" // ], ⋮---- // An array of file extensions your modules use // moduleFileExtensions: [ // "js", // "mjs", // "cjs", // "jsx", // "ts", // "tsx", // "json", // "node" // ], ⋮---- // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module // moduleNameMapper: {}, ⋮---- // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader // modulePathIgnorePatterns: [], ⋮---- // Activates notifications for test results // notify: false, ⋮---- // An enum that specifies notification mode. Requires { notify: true } // notifyMode: "failure-change", ⋮---- // A preset that is used as a base for Jest's configuration // preset: undefined, ⋮---- // Run tests from one or more projects // projects: undefined, ⋮---- // Use this configuration option to add custom reporters to Jest // reporters: undefined, ⋮---- // Automatically reset mock state before every test // resetMocks: false, ⋮---- // Reset the module registry before running each individual test // resetModules: false, ⋮---- // A path to a custom resolver // resolver: undefined, ⋮---- // Automatically restore mock state and implementation before every test // restoreMocks: false, ⋮---- // The root directory that Jest should scan for tests and modules within // rootDir: undefined, ⋮---- // A list of paths to directories that Jest should use to search for files in // roots: [ // "<rootDir>" // ], ⋮---- // Allows you to use a custom runner instead of Jest's default test runner // runner: "jest-runner", ⋮---- // The paths to modules that run some code to configure or set up the testing environment before each test // setupFiles: [], ⋮---- // A list of paths to modules that run some code to configure or set up the testing framework before each test // setupFilesAfterEnv: [], ⋮---- // The number of seconds after which a test is considered as slow and reported as such in the results. // slowTestThreshold: 5, ⋮---- // A list of paths to snapshot serializer modules Jest should use for snapshot testing // snapshotSerializers: [], ⋮---- // The test environment that will be used for testing // testEnvironment: "jest-environment-node", ⋮---- // Options that will be passed to the testEnvironment // testEnvironmentOptions: {}, ⋮---- // Adds a location field to test results // testLocationInResults: false, ⋮---- // The glob patterns Jest uses to detect test files ⋮---- // "**/__tests__/**/*.[jt]s?(x)", ⋮---- // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped // testPathIgnorePatterns: [ // "/node_modules/" // ], ⋮---- // The regexp pattern or array of patterns that Jest uses to detect test files // testRegex: [], ⋮---- // This option allows the use of a custom results processor // testResultsProcessor: undefined, ⋮---- // This option allows use of a custom test runner // testRunner: "jest-circus/runner", ⋮---- // A map from regular expressions to paths to transformers // transform: undefined, ⋮---- // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation // transformIgnorePatterns: [ // "/node_modules/", // "\\.pnp\\.[^\\/]+$" // ], ⋮---- // Prevents import esm module error from v1 axios release, issue #5026 ⋮---- // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them // unmockedModulePathPatterns: undefined, ⋮---- // Indicates whether each individual test should be reported during the run // verbose: undefined, verbose: true, // report individual test ⋮---- // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode // watchPathIgnorePatterns: [], ⋮---- // Whether to use watchman for file crawling // watchman: true, ================ File: postBuild.sh ================ #!/bin/bash # # Add package.json files to cjs/mjs subtrees # cat >dist/cjs/package.json <<!EOF { "type": "commonjs" } !EOF cat >dist/mjs/package.json <<!EOF { "type": "module" } !EOF find src -name '*.d.ts' -exec cp {} dist/mjs \; find src -name '*.d.ts' -exec cp {} dist/cjs \; ================ File: tea.yaml ================ --- version: 1.0.0 codeOwners: - '0xeb1a7BF44a801e33a339705A266Afc0Cba3D6D54' quorum: 1 ================ File: tsconfig.cjs.json ================ { "extends": "./tsconfig.json", "compilerOptions": { "module": "commonjs", "outDir": "dist/cjs", "target": "esnext" }, "include": ["src/**/*.*"] } ================ File: tsconfig.esm.json ================ { "extends": "./tsconfig.json", "compilerOptions": { "module": "esnext", "outDir": "dist/mjs", "target": "esnext" }, "include": ["src/**/*.*"] } ================ File: tsconfig.linting.json ================ { "extends": "./tsconfig.json", "compilerOptions": { "module": "commonjs", "outDir": "dist/cjs", "target": "esnext", "rootDir": "../" }, "include": ["src/**/*.*", "test/**/*.*", "examples/**/*.*", ".eslintrc.cjs"] } ================ File: examples/fasterHmacSign.ts ================ import { createHmac } from 'crypto'; ⋮---- import { RestClient } from '../src'; // import from npm, after installing via npm `npm install bitmart-api` // import { RestClient } from 'bitmart-api'; ⋮---- /** * Overkill in almost every case, but if you need any optimisation available, * you can inject a faster sign mechanism such as node's native createHmac: */ ⋮---- async function getSpotBalances() ================ File: examples/futures-get-balances.ts ================ import { FuturesClientV2 } from '../src/index.js'; // // import from npm, after installing via npm `npm install bitmart-api` // import { FuturesClientV2 } from 'bitmart-api'; ⋮---- async function getFuturesAssets() ================ File: examples/futures-get-klines.ts ================ import { FuturesClientV2 } from '../src/index.js'; // // import from npm, after installing via npm `npm install bitmart-api` // import { FuturesClientV2 } from 'bitmart-api'; ⋮---- async function getFuturesKlines() ================ File: examples/futures-get-tickers.ts ================ import { FuturesClientV2 } from '../src/index.js'; // // import from npm, after installing via npm `npm install bitmart-api` // import { FuturesClientV2 } from 'bitmart-api'; ⋮---- async function getFuturesTickers() ================ File: examples/spot-get-balances.ts ================ import { RestClient } from '../src'; // import from npm, after installing via npm `npm install bitmart-api` // import { RestClient } from 'bitmart-api'; ⋮---- async function getSpotBalances() ================ File: examples/spot-get-klines.ts ================ import { RestClient } from '../src'; // import from npm, after installing via npm `npm install bitmart-api` // import { RestClient } from 'bitmart-api'; ⋮---- async function getKlines() ================ File: examples/spot-get-symbols.ts ================ import { RestClient } from '../src'; // import from npm, after installing via npm `npm install bitmart-api` // import { RestClient } from 'bitmart-api'; ⋮---- async function getTickers() ================ File: examples/spot-submit-order.ts ================ import { RestClient } from '../src'; // import from npm, after installing via npm `npm install bitmart-api` // import { RestClient } from 'bitmart-api'; ⋮---- async function start() ⋮---- // const usdValue = 6; // const price = 52000; // const qty = usdValue / price; ⋮---- // const limitBuyOrder = { // symbol: 'BTC_USDT', // side: 'buy', // type: 'limit', // size: String(qty), // price: String(price), // }; ⋮---- // const res = await client.submitSpotOrder({ // symbol: 'BTC_USDT', // side: 'buy', // type: 'market', // size: String(qty), // }); ================ File: examples/ws-custom-logger.ts ================ import { DefaultLogger, LogParams, WebsocketClient } from '../src'; // import from npm, after installing via npm `npm install bitmart-api` // import { DefaultLogger, LogParams, WebsocketClient } from 'bitmart-api'; ⋮---- /** Optional, implement a custom logger */ ⋮---- async function start() ⋮---- // Data received ⋮---- // Something happened, attempting to reconenct ⋮---- // Reconnect successful ⋮---- // Connection closed. If unexpected, expect reconnect -> reconnected. ⋮---- // Reply to a request, e.g. "subscribe"/"unsubscribe"/"authenticate" ⋮---- // ================ File: examples/ws-futures-public.ts ================ import { WebsocketClient } from '../src'; // import from npm, after installing via npm `npm install bitmart-api` // import { WebsocketClient } from 'bitmart-api'; ⋮---- async function start() ⋮---- // Data received ⋮---- // Something happened, attempting to reconenct ⋮---- // Reconnect successful ⋮---- // Connection closed. If unexpected, expect reconnect -> reconnected. ⋮---- // Reply to a request, e.g. "subscribe"/"unsubscribe"/"authenticate" ⋮---- // Ticker Channel // client.subscribe('futures/ticker', 'futures'); ⋮---- // Depth Channel // client.subscribe('futures/depth20:BTCUSDT', 'futures'); ⋮---- // Trade Channel // client.subscribe('futures/trade:BTCUSDT', 'futures'); ⋮---- // KlineBin Channel // client.subscribe('futures/klineBin1m:BTCUSDT', 'futures'); ⋮---- // Or have multiple topics in one array: ================ File: examples/ws-spot-private.ts ================ import { LogParams, WebsocketClient } from '../src'; // import from npm, after installing via npm `npm install bitmart-api` // import { LogParams, WebsocketClient } from 'bitmart-api'; ⋮---- // eslint-disable-next-line @typescript-eslint/no-unused-vars ⋮---- async function start() ⋮---- // Data received ⋮---- // Something happened, attempting to reconenct ⋮---- // Reconnect successful ⋮---- // Connection closed. If unexpected, expect reconnect -> reconnected. ⋮---- // Reply to a request, e.g. "subscribe"/"unsubscribe"/"authenticate" ⋮---- // order progress ⋮---- // balance updates // client.subscribe('spot/user/balance:BALANCE_UPDATE', 'spot'); ================ File: examples/ws-spot-public.ts ================ import { WebsocketClient, // WsSpotOperation, } from '../src'; ⋮---- // WsSpotOperation, ⋮---- // import from npm, after installing via npm `npm install bitmart-api` // import { WebsocketClient } from 'bitmart-api'; ⋮---- async function start() ⋮---- // Some topics allow requests, here's an example for sending a request // const wsKey = 'spotPublicV1'; // if (data?.wsKey === wsKey) { // const depthIncreaseDataRequest: WsSpotOperation = { // op: 'request', // args: ['spot/depth/increase100:BTC_USDT'], // }; ⋮---- // client.tryWsSend( // 'spotPublicV1', // JSON.stringify(depthIncreaseDataRequest), // ); // } ⋮---- // Data received ⋮---- // Something happened, attempting to reconenct ⋮---- // Reconnect successful ⋮---- // Connection closed. If unexpected, expect reconnect -> reconnected. ⋮---- // Reply to a request, e.g. "subscribe"/"unsubscribe"/"authenticate" ⋮---- /** * Use the client subscribe(topic, market) pattern to subscribe to any websocket topic. * * You can subscribe to topics one at a time: */ ⋮---- // Ticker Channel // client.subscribe('spot/ticker:BTC_USDT', 'spot'); ⋮---- // KLine/Candles Channel // client.subscribe('spot/kline1m:BTC_USDT', 'spot'); ⋮---- // Depth-All Channel // client.subscribe('spot/depth5:BTC_USDT', 'spot'); ⋮---- // Depth-Increase Channel // client.subscribe('spot/depth/increase100:BTC_USDT', 'spot'); ⋮---- // Trade Channel // client.subscribe('spot/trade:BTC_USDT', 'spot'); ⋮---- /** * Or have multiple topics in one array, in a single request: */ ================ File: src/lib/websocket/WsStore.ts ================ import WebSocket from 'isomorphic-ws'; ⋮---- import { DefaultLogger } from '../logger.js'; import { WsConnectionStateEnum, WsStoredState } from './WsStore.types.js'; ⋮---- /** * Simple comparison of two objects, only checks 1-level deep (nested objects won't match) */ function isDeepObjectMatch(object1: unknown, object2: unknown) ⋮---- export class WsStore< WsKey extends string, ⋮---- constructor(logger: typeof DefaultLogger) ⋮---- /** Get WS stored state for key, optionally create if missing */ get( key: WsKey, createIfMissing?: true, ): WsStoredState<TWSTopicSubscribeEventArgs>; ⋮---- get( key: WsKey, createIfMissing?: false, ): WsStoredState<TWSTopicSubscribeEventArgs> | undefined; ⋮---- get( key: WsKey, createIfMissing?: boolean, ): WsStoredState<TWSTopicSubscribeEventArgs> | undefined ⋮---- getKeys(): WsKey[] ⋮---- create(key: WsKey): WsStoredState<TWSTopicSubscribeEventArgs> | undefined ⋮---- delete(key: WsKey): void ⋮---- // TODO: should we allow this at all? Perhaps block this from happening... ⋮---- /* connection websocket */ ⋮---- hasExistingActiveConnection(key: WsKey): boolean ⋮---- getWs(key: WsKey): WebSocket | undefined ⋮---- setWs(key: WsKey, wsConnection: WebSocket): WebSocket ⋮---- /* connection state */ ⋮---- isWsOpen(key: WsKey): boolean ⋮---- getConnectionState(key: WsKey): WsConnectionStateEnum ⋮---- setConnectionState(key: WsKey, state: WsConnectionStateEnum) ⋮---- isConnectionState(key: WsKey, state: WsConnectionStateEnum): boolean ⋮---- /* subscribed topics */ ⋮---- getTopics(key: WsKey): Set<TWSTopicSubscribeEventArgs> ⋮---- getTopicsByKey(): Record<string, Set<TWSTopicSubscribeEventArgs>> ⋮---- // Since topics are objects we can't rely on the set to detect duplicates getMatchingTopic(key: WsKey, topic: TWSTopicSubscribeEventArgs) ⋮---- // if (typeof topic === 'string') { // return this.getMatchingTopic(key, { channel: topic }); // } ⋮---- addTopic(key: WsKey, topic: TWSTopicSubscribeEventArgs) ⋮---- // if (typeof topic === 'string') { // return this.addTopic(key, { // instType: 'sp', // channel: topic, // instId: 'default', // }; // } // Check for duplicate topic. If already tracked, don't store this one ⋮---- deleteTopic(key: WsKey, topic: TWSTopicSubscribeEventArgs) ⋮---- // Check if we're subscribed to a topic like this ================ File: src/types/response/shared.types.ts ================ export interface APIResponse<TData = {}, TCode = number> { message: string; code: TCode; trace: string; data: TData; } ⋮---- export type OrderSide = 'buy' | 'sell'; ⋮---- /** * Spot & Futures uses this */ export interface AccountCurrencyBalanceV1 { currency: string; name: string; available: string; available_usd_valuation: string; frozen: string; } ================ File: .nvmrc ================ v22.17.1 ================ File: examples/futures-submit-order.ts ================ import { FuturesClientV2 } from '../src/index.js'; // // import from npm, after installing via npm `npm install bitmart-api` // import { FuturesClientV2 } from 'bitmart-api'; ⋮---- async function SumbitFuturesOrder() ⋮---- side: 1, // Order side - 1=buy_open_long -2=buy_close_short -3=sell_close_long -4=sell_open_short ================ File: examples/ws-futures-private.ts ================ import { DefaultLogger, LogParams, WebsocketClient } from '../src/index.js'; ⋮---- // import from npm, after installing via npm `npm install bitmart-api` // import { DefaultLogger, LogParams, WebsocketClient } from 'bitmart-api'; ⋮---- async function start() ⋮---- // Data received ⋮---- // Something happened, attempting to reconenct ⋮---- // Reconnect successful ⋮---- // Connection closed. If unexpected, expect reconnect -> reconnected. ⋮---- // Reply to a request, e.g. "subscribe"/"unsubscribe"/"authenticate" ⋮---- // Assets Channel ⋮---- // Position Channel // client.subscribe('futures/position', 'futures'); ⋮---- // Order Channel // client.subscribe('futures/order', 'futures'); ================ File: src/lib/requestUtils.ts ================ import WebSocket from 'isomorphic-ws'; ⋮---- import { REST_CLIENT_TYPE_ENUM, RestClientType } from './BaseRestClient.js'; ⋮---- export interface RestClientOptions { /** Your API key */ apiKey?: string; /** Your API secret */ apiSecret?: string; /** Your API memo (can be anything) that you included when creating this API key */ apiMemo?: string; /** * Override the default/global max size of the request window (in ms) for signed api calls. * If you don't include a recv window when making an API call, this value will be used as default */ recvWindow?: number; /** Default: false. If true, we'll throw errors if any params are undefined */ strictParamValidation?: boolean; /** * Optionally override API protocol + domain * e.g baseUrl: 'https://api.bitmart.com' **/ baseUrl?: string; /** Default: true. whether to try and post-process request exceptions (and throw them). */ parseExceptions?: boolean; /** * Allows you to provide a custom "signMessage" function, e.g. to use node's much faster createHmac method * * Look in the examples folder for a demonstration on using node's createHmac instead. */ customSignMessageFn?: (message: string, secret: string) => Promise<string>; /** * Enable keep alive for REST API requests (via axios). */ keepAlive?: boolean; /** * When using HTTP KeepAlive, how often to send TCP KeepAlive packets over sockets being kept alive. Default = 1000. * Only relevant if keepAlive is set to true. * Default: 1000 (defaults comes from https agent) */ keepAliveMsecs?: number; } ⋮---- /** Your API key */ ⋮---- /** Your API secret */ ⋮---- /** Your API memo (can be anything) that you included when creating this API key */ ⋮---- /** * Override the default/global max size of the request window (in ms) for signed api calls. * If you don't include a recv window when making an API call, this value will be used as default */ ⋮---- /** Default: false. If true, we'll throw errors if any params are undefined */ ⋮---- /** * Optionally override API protocol + domain * e.g baseUrl: 'https://api.bitmart.com' **/ ⋮---- /** Default: true. whether to try and post-process request exceptions (and throw them). */ ⋮---- /** * Allows you to provide a custom "signMessage" function, e.g. to use node's much faster createHmac method * * Look in the examples folder for a demonstration on using node's createHmac instead. */ ⋮---- /** * Enable keep alive for REST API requests (via axios). */ ⋮---- /** * When using HTTP KeepAlive, how often to send TCP KeepAlive packets over sockets being kept alive. Default = 1000. * Only relevant if keepAlive is set to true. * Default: 1000 (defaults comes from https agent) */ ⋮---- export function serializeParams<T extends Record<string, any> | undefined = {}>( params: T, strict_validation: boolean | undefined, encodeValues: boolean, prefixWith: string, ): string ⋮---- // Only prefix if there's a value ⋮---- export function getRestBaseUrl( useTestnet: boolean, restInverseOptions: RestClientOptions, restClientType: RestClientType, ): string ⋮---- export interface MessageEventLike { target: WebSocket; type: 'message'; data: string; } ⋮---- export function isMessageEvent(msg: unknown): msg is MessageEventLike ================ File: src/types/request/spot.types.ts ================ export interface SpotKlineV3Request { symbol: string; before?: number; after?: number; step?: number; limit?: number; } ⋮---- export interface SpotKlinesV1Request { symbol: string; from: number; to: number; step?: number; } ⋮---- export interface SpotOrderBookDepthV1Request { symbol: string; precision?: string; size?: number; } ⋮---- export interface SubmitWithdrawalV1Request { currency: string; amount: string; destination: 'To Digital Address'; address: string; address_memo?: string; } ⋮---- export interface DepositWithdrawHistoryV2Request { currency?: string; operation_type: 'deposit' | 'withdraw'; start_time?: number; end_time?: number; N: number; } ⋮---- export interface SubmitMarginTransferV1Request { symbol: string; currency: string; amount: string; side: 'in' | 'out'; } ⋮---- export interface SubmitSpotOrderV2Request { symbol: string; side: 'buy' | 'sell'; type: 'limit' | 'market' | 'limit_maker' | 'ioc'; stpmode?: 'none' | 'cancel_maker' | 'cancel_taker' | 'cancel_both'; client_order_id?: string; size?: string; price?: string; notional?: string; } ⋮---- export type CancelOrdersV3Request = { symbol: string; order_id?: string; client_order_id?: string; } & ({ order_id: string } | { client_order_id: string }); ⋮---- export interface SubmitSpotBatchOrdersV4Request { symbol: string; orderParams: { clientOrderId?: string; size?: string; price?: string; side: 'buy' | 'sell'; type: 'limit' | 'market' | 'limit_maker' | 'ioc'; stpmode?: 'none' | 'cancel_maker' | 'cancel_taker' | 'cancel_both'; notional?: string; }[]; recvWindow?: number; } ⋮---- export interface CancelSpotBatchOrdersV4Request { symbol: string; orderIds?: string[]; clientOrderIds?: string[]; recvWindow?: number; } ⋮---- export interface SpotOrderByIdV4Request { orderId: string; queryState?: 'open' | 'history'; recvwindow?: number; } ⋮---- export interface SpotOrderByClientOrderIdV4Request { clientOrderId: string; queryState?: 'open' | 'history'; recvwindow?: number; } ⋮---- export interface SpotOpenOrdersV4Request { orderMode?: 'spot' | 'iso_margin'; // Order mode: 'spot' for spot trade, 'iso_margin' for isolated margin trade startTime?: number; // Start time in milliseconds, e.g., 1681701557927 endTime?: number; // End time in milliseconds, e.g., 1681701557927 limit?: number; // Number of queries, allowed range [1,200], default is 200 recvWindow?: number; // Trade time limit, allowed range (0,60000], default: 5000 milliseconds } ⋮---- orderMode?: 'spot' | 'iso_margin'; // Order mode: 'spot' for spot trade, 'iso_margin' for isolated margin trade startTime?: number; // Start time in milliseconds, e.g., 1681701557927 endTime?: number; // End time in milliseconds, e.g., 1681701557927 limit?: number; // Number of queries, allowed range [1,200], default is 200 recvWindow?: number; // Trade time limit, allowed range (0,60000], default: 5000 milliseconds ⋮---- export interface SpotOrderTradeHistoryV4Request { orderMode?: 'spot' | 'iso_margin'; // Order mode: 'spot' for spot trade, 'iso_margin' for isolated margin trade startTime?: number; // Start time in milliseconds, e.g., 1681701557927 endTime?: number; // End time in milliseconds, e.g., 1681701557927 limit?: number; // Number of queries, allowed range [1,200], default is 200 recvWindow?: number; // Trade time limit, allowed range (0,60000], default: 5000 milliseconds symbol?: string; // Trading pair, e.g., BTC_USDT } ⋮---- orderMode?: 'spot' | 'iso_margin'; // Order mode: 'spot' for spot trade, 'iso_margin' for isolated margin trade startTime?: number; // Start time in milliseconds, e.g., 1681701557927 endTime?: number; // End time in milliseconds, e.g., 1681701557927 limit?: number; // Number of queries, allowed range [1,200], default is 200 recvWindow?: number; // Trade time limit, allowed range (0,60000], default: 5000 milliseconds symbol?: string; // Trading pair, e.g., BTC_USDT ⋮---- export interface MarginBorrowRepayV1Request { symbol: string; currency: string; amount: string; } ⋮---- export interface MarginBorrowRecordsV1Request { symbol: string; start_time?: number; end_time?: number; N?: number; borrow_id?: string; } ⋮---- export interface MarginRepayRecordsV1Request { symbol: string; start_time?: number; end_time?: number; N?: number; repay_id?: string; currency?: string; } ⋮---- export interface SubmitSubTransferSubToMainV1Request { requestNo: string; amount: string; currency: string; } ⋮---- export interface SubmitSubTransferV1Request { requestNo: string; amount: string; currency: string; subAccount: string; } ⋮---- export interface SubmitMainTransferSubToSubV1Request { requestNo: string; amount: string; currency: string; fromAccount: string; toAccount: string; } ⋮---- export interface SubTransfersV1Request { moveType: 'spot to spot'; N: number; accountName?: string; } ⋮---- export interface AccountSubTransfersV1Request { moveType: 'spot to spot'; N: number; } ⋮---- export interface SubSpotWalletBalancesV1Request { subAccount: string; currency?: string; } ⋮---- export interface SpotBrokerRebateRequest { start_time?: number; end_time?: number; } ================ File: src/index.ts ================ ================ File: .eslintrc.cjs ================ // 'no-unused-vars': ['warn'], ================ File: src/lib/websocket/websocket-util.ts ================ import WebSocket from 'isomorphic-ws'; ⋮---- /** Should be one WS key per unique URL */ ⋮---- /** This is used to differentiate between each of the available websocket streams */ export type WsKey = (typeof WS_KEY_MAP)[keyof typeof WS_KEY_MAP]; ⋮---- /** * Some exchanges have two livenet environments, some have test environments, some dont. This allows easy flexibility for different exchanges. * Examples: * - One livenet and one testnet: NetworkMap<'livenet' | 'testnet'> * - One livenet, sometimes two, one testnet: NetworkMap<'livenet' | 'testnet', 'livenet2'> * - Only one livenet, no other networks: NetworkMap<'livenet'> */ type NetworkMap< TRequiredKeys extends string, TOptionalKeys extends string | undefined = undefined, > = Record<TRequiredKeys, string> & (TOptionalKeys extends string ? Record<TOptionalKeys, string | undefined> : Record<TRequiredKeys, string>); ⋮---- export function neverGuard(x: never, msg: string): Error ⋮---- /** * ws.terminate() is undefined in browsers. * This only works in node.js, not in browsers. * Does nothing if `ws` is undefined. Does nothing in browsers. */ export function safeTerminateWs( ws?: WebSocket | any, fallbackToClose?: boolean, ): boolean ================ File: src/lib/BaseRestClient.ts ================ import axios, { AxiosRequestConfig, AxiosResponse, Method } from 'axios'; import https from 'https'; ⋮---- import { neverGuard } from './misc-util.js'; import { APIID, getRestBaseUrl, RestClientOptions, serializeParams, } from './requestUtils.js'; import { signMessage } from './webCryptoAPI.js'; ⋮---- /** * Used to switch how authentication/requests work under the hood */ ⋮---- export type RestClientType = (typeof REST_CLIENT_TYPE_ENUM)[keyof typeof REST_CLIENT_TYPE_ENUM]; ⋮---- interface SignedRequest<T extends object | undefined = {}> { originalParams: T; paramsWithSign?: T & { sign: string }; serializedParams: string; sign: string; queryParamsWithSign: string; timestamp: number; recvWindow: number; } ⋮---- interface UnsignedRequest<T extends object | undefined = {}> { originalParams: T; paramsWithSign: T; } ⋮---- type SignMethod = 'bitmart'; ⋮---- /** * Enables: * - Detailed request/response logging * - Full request dump in any exceptions thrown from API responses */ ⋮---- // request: { // url: response.config.url, // method: response.config.method, // data: response.config.data, // headers: response.config.headers, // }, ⋮---- export abstract class BaseRestClient ⋮---- /** Defines the client type (affecting how requests & signatures behave) */ abstract getClientType(): RestClientType; ⋮---- /** * Create an instance of the REST client. Pass API credentials in the object in the first parameter. * @param {RestClientOptions} [restClientOptions={}] options to configure REST API connectivity * @param {AxiosRequestConfig} [networkOptions={}] HTTP networking options for axios */ constructor( restClientOptions: RestClientOptions = {}, networkOptions: AxiosRequestConfig = {}, ) ⋮---- /** Throw errors if any request params are empty */ ⋮---- /** in ms == 5 minutes by default */ ⋮---- /** inject custom rquest options based on axios specs - see axios docs for more guidance on AxiosRequestConfig: https://github.com/axios/axios#request-config */ ⋮---- // If enabled, configure a https agent with keepAlive enabled ⋮---- // Extract existing https agent parameters, if provided, to prevent the keepAlive flag from overwriting an existing https agent completely ⋮---- // For more advanced configuration, raise an issue on GitHub or use the "networkOptions" // parameter to define a custom httpsAgent with the desired properties ⋮---- // Throw if one of the 3 values is missing, but at least one of them is set ⋮---- /** * Timestamp used to sign the request. Override this method to implement your own timestamp/sync mechanism */ getSignTimestampMs(): number ⋮---- get(endpoint: string, params?: any) ⋮---- post(endpoint: string, params?: any) ⋮---- getPrivate(endpoint: string, params?: any) ⋮---- postPrivate(endpoint: string, params?: any) ⋮---- deletePrivate(endpoint: string, params?: any) ⋮---- /** * @private Make a HTTP request to a specific endpoint. Private endpoint API calls are automatically signed. */ private async _call( method: Method, endpoint: string, params?: any, isPublicApi?: boolean, ): Promise<any> ⋮---- // Sanity check to make sure it's only ever prefixed by one forward slash ⋮---- // Build a request and handle signature process ⋮---- // Dispatch request ⋮---- // Throw API rejections by parsing the response code from the body ⋮---- /** * @private generic handler to parse request exceptions */ parseException(e: any, request: AxiosRequestConfig<any>): unknown ⋮---- // Something happened in setting up the request that triggered an error ⋮---- // request made but no response received ⋮---- // The request was made and the server responded with a status code // that falls out of the range of 2xx ⋮---- // console.error('err: ', response?.data); ⋮---- // Prevent credentials from leaking into error messages ⋮---- /** * @private sign request and set recv window */ private async signRequest<T extends object | undefined = {}>( data: T, _endpoint: string, method: Method, signMethod: SignMethod, ): Promise<SignedRequest<T>> ⋮---- // It's possible to override the recv window on a per rquest level ⋮---- private async prepareSignParams<TParams extends object | undefined>( method: Method, endpoint: string, signMethod: SignMethod, params?: TParams, isPublicApi?: true, ): Promise<UnsignedRequest<TParams>>; ⋮---- private async prepareSignParams<TParams extends object | undefined>( method: Method, endpoint: string, signMethod: SignMethod, params?: TParams, isPublicApi?: false | undefined, ): Promise<SignedRequest<TParams>>; ⋮---- private async prepareSignParams<TParams extends object | undefined>( method: Method, endpoint: string, signMethod: SignMethod, params?: TParams, isPublicApi?: boolean, ) ⋮---- /** Returns an axios request object. Handles signing process automatically if this is a private API call */ private async buildRequest( method: Method, endpoint: string, url: string, params?: any, isPublicApi?: boolean, ): Promise<AxiosRequestConfig> ================ File: src/lib/BaseWSClient.ts ================ import EventEmitter from 'events'; import WebSocket from 'isomorphic-ws'; ⋮---- import { WebsocketClientOptions, WSClientConfigurableOptions, } from '../types/websockets/client.js'; import { WS_LOGGER_CATEGORY } from '../WebsocketClient.js'; import { DefaultLogger } from './logger.js'; import { isMessageEvent, MessageEventLike } from './requestUtils.js'; import { safeTerminateWs } from './websocket/websocket-util.js'; import { WsStore } from './websocket/WsStore.js'; import { WsConnectionStateEnum } from './websocket/WsStore.types.js'; ⋮---- interface WSClientEventMap<WsKey extends string> { /** Connection opened. If this connection was previously opened and reconnected, expect the reconnected event instead */ open: (evt: { wsKey: WsKey; event: any }) => void; /** Reconnecting a dropped connection */ reconnect: (evt: { wsKey: WsKey; event: any }) => void; /** Successfully reconnected a connection that dropped */ reconnected: (evt: { wsKey: WsKey; event: any }) => void; /** Connection closed */ close: (evt: { wsKey: WsKey; event: any }) => void; /** Received reply to websocket command (e.g. after subscribing to topics) */ response: (response: any & { wsKey: WsKey }) => void; /** Received data for topic */ update: (response: any & { wsKey: WsKey }) => void; /** Exception from ws client OR custom listeners (e.g. if you throw inside your event handler) */ exception: (response: any & { wsKey: WsKey }) => void; /** Confirmation that a connection successfully authenticated */ authenticated: (event: { wsKey: WsKey; event: any }) => void; } ⋮---- /** Connection opened. If this connection was previously opened and reconnected, expect the reconnected event instead */ ⋮---- /** Reconnecting a dropped connection */ ⋮---- /** Successfully reconnected a connection that dropped */ ⋮---- /** Connection closed */ ⋮---- /** Received reply to websocket command (e.g. after subscribing to topics) */ ⋮---- /** Received data for topic */ ⋮---- /** Exception from ws client OR custom listeners (e.g. if you throw inside your event handler) */ ⋮---- /** Confirmation that a connection successfully authenticated */ ⋮---- export interface EmittableEvent<TEvent = any> { eventType: 'response' | 'update' | 'exception' | 'authenticated'; event: TEvent; } ⋮---- // Type safety for on and emit handlers: https://stackoverflow.com/a/61609010/880837 export interface BaseWebsocketClient< // eslint-disable-next-line @typescript-eslint/no-unused-vars TWSMarket extends string, TWSKey extends string, // eslint-disable-next-line @typescript-eslint/no-unused-vars TWSTopic = any, > { on<U extends keyof WSClientEventMap<TWSKey>>( event: U, listener: WSClientEventMap<TWSKey>[U], ): this; emit<U extends keyof WSClientEventMap<TWSKey>>( event: U, ...args: Parameters<WSClientEventMap<TWSKey>[U]> ): boolean; } ⋮---- // eslint-disable-next-line @typescript-eslint/no-unused-vars ⋮---- // eslint-disable-next-line @typescript-eslint/no-unused-vars ⋮---- on<U extends keyof WSClientEventMap<TWSKey>>( event: U, listener: WSClientEventMap<TWSKey>[U], ): this; ⋮---- emit<U extends keyof WSClientEventMap<TWSKey>>( event: U, ...args: Parameters<WSClientEventMap<TWSKey>[U]> ): boolean; ⋮---- // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging export abstract class BaseWebsocketClient< TWSMarket extends string, ⋮---- /** * The "topic" being subscribed to, not the event sent to subscribe to one or more topics */ ⋮---- constructor( options?: WSClientConfigurableOptions, logger?: typeof DefaultLogger, ) ⋮---- protected abstract getWsKeyForMarket( market: TWSMarket, isPrivate?: boolean, ): TWSKey; ⋮---- protected abstract sendPingEvent(wsKey: TWSKey, ws: WebSocket): void; ⋮---- protected abstract isWsPong(data: any): boolean; ⋮---- protected abstract getWsAuthRequestEvent(wsKey: TWSKey): Promise<object>; ⋮---- protected abstract getWsMarketForWsKey(key: TWSKey): TWSMarket; ⋮---- protected abstract isPrivateChannel(subscribeEvent: TWSTopic): boolean; ⋮---- protected abstract getPrivateWSKeys(): TWSKey[]; ⋮---- protected abstract getWsUrl(wsKey: TWSKey): string; ⋮---- protected abstract getMaxTopicsPerSubscribeEvent( wsKey: TWSKey, ): number | null; ⋮---- /** * Returns a list of string events that can be individually sent upstream to complete subscribing to these topics */ protected abstract getWsSubscribeEventsForTopics( topics: TWSTopic[], wsKey: TWSKey, ): string[]; ⋮---- /** * Returns a list of string events that can be individually sent upstream to complete unsubscribing to these topics */ protected abstract getWsUnsubscribeEventsForTopics( topics: TWSTopic[], wsKey: TWSKey, ): string[]; ⋮---- /** * Abstraction called to sort ws events into emittable event types (response to a request, data update, etc) */ protected abstract resolveEmittableEvents( event: MessageEventLike, ): EmittableEvent[]; ⋮---- /** * Request connection of all dependent (publi