toobit-trade-cli 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -167,7 +167,9 @@ var TOOBIT_CODE_BEHAVIORS = {
167
167
  "-2013": { retry: false, suggestion: "Order does not exist." },
168
168
  "-2014": { retry: false, suggestion: "Bad API key format." },
169
169
  "-2015": { retry: false, suggestion: "Invalid API key, IP, or permission." },
170
- "-2017": { retry: false, suggestion: "API key expired. Generate a new one." }
170
+ "-2017": { retry: false, suggestion: "API key expired. Generate a new one." },
171
+ "-1130": { retry: false, suggestion: "Invalid symbol format. Futures endpoints require BTC-SWAP-USDT format; spot endpoints use BTCUSDT." },
172
+ "-1107": { retry: false, suggestion: "API key is missing or malformed. Check X-BB-APIKEY header." }
171
173
  };
172
174
  function isDefined(value) {
173
175
  return value !== void 0 && value !== null;
@@ -285,34 +287,42 @@ var ToobitRestClient = class {
285
287
  `${config.method} ${config.path}`
286
288
  );
287
289
  }
288
- if (!response.ok) {
289
- throw new ToobitApiError(
290
- `HTTP ${response.status} from Toobit: ${parsed.msg ?? "Unknown error"}`,
291
- { code: String(response.status), endpoint: `${config.method} ${config.path}` }
292
- );
293
- }
294
290
  const responseCode = parsed.code;
295
- if (responseCode !== void 0 && responseCode !== 0) {
296
- const message = parsed.msg || "Toobit API request failed.";
297
- const endpoint = `${config.method} ${config.path}`;
291
+ const responseMsg = parsed.msg || void 0;
292
+ const endpoint = `${config.method} ${config.path}`;
293
+ const hasBusinessCode = responseCode !== void 0 && responseCode !== 0 && responseCode !== 200;
294
+ if (hasBusinessCode) {
298
295
  const codeStr = String(responseCode);
299
- if (codeStr === "-1002" || codeStr === "-1022" || codeStr === "-2014" || codeStr === "-2015") {
296
+ const message = responseMsg || "Toobit API request failed.";
297
+ const behavior = TOOBIT_CODE_BEHAVIORS[codeStr];
298
+ if (codeStr === "-1002" || codeStr === "-1022" || codeStr === "-1107" || codeStr === "-2014" || codeStr === "-2015" || codeStr === "-2017") {
300
299
  throw new AuthenticationError(
301
300
  message,
302
- "Check API key, secret key and permissions.",
301
+ behavior?.suggestion ?? "Check API key, secret key and permissions.",
303
302
  endpoint
304
303
  );
305
304
  }
306
305
  if (codeStr === "-1003") {
307
306
  throw new RateLimitError(message, "Too many requests. Back off.", endpoint);
308
307
  }
309
- const behavior = TOOBIT_CODE_BEHAVIORS[codeStr];
310
308
  throw new ToobitApiError(message, {
311
309
  code: codeStr,
312
310
  endpoint,
313
311
  suggestion: behavior?.suggestion
314
312
  });
315
313
  }
314
+ if (!response.ok) {
315
+ const rawMsg = responseMsg ?? "Unknown error";
316
+ let suggestion;
317
+ if (/symbol|paramter|parameter/i.test(rawMsg)) {
318
+ const isFuturesPath = /futures|fundingRate|openInterest|markPrice|contract|longShort|insurance|riskLimit/i.test(config.path);
319
+ suggestion = isFuturesPath ? "Futures endpoints require contract symbol format, e.g. BTC-SWAP-USDT instead of BTCUSDT." : "Spot endpoints require symbol format like BTCUSDT.";
320
+ }
321
+ throw new ToobitApiError(
322
+ `HTTP ${response.status} from Toobit: ${rawMsg}`,
323
+ { code: String(response.status), endpoint, suggestion }
324
+ );
325
+ }
316
326
  return {
317
327
  endpoint: `${config.method} ${config.path}`,
318
328
  requestTime: (/* @__PURE__ */ new Date()).toISOString(),
@@ -459,24 +469,26 @@ function registerAccountTools() {
459
469
  inputSchema: {
460
470
  type: "object",
461
471
  properties: {
462
- fromAccountType: { type: "number", description: "1=coin, 2=contract" },
463
- toAccountType: { type: "number", description: "1=coin, 2=contract" },
464
- tokenId: { type: "string", description: "e.g. USDT" },
465
- amount: { type: "string" },
466
- subAccountId: { type: "string" }
472
+ fromUid: { type: "number", description: "From user ID" },
473
+ toUid: { type: "number", description: "To user ID" },
474
+ fromAccountType: { type: "string", enum: ["MAIN", "FUTURES", "COPY_TRADING"], description: "MAIN=spot, FUTURES=contract, COPY_TRADING=copy trading" },
475
+ toAccountType: { type: "string", enum: ["MAIN", "FUTURES", "COPY_TRADING"], description: "MAIN=spot, FUTURES=contract, COPY_TRADING=copy trading" },
476
+ asset: { type: "string", description: "Asset name, e.g. USDT" },
477
+ quantity: { type: "string", description: "Transfer quantity" }
467
478
  },
468
- required: ["tokenId", "amount"]
479
+ required: ["fromUid", "toUid", "fromAccountType", "toAccountType", "asset", "quantity"]
469
480
  },
470
481
  handler: async (rawArgs, context) => {
471
482
  const args = asRecord(rawArgs);
472
483
  const response = await context.client.privatePost(
473
484
  "/api/v1/subAccount/transfer",
474
485
  compactObject({
475
- fromAccountType: readNumber(args, "fromAccountType"),
476
- toAccountType: readNumber(args, "toAccountType"),
477
- tokenId: requireString(args, "tokenId"),
478
- amount: requireString(args, "amount"),
479
- subAccountId: readString(args, "subAccountId")
486
+ fromUid: readNumber(args, "fromUid"),
487
+ toUid: readNumber(args, "toUid"),
488
+ fromAccountType: requireString(args, "fromAccountType"),
489
+ toAccountType: requireString(args, "toAccountType"),
490
+ asset: requireString(args, "asset"),
491
+ quantity: requireString(args, "quantity")
480
492
  }),
481
493
  privateRateLimit("account_sub_transfer", 5)
482
494
  );
@@ -571,18 +583,18 @@ function registerAccountTools() {
571
583
  inputSchema: {
572
584
  type: "object",
573
585
  properties: {
574
- tokenId: { type: "string", description: "e.g. USDT" },
575
- chainType: { type: "string" }
586
+ coin: { type: "string", description: "Asset name, e.g. USDT" },
587
+ chainType: { type: "string", description: "Chain type, e.g. ERC20, TRC20, OMNI" }
576
588
  },
577
- required: ["tokenId"]
589
+ required: ["coin", "chainType"]
578
590
  },
579
591
  handler: async (rawArgs, context) => {
580
592
  const args = asRecord(rawArgs);
581
593
  const response = await context.client.privateGet(
582
594
  "/api/v1/account/deposit/address",
583
595
  compactObject({
584
- tokenId: requireString(args, "tokenId"),
585
- chainType: readString(args, "chainType")
596
+ coin: requireString(args, "coin"),
597
+ chainType: requireString(args, "chainType")
586
598
  }),
587
599
  privateRateLimit("account_get_deposit_address", 20)
588
600
  );
@@ -665,38 +677,45 @@ function registerFuturesTools() {
665
677
  {
666
678
  name: "futures_place_order",
667
679
  module: "futures",
668
- description: "Place a USDT-M futures order. [CAUTION] Executes real trades. Private endpoint. Rate limit: 20 req/s.",
680
+ description: "Place a USDT-M futures order. For market orders, set orderType to MARKET \u2014 the handler automatically converts to type=LIMIT + priceType=MARKET as required by Toobit API. [CAUTION] Executes real trades. Private endpoint. Rate limit: 20 req/s.",
669
681
  isWrite: true,
670
682
  inputSchema: {
671
683
  type: "object",
672
684
  properties: {
673
- symbol: { type: "string", description: "e.g. BTCUSDT" },
685
+ symbol: { type: "string", description: "Futures contract symbol, e.g. BTC-SWAP-USDT" },
674
686
  side: { type: "string", enum: ["BUY_OPEN", "SELL_OPEN", "BUY_CLOSE", "SELL_CLOSE"] },
675
- orderType: { type: "string", enum: ["LIMIT", "MARKET"], description: "Order type" },
687
+ orderType: { type: "string", enum: ["LIMIT", "MARKET", "STOP"], description: "LIMIT=limit order, MARKET=market order (auto-converted), STOP=conditional order" },
676
688
  quantity: { type: "string", description: "Order quantity (contracts)" },
677
- price: { type: "string", description: "Required for LIMIT" },
689
+ price: { type: "string", description: "Required for LIMIT orders with priceType=INPUT" },
678
690
  leverage: { type: "string", description: "Leverage, e.g. 10" },
679
- clientOrderId: { type: "string" },
680
- priceType: { type: "string", enum: ["INPUT", "OPPONENT", "QUEUE", "OVER", "MARKET"], description: "Price type for trigger orders" },
681
- triggerPrice: { type: "string", description: "Trigger price for conditional orders" },
682
- timeInForce: { type: "string", enum: ["GTC", "IOC", "FOK", "LIMIT_MAKER"] }
691
+ newClientOrderId: { type: "string", description: "Unique client order ID. Auto-generated if omitted." },
692
+ priceType: { type: "string", enum: ["INPUT", "OPPONENT", "QUEUE", "OVER", "MARKET"], description: "Price type. INPUT=specified price, MARKET=market price" },
693
+ stopPrice: { type: "string", description: "Trigger price for STOP orders" },
694
+ timeInForce: { type: "string", enum: ["GTC", "IOC", "FOK"] }
683
695
  },
684
696
  required: ["symbol", "side", "orderType", "quantity"]
685
697
  },
686
698
  handler: async (rawArgs, context) => {
687
699
  const args = asRecord(rawArgs);
700
+ let orderType = requireString(args, "orderType");
701
+ let priceType = readString(args, "priceType");
702
+ if (orderType === "MARKET") {
703
+ orderType = "LIMIT";
704
+ priceType = "MARKET";
705
+ }
706
+ const clientId = readString(args, "newClientOrderId") ?? `mcp_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
688
707
  const response = await context.client.privatePost(
689
708
  "/api/v1/futures/order",
690
709
  compactObject({
691
710
  symbol: requireString(args, "symbol"),
692
711
  side: requireString(args, "side"),
693
- orderType: requireString(args, "orderType"),
712
+ type: orderType,
694
713
  quantity: requireString(args, "quantity"),
695
714
  price: readString(args, "price"),
696
715
  leverage: readString(args, "leverage"),
697
- clientOrderId: readString(args, "clientOrderId"),
698
- priceType: readString(args, "priceType"),
699
- triggerPrice: readString(args, "triggerPrice"),
716
+ newClientOrderId: clientId,
717
+ priceType,
718
+ stopPrice: readString(args, "stopPrice"),
700
719
  timeInForce: readString(args, "timeInForce")
701
720
  }),
702
721
  privateRateLimit("futures_place_order", 20)
@@ -1027,7 +1046,7 @@ function registerFuturesTools() {
1027
1046
  type: "object",
1028
1047
  properties: {
1029
1048
  symbol: { type: "string", description: "e.g. BTCUSDT" },
1030
- marginType: { type: "string", enum: ["1", "2"], description: "1=cross, 2=isolated" }
1049
+ marginType: { type: "string", enum: ["CROSS", "ISOLATED"], description: "CROSS=cross margin, ISOLATED=isolated margin" }
1031
1050
  },
1032
1051
  required: ["symbol", "marginType"]
1033
1052
  },
@@ -1298,6 +1317,13 @@ function registerFuturesTools() {
1298
1317
  }
1299
1318
  ];
1300
1319
  }
1320
+ function readPositiveInt(args, key) {
1321
+ const value = readNumber(args, key);
1322
+ if (value !== void 0 && value < 1) {
1323
+ throw new ValidationError(`Parameter "${key}" must be a positive integer.`);
1324
+ }
1325
+ return value;
1326
+ }
1301
1327
  function normalize3(response) {
1302
1328
  return { endpoint: response.endpoint, requestTime: response.requestTime, data: response.data };
1303
1329
  }
@@ -1342,7 +1368,7 @@ function registerMarketTools() {
1342
1368
  const args = asRecord(rawArgs);
1343
1369
  const response = await context.client.publicGet(
1344
1370
  "/quote/v1/depth",
1345
- compactObject({ symbol: requireString(args, "symbol"), limit: readNumber(args, "limit") }),
1371
+ compactObject({ symbol: requireString(args, "symbol"), limit: readPositiveInt(args, "limit") }),
1346
1372
  publicRateLimit("market_get_depth", 20)
1347
1373
  );
1348
1374
  return normalize3(response);
@@ -1366,7 +1392,7 @@ function registerMarketTools() {
1366
1392
  const args = asRecord(rawArgs);
1367
1393
  const response = await context.client.publicGet(
1368
1394
  "/quote/v1/depth/merged",
1369
- compactObject({ symbol: requireString(args, "symbol"), scale: readNumber(args, "scale"), limit: readNumber(args, "limit") }),
1395
+ compactObject({ symbol: requireString(args, "symbol"), scale: readNumber(args, "scale"), limit: readPositiveInt(args, "limit") }),
1370
1396
  publicRateLimit("market_get_merged_depth", 20)
1371
1397
  );
1372
1398
  return normalize3(response);
@@ -1389,7 +1415,7 @@ function registerMarketTools() {
1389
1415
  const args = asRecord(rawArgs);
1390
1416
  const response = await context.client.publicGet(
1391
1417
  "/quote/v1/trades",
1392
- compactObject({ symbol: requireString(args, "symbol"), limit: readNumber(args, "limit") }),
1418
+ compactObject({ symbol: requireString(args, "symbol"), limit: readPositiveInt(args, "limit") }),
1393
1419
  publicRateLimit("market_get_trades", 20)
1394
1420
  );
1395
1421
  return normalize3(response);
@@ -1420,7 +1446,7 @@ function registerMarketTools() {
1420
1446
  interval: requireString(args, "interval"),
1421
1447
  startTime: readNumber(args, "startTime"),
1422
1448
  endTime: readNumber(args, "endTime"),
1423
- limit: readNumber(args, "limit")
1449
+ limit: readPositiveInt(args, "limit")
1424
1450
  }),
1425
1451
  publicRateLimit("market_get_klines", 20)
1426
1452
  );
@@ -1430,12 +1456,12 @@ function registerMarketTools() {
1430
1456
  {
1431
1457
  name: "market_get_ticker_24hr",
1432
1458
  module: "market",
1433
- description: "Get 24h price change statistics for a spot symbol. Public endpoint. Rate limit: 20 req/s.",
1459
+ description: "Get 24h price change statistics for a spot symbol. Omitting symbol returns ALL symbols (900+). Always specify a symbol unless you need the full list. Public endpoint. Rate limit: 20 req/s.",
1434
1460
  isWrite: false,
1435
1461
  inputSchema: {
1436
1462
  type: "object",
1437
1463
  properties: {
1438
- symbol: { type: "string", description: "e.g. BTCUSDT. Omit for all symbols." }
1464
+ symbol: { type: "string", description: "e.g. BTCUSDT. Omit for all symbols (warning: 900+ results)." }
1439
1465
  }
1440
1466
  },
1441
1467
  handler: async (rawArgs, context) => {
@@ -1451,12 +1477,12 @@ function registerMarketTools() {
1451
1477
  {
1452
1478
  name: "market_get_ticker_price",
1453
1479
  module: "market",
1454
- description: "Get latest price for a spot symbol. Public endpoint. Rate limit: 20 req/s.",
1480
+ description: "Get latest price for a spot symbol. Omitting symbol returns ALL symbols (900+), which may consume many tokens. Always specify a symbol unless you need the full list. Public endpoint. Rate limit: 20 req/s.",
1455
1481
  isWrite: false,
1456
1482
  inputSchema: {
1457
1483
  type: "object",
1458
1484
  properties: {
1459
- symbol: { type: "string", description: "e.g. BTCUSDT. Omit for all symbols." }
1485
+ symbol: { type: "string", description: "e.g. BTCUSDT. Omit for all symbols (warning: 900+ results)." }
1460
1486
  }
1461
1487
  },
1462
1488
  handler: async (rawArgs, context) => {
@@ -1472,12 +1498,12 @@ function registerMarketTools() {
1472
1498
  {
1473
1499
  name: "market_get_book_ticker",
1474
1500
  module: "market",
1475
- description: "Get best bid/ask price for a spot symbol. Public endpoint. Rate limit: 20 req/s.",
1501
+ description: "Get best bid/ask price for a spot symbol. Omitting symbol returns ALL symbols. Always specify a symbol unless you need the full list. Public endpoint. Rate limit: 20 req/s.",
1476
1502
  isWrite: false,
1477
1503
  inputSchema: {
1478
1504
  type: "object",
1479
1505
  properties: {
1480
- symbol: { type: "string", description: "e.g. BTCUSDT. Omit for all symbols." }
1506
+ symbol: { type: "string", description: "e.g. BTCUSDT. Omit for all symbols (warning: 900+ results)." }
1481
1507
  }
1482
1508
  },
1483
1509
  handler: async (rawArgs, context) => {
@@ -1515,7 +1541,7 @@ function registerMarketTools() {
1515
1541
  interval: requireString(args, "interval"),
1516
1542
  startTime: readNumber(args, "startTime"),
1517
1543
  endTime: readNumber(args, "endTime"),
1518
- limit: readNumber(args, "limit")
1544
+ limit: readPositiveInt(args, "limit")
1519
1545
  }),
1520
1546
  publicRateLimit("market_get_index_klines", 20)
1521
1547
  );
@@ -1525,12 +1551,12 @@ function registerMarketTools() {
1525
1551
  {
1526
1552
  name: "market_get_mark_price",
1527
1553
  module: "market",
1528
- description: "Get latest mark price for a futures symbol. Public endpoint. Rate limit: 20 req/s.",
1554
+ description: "Get latest mark price for a futures symbol. Requires contract symbol format. Public endpoint. Rate limit: 20 req/s.",
1529
1555
  isWrite: false,
1530
1556
  inputSchema: {
1531
1557
  type: "object",
1532
1558
  properties: {
1533
- symbol: { type: "string", description: "e.g. BTCUSDT. Omit for all." }
1559
+ symbol: { type: "string", description: "Futures contract symbol, e.g. BTC-SWAP-USDT. Omit for all." }
1534
1560
  }
1535
1561
  },
1536
1562
  handler: async (rawArgs, context) => {
@@ -1546,12 +1572,12 @@ function registerMarketTools() {
1546
1572
  {
1547
1573
  name: "market_get_mark_price_klines",
1548
1574
  module: "market",
1549
- description: "Get mark price K-line data. Public endpoint. Rate limit: 20 req/s.",
1575
+ description: "Get mark price K-line data. Requires contract symbol format. Public endpoint. Rate limit: 20 req/s.",
1550
1576
  isWrite: false,
1551
1577
  inputSchema: {
1552
1578
  type: "object",
1553
1579
  properties: {
1554
- symbol: { type: "string", description: "e.g. BTCUSDT" },
1580
+ symbol: { type: "string", description: "Futures contract symbol, e.g. BTC-SWAP-USDT" },
1555
1581
  interval: { type: "string", enum: [...TOOBIT_CANDLE_BARS] },
1556
1582
  startTime: { type: "number" },
1557
1583
  endTime: { type: "number" },
@@ -1568,7 +1594,7 @@ function registerMarketTools() {
1568
1594
  interval: requireString(args, "interval"),
1569
1595
  startTime: readNumber(args, "startTime"),
1570
1596
  endTime: readNumber(args, "endTime"),
1571
- limit: readNumber(args, "limit")
1597
+ limit: readPositiveInt(args, "limit")
1572
1598
  }),
1573
1599
  publicRateLimit("market_get_mark_price_klines", 20)
1574
1600
  );
@@ -1578,12 +1604,12 @@ function registerMarketTools() {
1578
1604
  {
1579
1605
  name: "market_get_funding_rate",
1580
1606
  module: "market",
1581
- description: "Get current funding rate for a futures symbol. Public endpoint. Rate limit: 20 req/s.",
1607
+ description: "Get current funding rate for a futures symbol. Requires contract symbol format. Public endpoint. Rate limit: 20 req/s.",
1582
1608
  isWrite: false,
1583
1609
  inputSchema: {
1584
1610
  type: "object",
1585
1611
  properties: {
1586
- symbol: { type: "string", description: "e.g. BTCUSDT. Omit for all." }
1612
+ symbol: { type: "string", description: "Futures contract symbol, e.g. BTC-SWAP-USDT. Omit for all." }
1587
1613
  }
1588
1614
  },
1589
1615
  handler: async (rawArgs, context) => {
@@ -1599,12 +1625,12 @@ function registerMarketTools() {
1599
1625
  {
1600
1626
  name: "market_get_funding_rate_history",
1601
1627
  module: "market",
1602
- description: "Get historical funding rates for a futures symbol. Public endpoint. Rate limit: 20 req/s.",
1628
+ description: "Get historical funding rates for a futures symbol. Requires contract symbol format. Public endpoint. Rate limit: 20 req/s.",
1603
1629
  isWrite: false,
1604
1630
  inputSchema: {
1605
1631
  type: "object",
1606
1632
  properties: {
1607
- symbol: { type: "string", description: "e.g. BTCUSDT" },
1633
+ symbol: { type: "string", description: "Futures contract symbol, e.g. BTC-SWAP-USDT" },
1608
1634
  startTime: { type: "number" },
1609
1635
  endTime: { type: "number" },
1610
1636
  limit: { type: "number", description: "Default 100, max 1000" }
@@ -1619,7 +1645,7 @@ function registerMarketTools() {
1619
1645
  symbol: requireString(args, "symbol"),
1620
1646
  startTime: readNumber(args, "startTime"),
1621
1647
  endTime: readNumber(args, "endTime"),
1622
- limit: readNumber(args, "limit")
1648
+ limit: readPositiveInt(args, "limit")
1623
1649
  }),
1624
1650
  publicRateLimit("market_get_funding_rate_history", 20)
1625
1651
  );
@@ -1629,12 +1655,12 @@ function registerMarketTools() {
1629
1655
  {
1630
1656
  name: "market_get_open_interest",
1631
1657
  module: "market",
1632
- description: "Get total open interest for a futures symbol. Public endpoint. Rate limit: 20 req/s.",
1658
+ description: "Get total open interest for a futures symbol. Requires contract symbol format. Public endpoint. Rate limit: 20 req/s.",
1633
1659
  isWrite: false,
1634
1660
  inputSchema: {
1635
1661
  type: "object",
1636
1662
  properties: {
1637
- symbol: { type: "string", description: "e.g. BTCUSDT. Omit for all." }
1663
+ symbol: { type: "string", description: "Futures contract symbol, e.g. BTC-SWAP-USDT. Omit for all." }
1638
1664
  }
1639
1665
  },
1640
1666
  handler: async (rawArgs, context) => {
@@ -1655,7 +1681,7 @@ function registerMarketTools() {
1655
1681
  inputSchema: {
1656
1682
  type: "object",
1657
1683
  properties: {
1658
- symbol: { type: "string", description: "e.g. BTCUSDT" },
1684
+ symbol: { type: "string", description: "Futures contract symbol, e.g. BTC-SWAP-USDT" },
1659
1685
  period: { type: "string", description: "e.g. 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d" },
1660
1686
  startTime: { type: "number" },
1661
1687
  endTime: { type: "number" },
@@ -1672,7 +1698,7 @@ function registerMarketTools() {
1672
1698
  period: requireString(args, "period"),
1673
1699
  startTime: readNumber(args, "startTime"),
1674
1700
  endTime: readNumber(args, "endTime"),
1675
- limit: readNumber(args, "limit")
1701
+ limit: readPositiveInt(args, "limit")
1676
1702
  }),
1677
1703
  publicRateLimit("market_get_long_short_ratio", 20)
1678
1704
  );
@@ -1682,12 +1708,12 @@ function registerMarketTools() {
1682
1708
  {
1683
1709
  name: "market_get_contract_ticker_24hr",
1684
1710
  module: "market",
1685
- description: "Get 24h price change statistics for a futures contract. Public endpoint. Rate limit: 20 req/s.",
1711
+ description: "Get 24h price change statistics for a futures contract. Requires contract symbol format. Public endpoint. Rate limit: 20 req/s.",
1686
1712
  isWrite: false,
1687
1713
  inputSchema: {
1688
1714
  type: "object",
1689
1715
  properties: {
1690
- symbol: { type: "string", description: "e.g. BTCUSDT. Omit for all." }
1716
+ symbol: { type: "string", description: "Futures contract symbol, e.g. BTC-SWAP-USDT. Omit for all." }
1691
1717
  }
1692
1718
  },
1693
1719
  handler: async (rawArgs, context) => {
@@ -1703,12 +1729,12 @@ function registerMarketTools() {
1703
1729
  {
1704
1730
  name: "market_get_contract_ticker_price",
1705
1731
  module: "market",
1706
- description: "Get latest price for a futures contract. Public endpoint. Rate limit: 20 req/s.",
1732
+ description: "Get latest price for a futures contract. Supports both spot (BTCUSDT) and contract (BTC-SWAP-USDT) symbol formats. Public endpoint. Rate limit: 20 req/s.",
1707
1733
  isWrite: false,
1708
1734
  inputSchema: {
1709
1735
  type: "object",
1710
1736
  properties: {
1711
- symbol: { type: "string", description: "e.g. BTCUSDT. Omit for all." }
1737
+ symbol: { type: "string", description: "e.g. BTC-SWAP-USDT or BTCUSDT. Omit for all." }
1712
1738
  }
1713
1739
  },
1714
1740
  handler: async (rawArgs, context) => {
@@ -1745,12 +1771,12 @@ function registerMarketTools() {
1745
1771
  {
1746
1772
  name: "market_get_insurance_fund",
1747
1773
  module: "market",
1748
- description: "Get insurance fund balance for a symbol. Public endpoint. Rate limit: 20 req/s.",
1774
+ description: "Get insurance fund balance for a futures symbol. Requires contract symbol format. Public endpoint. Rate limit: 20 req/s.",
1749
1775
  isWrite: false,
1750
1776
  inputSchema: {
1751
1777
  type: "object",
1752
1778
  properties: {
1753
- symbol: { type: "string", description: "e.g. BTCUSDT" }
1779
+ symbol: { type: "string", description: "Futures contract symbol, e.g. BTC-SWAP-USDT" }
1754
1780
  },
1755
1781
  required: ["symbol"]
1756
1782
  },
@@ -1767,12 +1793,12 @@ function registerMarketTools() {
1767
1793
  {
1768
1794
  name: "market_get_risk_limits",
1769
1795
  module: "market",
1770
- description: "Get risk limits configuration for a futures symbol. Public endpoint. Rate limit: 20 req/s.",
1796
+ description: "Get risk limits configuration for a futures symbol. Requires contract symbol format. Public endpoint. Rate limit: 20 req/s.",
1771
1797
  isWrite: false,
1772
1798
  inputSchema: {
1773
1799
  type: "object",
1774
1800
  properties: {
1775
- symbol: { type: "string", description: "e.g. BTCUSDT" }
1801
+ symbol: { type: "string", description: "Futures contract symbol, e.g. BTC-SWAP-USDT" }
1776
1802
  },
1777
1803
  required: ["symbol"]
1778
1804
  },
@@ -2078,12 +2104,15 @@ function allToolSpecs() {
2078
2104
  ];
2079
2105
  }
2080
2106
  function createToolRunner(client, config) {
2081
- const fullConfig = { ...config, modules: [...MODULES], readOnly: false };
2107
+ const fullConfig = { ...config, modules: [...MODULES] };
2082
2108
  const tools = allToolSpecs();
2083
2109
  const toolMap = new Map(tools.map((t) => [t.name, t]));
2084
2110
  return async (toolName, args) => {
2085
2111
  const tool = toolMap.get(toolName);
2086
2112
  if (!tool) throw new Error(`Unknown tool: ${toolName}`);
2113
+ if (config.readOnly && tool.isWrite) {
2114
+ throw new Error(`Tool "${toolName}" is a write operation and is blocked in read-only mode.`);
2115
+ }
2087
2116
  const result = await tool.handler(args, { config: fullConfig, client });
2088
2117
  return result;
2089
2118
  };
@@ -2264,11 +2293,58 @@ function parseCli(argv = process.argv.slice(2)) {
2264
2293
  }
2265
2294
 
2266
2295
  // src/formatter.ts
2296
+ function extractRows(value) {
2297
+ if (Array.isArray(value)) return value;
2298
+ if (typeof value === "object" && value !== null) {
2299
+ const entries = Object.values(value);
2300
+ for (const v of entries) {
2301
+ if (Array.isArray(v)) return v;
2302
+ }
2303
+ }
2304
+ return null;
2305
+ }
2267
2306
  function formatJson(data, json) {
2268
2307
  if (json) return JSON.stringify(data, null, 2);
2269
2308
  if (data === null || data === void 0) return "No data.";
2270
2309
  if (typeof data !== "object") return String(data);
2271
- return JSON.stringify(data, null, 2);
2310
+ const record = data;
2311
+ const inner = record.data ?? record;
2312
+ const rows = extractRows(inner);
2313
+ if (rows) {
2314
+ if (rows.length === 0) return "No data.";
2315
+ if (typeof rows[0] === "object" && rows[0] !== null) {
2316
+ return formatTable(rows);
2317
+ }
2318
+ return rows.map(String).join("\n");
2319
+ }
2320
+ if (typeof inner === "object" && inner !== null) {
2321
+ return formatKv(inner);
2322
+ }
2323
+ return String(inner);
2324
+ }
2325
+ function formatKv(data) {
2326
+ const entries = Object.entries(data).filter(([, v]) => v !== void 0 && v !== null);
2327
+ if (entries.length === 0) return "No data.";
2328
+ const maxKey = Math.max(...entries.map(([k]) => k.length));
2329
+ return entries.map(([k, v]) => {
2330
+ if (typeof v === "object") return `${k.padEnd(maxKey)} ${JSON.stringify(v)}`;
2331
+ return `${k.padEnd(maxKey)} ${String(v)}`;
2332
+ }).join("\n");
2333
+ }
2334
+ function formatTable(rows, columns) {
2335
+ if (rows.length === 0) return "No data.";
2336
+ const keys = columns ?? Object.keys(rows[0]);
2337
+ const widths = keys.map(
2338
+ (k) => Math.max(k.length, ...rows.map((r) => String(r[k] ?? "").length))
2339
+ );
2340
+ const header = keys.map((k, i) => k.padEnd(widths[i])).join(" ");
2341
+ const separator = widths.map((w) => "-".repeat(w)).join(" ");
2342
+ const body = rows.map(
2343
+ (row) => keys.map((k, i) => String(row[k] ?? "").padEnd(widths[i])).join(" ")
2344
+ ).join("\n");
2345
+ return `${header}
2346
+ ${separator}
2347
+ ${body}`;
2272
2348
  }
2273
2349
 
2274
2350
  // src/commands/market.ts
@@ -2331,9 +2407,18 @@ async function handleMarketCommand(cli, run) {
2331
2407
  case "long-short-ratio":
2332
2408
  result = await run("market_get_long_short_ratio", { symbol: f.symbol, period: f.period ?? "1h" });
2333
2409
  break;
2410
+ case "contract-ticker-price":
2411
+ result = await run("market_get_contract_ticker_price", { symbol: f.symbol });
2412
+ break;
2413
+ case "insurance-fund":
2414
+ result = await run("market_get_insurance_fund", { symbol: f.symbol });
2415
+ break;
2416
+ case "risk-limits":
2417
+ result = await run("market_get_risk_limits", { symbol: f.symbol });
2418
+ break;
2334
2419
  default:
2335
2420
  process.stdout.write(`Unknown market subcommand: ${cli.subcommand}
2336
- Available: time, info, ticker, ticker-24hr, depth, trades, klines, candles, book-ticker, mark-price, funding-rate, funding-rate-history, open-interest, index, contract-ticker, long-short-ratio
2421
+ Available: time, info, ticker, ticker-24hr, depth, trades, klines, candles, book-ticker, mark-price, funding-rate, funding-rate-history, open-interest, index, contract-ticker, contract-ticker-price, long-short-ratio, insurance-fund, risk-limits
2337
2422
  `);
2338
2423
  return;
2339
2424
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toobit-trade-cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "toobit": "dist/index.js"
@@ -62,8 +62,17 @@ export async function handleMarketCommand(cli: CliParsed, run: ToolRunner): Prom
62
62
  case "long-short-ratio":
63
63
  result = await run("market_get_long_short_ratio", { symbol: f.symbol, period: f.period ?? "1h" });
64
64
  break;
65
+ case "contract-ticker-price":
66
+ result = await run("market_get_contract_ticker_price", { symbol: f.symbol });
67
+ break;
68
+ case "insurance-fund":
69
+ result = await run("market_get_insurance_fund", { symbol: f.symbol });
70
+ break;
71
+ case "risk-limits":
72
+ result = await run("market_get_risk_limits", { symbol: f.symbol });
73
+ break;
65
74
  default:
66
- process.stdout.write(`Unknown market subcommand: ${cli.subcommand}\nAvailable: time, info, ticker, ticker-24hr, depth, trades, klines, candles, book-ticker, mark-price, funding-rate, funding-rate-history, open-interest, index, contract-ticker, long-short-ratio\n`);
75
+ process.stdout.write(`Unknown market subcommand: ${cli.subcommand}\nAvailable: time, info, ticker, ticker-24hr, depth, trades, klines, candles, book-ticker, mark-price, funding-rate, funding-rate-history, open-interest, index, contract-ticker, contract-ticker-price, long-short-ratio, insurance-fund, risk-limits\n`);
67
76
  return;
68
77
  }
69
78
 
package/src/formatter.ts CHANGED
@@ -1,8 +1,46 @@
1
+ function extractRows(value: unknown): unknown[] | null {
2
+ if (Array.isArray(value)) return value;
3
+ if (typeof value === "object" && value !== null) {
4
+ const entries = Object.values(value as Record<string, unknown>);
5
+ for (const v of entries) {
6
+ if (Array.isArray(v)) return v;
7
+ }
8
+ }
9
+ return null;
10
+ }
11
+
1
12
  export function formatJson(data: unknown, json: boolean): string {
2
13
  if (json) return JSON.stringify(data, null, 2);
3
14
  if (data === null || data === undefined) return "No data.";
4
15
  if (typeof data !== "object") return String(data);
5
- return JSON.stringify(data, null, 2);
16
+
17
+ const record = data as Record<string, unknown>;
18
+ const inner = record.data ?? record;
19
+
20
+ const rows = extractRows(inner);
21
+ if (rows) {
22
+ if (rows.length === 0) return "No data.";
23
+ if (typeof rows[0] === "object" && rows[0] !== null) {
24
+ return formatTable(rows as Record<string, unknown>[]);
25
+ }
26
+ return rows.map(String).join("\n");
27
+ }
28
+
29
+ if (typeof inner === "object" && inner !== null) {
30
+ return formatKv(inner as Record<string, unknown>);
31
+ }
32
+
33
+ return String(inner);
34
+ }
35
+
36
+ export function formatKv(data: Record<string, unknown>): string {
37
+ const entries = Object.entries(data).filter(([, v]) => v !== undefined && v !== null);
38
+ if (entries.length === 0) return "No data.";
39
+ const maxKey = Math.max(...entries.map(([k]) => k.length));
40
+ return entries.map(([k, v]) => {
41
+ if (typeof v === "object") return `${k.padEnd(maxKey)} ${JSON.stringify(v)}`;
42
+ return `${k.padEnd(maxKey)} ${String(v)}`;
43
+ }).join("\n");
6
44
  }
7
45
 
8
46
  export function formatTable(rows: Record<string, unknown>[], columns?: string[]): string {