bitmart-api
Version:
Complete & robust Node.js SDK for BitMart's REST APIs and WebSockets, with TypeScript declarations.
1,682 lines (1,578 loc) • 143 kB
Plain Text
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
LICENSE.md
package.json
postBuild.sh
PR_SUMMARY.md
README.md
tea.yaml
tsconfig.cjs.json
tsconfig.esm.json
tsconfig.json
tsconfig.linting.json
================================================================
Files
================================================================
================
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/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/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/tsconfig.examples.json
================
{
"extends": "../tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "dist/cjs",
"target": "esnext",
"rootDir": "../"
},
"include": ["../src/**/*.*", "../examples/**/*.*"]
}
================
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-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: 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.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/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;
/**
* Default: false. If true, use the simulated trading demo environment.
* For V2 Futures WebSocket: wss://openapi-wsdemo-v2.bitmart.com
* Note: The API keys for Simulated-Environment and Prod-Environment are the same.
*/
useDemo?: 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>;
}
⋮----
/** 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 */
⋮----
/**
* Default: false. If true, use the simulated trading demo environment.
* For V2 Futures WebSocket: wss://openapi-wsdemo-v2.bitmart.com
* Note: The API keys for Simulated-Environment and Prod-Environment are the same.
*/
⋮----
/**
* 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: src/index.ts
================
================
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: PR_SUMMARY.md
================
# PR Summary: BitMart API Updates - New Endpoints & Demo Trading Support
## Overview
This PR implements updates from BitMart's API changelog (2025-11-18 to 2025-12-11), adding new endpoints, updating existing ones, and introducing demo/simulated trading environment support.
## Changes
### 1. Updated `/contract/private/trades` Endpoint (2025-12-02)
**Files Modified:**
- `src/types/request/futures.types.ts`
**Changes:**
- Added optional `order_id` and `client_order_id` fields to `FuturesAccountTradesRequest`
- Made `symbol` field optional to support querying by order ID or client order ID
- Enables querying order trades by order ID or client order ID in addition to symbol
### 2. New Affiliate Rebate Endpoints (2025-12-11)
**Files Modified:**
- `src/types/request/futures.types.ts`
- `src/types/response/futures.types.ts`
- `src/FuturesClientV2.ts`
**New Endpoints:**
- `getFuturesAffiliateRebateUser()` - `/contract/private/affiliate/rebate-user`
- Query contract rebate data for users (up to 60 days)
- `getFuturesAffiliateRebateApi()` - `/contract/private/affiliate/rebate-api`
- Query contract API rebate data (up to 60 days)
**New Types:**
- `FuturesAffiliateRebateUserRequest` - Request type for user rebate data
- `FuturesAffiliateRebateApiRequest` - Request type for API rebate data
- `FuturesAffiliateRebateUserResponse` - Response type for user rebate data
- `FuturesAffiliateRebateApiResponse` - Response type for API rebate data
### 3. Simulated Trading / Demo Environment Support (2025-11-18)
**Files Modified:**
- `src/types/request/futures.types.ts`
- `src/types/response/futures.types.ts`
- `src/lib/requestUtils.ts`
- `src/lib/websocket/websocket-util.ts`
- `src/types/websockets/client.ts`
- `src/FuturesClientV2.ts`
- `src/WebsocketClient.ts`
- `README.md`
**REST API Changes:**
- Added `useDemo` option to `RestClientOptions` interface
- Updated `getRestBaseUrl()` to support demo environment
- Demo REST endpoint: `https://demo-api-cloud-v2.bitmart.com`
- Added `submitFuturesSimulatedClaim()` method for simulated account claim
- New types: `SubmitFuturesSimulatedClaimRequest`, `FuturesSimulatedClaimResponse`
**WebSocket Changes:**
- Added `useDemo` option to `WSClientConfigurableOptions` interface
- Updated `WS_BASE_URL_MAP` to include demo URLs for V2 Futures
- Updated `getWsUrl()` method to support demo environment
- Demo WebSocket endpoints:
- Public: `wss://openapi-wsdemo-v2.bitmart.com/api?protocol=1.1`
- Private: `wss://openapi-wsdemo-v2.bitmart.com/user?protocol=1.1`
**Documentation:**
- Added demo trading section to README.md
- Included examples for both REST and WebSocket clients
- Clarified that demo environment is only available for V2 Futures
## Usage Examples
### New Affiliate Rebate Endpoints
```typescript
// Get user rebate data
const userRebate = await futuresClient.getFuturesAffiliateRebateUser({
cid: 1000000,
start_time: 1000000000,
end_time: 2000000000,
});
// Get API rebate data
const apiRebate = await futuresClient.getFuturesAffiliateRebateApi({
cid: 1000000,
start_time: 1000000000,
end_time: 2000000000,
});
```
### Updated Trades Endpoint
```typescript
// Query by order ID
const trades = await futuresClient.getFuturesAccountTrades({
order_id: 123456789,
});
// Query by client order ID
const trades = await futuresClient.getFuturesAccountTrades({
client_order_id: 'my-order-id',
});
```
### Demo Trading Environment
```typescript
// REST API
const demoClient = new FuturesClientV2({
apiKey: API_KEY,
apiSecret: API_SECRET,
apiMemo: API_MEMO,
useDemo: true,
});
// WebSocket
const demoWsClient = new WebsocketClient({
apiKey: API_KEY,
apiSecret: API_SECRET,
apiMemo: API_MEMO,
useDemo: true, // V2 Futures only
});
// Simulated claim
await demoClient.submitFuturesSimulatedClaim({
currency: 'USDT',
amount: '10',
});
```
## Breaking Changes
None - all changes are backward compatible.
## Notes
- Demo environment is only available for V2 Futures (not Spot or V1 Futures)
- The same API keys work for both production and demo environments
- Manual `baseUrl`/`wsUrl` override still takes precedence over `useDemo` flag
- All new endpoints follow existing code patterns and conventions
## Testing
- All TypeScript types are properly defined
- Linter checks pass
- Code follows existing patterns and conventions
- Documentation updated with examples
================
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/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: 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/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: false. If true, use the simulated trading demo environment.
* For V2 Futures: https://demo-api-cloud-v2.bitmart.com
* Note: The API keys for Simulated-Environment and Prod-Environment are the same.
*/
useDemo?: boolean;
/** 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: false. If true, use the simulated trading demo environment.
* For V2 Futures: https://demo-api-cloud-v2.bitmart.com
* Note: The API keys for Simulated-Environment and Prod-Environment are the same.
*/
⋮----
/** 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
⋮----
// Demo environment is only available for V2 Futures
⋮----
// For V1, fall back to production
⋮----
export interface MessageEventLike {
target: WebSocket;
type: 'message';
data: string;
}
⋮----
export function isMessageEvent(msg: unknown): msg is MessageEventLike
================
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>
⋮----
export function checkWebCryptoAPISupported()
================
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: LICENSE.md
================
Copyright 2025 Tiago Siebler
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================
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/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/WebsocketClient.ts
================
import WebSocket from 'isomorphic-ws';
⋮----
import { BaseWebsocketClient, EmittableEvent } from './lib/BaseWSClient.js';
import { neverGuard } from './lib/misc-util.js';
import { MessageEventLike } from './lib/requestUtils.js';
import { signMessage } from './lib/webCryptoAPI.js';
import {
WS_BASE_URL_MAP,
WS_KEY_MAP,
WsKey,
} from './lib/websocket/websocket-util.js';
import { WsMarket } from './types/websockets/client.js';
import {
WsFuturesOperation,
WsOperation,
WsRequestOperation,
WsSpotOperation,
} from './types/websockets/requests.js';
⋮----
/** Any WS keys in this list will trigger auth on connect, if credentials are available */
⋮----
/** Any WS keys in this list will ALWAYS skip the authentication process, even if credentials are available */
⋮----
/**
* WS topics are always a string for bitmart. Some exchanges use complex objects
*/
type WsTopic = string;
⋮----
export class WebsocketClient extends BaseWebsocketClient<
⋮----
/**
* Request connection of all dependent (public & private) websockets, instead of waiting for automatic connection by library
*/
public connectAll(): Promise<WebSocket | undefined>[]
⋮----
/**
* Request subscription to one or more topics.
*
* - Subscriptions are automatically routed to the correct websocket connection.
* - Authentication/connection is automatic.
* - Resubscribe after network issues is automatic.
*
* Call `unsubscribeTopics(topics)` to remove topics
*/
public subscribeTopics(topics: WsTopic[])
⋮----
/**
* Unsubscribe from one or more topics.
*
* - Requests are automatically routed to the correct websocket connection.
* - These topics will be removed from the topic cache, so they won't be subscribed to again.
*/
public unsubscribeTopics(topics: WsTopic[])
⋮----
/**
*
* Internal methods
*
*/
⋮----
protected sendPingEvent(wsKey: WsKey)
⋮----
protected isWsPong(msg: any): boolean
⋮----
// bitmart spot
⋮----
// bitmart futures
// if (typeof event?.data === 'string') {
// return true;
// }
⋮----
// this.logger.info(`Not a pong: `, msg);
⋮----
protected resolveEmittableEvents(event: MessageEventLike): EmittableEvent[]
⋮----
// These are request/reply pattern events (e.g. after subscribing to topics or authenticating)
⋮----
// Request/reply pattern for authentication success
⋮----
/**
* Determines if a topic is for a private channel, using a hardcoded list of strings
*/
protected isPrivateChannel(topic: WsTopic): boolean
⋮----
// console.error(`No topic name? "${topicName}" from topic "${topic}"?`);
⋮----
/** Spot */
⋮----
/** Futures */
⋮----
protected getWsKeyForMarket(market: WsMarket, isPrivate: boolean): WsKey
⋮----
protected getWsMarketForWsKey(key: WsKey): WsMarket
⋮----
protected getWsKeyForTopic(topic: WsTopic): WsKey
⋮----
protected getPrivateWSKeys(): WsKey[]
⋮----
protected getWsUrl(wsKey: WsKey): string
⋮----
// Demo environment is only available for V2 Futures
⋮----
// Fallback to livenet if demo is not available for this wsKey
⋮----
/** Force subscription requests to be sent in smaller batches, if a number is returned */
protected getMaxTopicsPerSubscribeEvent(wsKey: WsKey): number | null
⋮----
// Return a number if there's a limit on the number of sub topics per rq
⋮----
/**
* Map one or more topics into fully prepared "subscribe request" events (already stringified and ready to send)
*/
protected getWsSubscribeEventsForTopics(
topics: WsTopic[],
wsKey: WsKey,
): string[]
⋮----
/**
* Map one or more topics into fully prepared "unsubscribe request" events (already stringified and ready to send)
*/
protec