mexc-futures-sdk
Version:
Unofficial TypeScript SDK for MEXC Futures trading with maintenance bypass. Uses browser session tokens to work 24/7 even during API downtime.
408 lines • 16.8 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MexcFuturesSDK = void 0;
const axios_1 = __importDefault(require("axios"));
const constants_1 = require("./utils/constants");
const headers_1 = require("./utils/headers");
const logger_1 = require("./utils/logger");
const errors_1 = require("./utils/errors");
const json_bigint_1 = __importDefault(require("json-bigint"));
// Big-int-safe + prototype-pollution-hardened JSON parser for HTTP responses.
// storeAsString: ids > 2^53 are returned as exact STRINGS (not native bigint) — this preserves
// precision AND stays JSON.stringify-safe (native bigint throws on JSON.stringify, breaking any
// consumer that logs/persists a response); protoAction/constructorAction "ignore": drop
// `__proto__`/`constructor` keys from untrusted responses.
const jsonBig = (0, json_bigint_1.default)({
storeAsString: true,
protoAction: "ignore",
constructorAction: "ignore",
});
class MexcFuturesSDK {
constructor(config) {
this.config = config;
this.logger = new logger_1.Logger(config.logLevel);
this.httpClient = axios_1.default.create({
baseURL: config.baseURL || "https://futures.mexc.com/api/v1",
timeout: config.timeout || 30000,
headers: (0, headers_1.generateHeaders)(config),
// Parse responses with a big-int-safe JSON parser. MEXC order ids exceed
// Number.MAX_SAFE_INTEGER (e.g. 817027833053397504), and the default JSON.parse
// silently corrupts them (…397504 -> …397500). JSONBigInt preserves them as BigInt
// so String(orderId) is exact; falls back to the raw string on non-JSON payloads.
transformResponse: [
(data) => {
try {
return jsonBig.parse(data);
}
catch {
return data;
}
},
],
});
// Request interceptor for debugging
this.httpClient.interceptors.request.use((requestConfig) => {
this.logger.debug(`🌐 ${requestConfig.method?.toUpperCase()} ${requestConfig.baseURL}${requestConfig.url}`);
if (requestConfig.data) {
this.logger.debug("📦 Request body:", JSON.stringify(requestConfig.data, null, 2));
}
return requestConfig;
});
// Response interceptor for error handling
this.httpClient.interceptors.response.use((response) => {
this.logger.debug(`✅ ${response.status} ${response.statusText}`);
return response;
}, (error) => {
// Parse the axios error into a user-friendly MEXC error
const mexcError = (0, errors_1.parseAxiosError)(error, error.config?.url, error.config?.method?.toUpperCase());
// Log the user-friendly error message
this.logger.error(mexcError.getUserFriendlyMessage());
// Log detailed error information in debug mode
if (this.logger.isDebugEnabled()) {
this.logger.debug("Detailed error info:", (0, errors_1.formatErrorForLogging)(mexcError));
}
return Promise.reject(mexcError);
});
}
/**
* Submit order using /api/v1/private/order/submit endpoint
* This is the alternative order submission method used by MEXC browser
*/
async submitOrder(orderParams) {
try {
// Validate params BEFORE signing/sending — this is a live-money endpoint, so a NaN/0/undefined
// field or a wrong enum must never reach the exchange as a signed order.
const p = orderParams;
if (!p || typeof p.symbol !== "string" || p.symbol.length === 0) {
throw new errors_1.MexcValidationError("symbol is required", "symbol");
}
if (!Number.isFinite(p.price) || p.price < 0) {
throw new errors_1.MexcValidationError("price must be a finite number >= 0", "price");
}
if (!Number.isFinite(p.vol) || p.vol <= 0) {
throw new errors_1.MexcValidationError("vol must be a finite number > 0", "vol");
}
if (![1, 2, 3, 4].includes(p.side)) {
throw new errors_1.MexcValidationError("side must be one of 1,2,3,4", "side");
}
if (![1, 2, 3, 4, 5, 6].includes(p.type)) {
throw new errors_1.MexcValidationError("type must be one of 1..6", "type");
}
if (![1, 2].includes(p.openType)) {
throw new errors_1.MexcValidationError("openType must be 1 (isolated) or 2 (cross)", "openType");
}
if (p.leverage !== undefined && (!Number.isFinite(p.leverage) || p.leverage <= 0)) {
throw new errors_1.MexcValidationError("leverage must be a finite number > 0", "leverage");
}
if (p.positionId !== undefined && !Number.isFinite(p.positionId)) {
throw new errors_1.MexcValidationError("positionId must be a finite number", "positionId");
}
if (p.stopLossPrice !== undefined && (!Number.isFinite(p.stopLossPrice) || p.stopLossPrice < 0)) {
throw new errors_1.MexcValidationError("stopLossPrice must be a finite number >= 0", "stopLossPrice");
}
if (p.takeProfitPrice !== undefined && (!Number.isFinite(p.takeProfitPrice) || p.takeProfitPrice < 0)) {
throw new errors_1.MexcValidationError("takeProfitPrice must be a finite number >= 0", "takeProfitPrice");
}
this.logger.info("🚀 Submitting order using /submit endpoint");
this.logger.debug("📦 Order parameters:", JSON.stringify(orderParams, null, 2));
// Generate headers with MEXC signature
const headers = (0, headers_1.generateHeaders)({
authToken: this.config.authToken,
userAgent: this.config.userAgent,
customHeaders: this.config.customHeaders,
}, true, orderParams);
const response = await this.httpClient.post(constants_1.ENDPOINTS.SUBMIT_ORDER, orderParams, {
headers,
});
this.logger.debug("🔍 Order response:", response.data);
return response.data;
}
catch (error) {
// Error is already logged by the interceptor with user-friendly message
throw error;
}
}
/**
* Cancel orders by order IDs (up to 50 orders at once)
*/
async cancelOrder(orderIds) {
try {
if (orderIds.length === 0) {
throw new errors_1.MexcValidationError("Order IDs array cannot be empty", "orderIds");
}
if (orderIds.length > 50) {
throw new errors_1.MexcValidationError("Cannot cancel more than 50 orders at once", "orderIds");
}
// Serialize ids as strings: real MEXC order ids exceed 2^53 and cannot round-trip through a JS
// number. Pass them through (string/bigint) and stringify so the signed body and the posted body
// carry the exact id. The signature MUST be computed over the same payload that is sent.
const ids = orderIds.map((id) => String(id));
// Generate headers with MEXC signature for POST request
const headers = (0, headers_1.generateHeaders)({
authToken: this.config.authToken,
userAgent: this.config.userAgent,
customHeaders: this.config.customHeaders,
}, true, ids);
const response = await this.httpClient.post(constants_1.ENDPOINTS.CANCEL_ORDER, ids, {
headers,
});
this.logger.debug("🔍 Cancel order response:", response.data);
return response.data;
}
catch (error) {
// Error is already logged by the interceptor with user-friendly message
throw error;
}
}
/**
* Cancel order by external order ID
*/
async cancelOrderByExternalId(params) {
try {
// Generate headers with MEXC signature for POST request
const headers = (0, headers_1.generateHeaders)({
authToken: this.config.authToken,
userAgent: this.config.userAgent,
customHeaders: this.config.customHeaders,
}, true, params);
const response = await this.httpClient.post(constants_1.ENDPOINTS.CANCEL_ORDER_BY_EXTERNAL_ID, params, {
headers,
});
this.logger.debug("🔍 Cancel order by external ID response:", response.data);
return response.data;
}
catch (error) {
// Error is already logged by the interceptor with user-friendly message
throw error;
}
}
/**
* Cancel all orders under a contract (or all orders if no symbol provided)
*/
async cancelAllOrders(params) {
try {
const payload = params || {};
// Generate headers with MEXC signature for POST request
const headers = (0, headers_1.generateHeaders)({
authToken: this.config.authToken,
userAgent: this.config.userAgent,
customHeaders: this.config.customHeaders,
}, true, payload);
const response = await this.httpClient.post(constants_1.ENDPOINTS.CANCEL_ALL_ORDERS, payload, {
headers,
});
return response.data;
}
catch (error) {
// Error is already logged by the interceptor with user-friendly message
throw error;
}
}
/**
* Get order history
*/
async getOrderHistory(params) {
try {
const response = await this.httpClient.get(constants_1.ENDPOINTS.ORDER_HISTORY, {
params,
});
return response.data;
}
catch (error) {
// Error is already logged by the interceptor with user-friendly message
throw error;
}
}
/**
* Get all transaction details of the user's orders
*/
async getOrderDeals(params) {
try {
const response = await this.httpClient.get(constants_1.ENDPOINTS.ORDER_DEALS, {
params,
});
return response.data;
}
catch (error) {
// Error is already logged by the interceptor with user-friendly message
throw error;
}
}
/**
* Get order information by order ID
* @param orderId Order ID to query
* @returns Detailed order information
*/
async getOrder(orderId) {
try {
const response = await this.httpClient.get(`${constants_1.ENDPOINTS.GET_ORDER}/${encodeURIComponent(String(orderId))}`);
this.logger.debug("🔍 Order response:", response.data);
return response.data;
}
catch (error) {
// Error is already logged by the interceptor with user-friendly message
throw error;
}
}
/**
* Get order information by external order ID
* @param symbol Contract symbol (e.g., "BTC_USDT")
* @param externalOid External order ID
* @returns Detailed order information
*/
async getOrderByExternalId(symbol, externalOid) {
try {
const response = await this.httpClient.get(`${constants_1.ENDPOINTS.GET_ORDER_BY_EXTERNAL_ID}/${encodeURIComponent(symbol)}/${encodeURIComponent(externalOid)}`);
return response.data;
}
catch (error) {
// Error is already logged by the interceptor with user-friendly message
throw error;
}
}
/**
* Get risk limits for account
*/
async getRiskLimit() {
try {
const response = await this.httpClient.get(constants_1.ENDPOINTS.RISK_LIMIT);
return response.data;
}
catch (error) {
// Error is already logged by the interceptor with user-friendly message
throw error;
}
}
/**
* Get fee rates for contracts
*/
async getFeeRate() {
try {
const response = await this.httpClient.get(constants_1.ENDPOINTS.FEE_RATE);
return response.data;
}
catch (error) {
// Error is already logged by the interceptor with user-friendly message
throw error;
}
}
/**
* Get user's single currency asset information
* @param currency Currency symbol (e.g., "USDT", "BTC")
* @returns Account asset information for the specified currency
*/
async getAccountAsset(currency) {
try {
const response = await this.httpClient.get(`${constants_1.ENDPOINTS.ACCOUNT_ASSET}/${encodeURIComponent(currency)}`);
return response.data;
}
catch (error) {
// Error is already logged by the interceptor with user-friendly message
throw error;
}
}
/**
* Get user's current holding positions
* @param symbol Optional: specific contract symbol to filter positions
* @returns List of open positions
*/
async getOpenPositions(symbol) {
try {
const params = symbol ? { symbol } : {};
const response = await this.httpClient.get(constants_1.ENDPOINTS.OPEN_POSITIONS, {
params,
});
return response.data;
}
catch (error) {
// Error is already logged by the interceptor with user-friendly message
throw error;
}
}
/**
* Get user's history position information
* @param params Parameters for filtering position history
* @returns List of historical positions
*/
async getPositionHistory(params) {
try {
const response = await this.httpClient.get(constants_1.ENDPOINTS.POSITION_HISTORY, {
params,
});
return response.data;
}
catch (error) {
// Error is already logged by the interceptor with user-friendly message
throw error;
}
}
/**
* Get ticker data for a specific symbol
*/
async getTicker(symbol) {
try {
const response = await this.httpClient.get(constants_1.ENDPOINTS.TICKER, {
params: { symbol },
});
return response.data;
}
catch (error) {
// Error is already logged by the interceptor with user-friendly message
throw error;
}
}
/**
* Get contract information
* @param symbol Optional: specific contract symbol (e.g., "BTC_USDT"). If not provided, returns all contracts
* @returns Contract details for specified symbol or all contracts
*/
async getContractDetail(symbol) {
try {
const params = symbol ? { symbol } : {};
const response = await this.httpClient.get(constants_1.ENDPOINTS.CONTRACT_DETAIL, {
params,
});
return response.data;
}
catch (error) {
// Error is already logged by the interceptor with user-friendly message
throw error;
}
}
/**
* Get contract's depth information (order book)
* @param symbol Contract symbol (e.g., "BTC_USDT")
* @param limit Optional: depth tier limit
* @returns Order book with bids and asks
*/
async getContractDepth(symbol, limit) {
try {
const params = limit ? { limit } : {};
const response = await this.httpClient.get(`${constants_1.ENDPOINTS.CONTRACT_DEPTH}/${encodeURIComponent(symbol)}`, { params });
return response.data;
}
catch (error) {
// Error is already logged by the interceptor with user-friendly message
throw error;
}
}
/**
* Test connection to the API (using public endpoint)
*/
async testConnection() {
try {
// Test with a common symbol
await this.getTicker("BTC_USDT");
return true;
}
catch (error) {
// Error is already logged by the interceptor, just return false
return false;
}
}
}
exports.MexcFuturesSDK = MexcFuturesSDK;
//# sourceMappingURL=client.js.map