toobit-trade-cli 1.0.0 → 1.0.1

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
@@ -1,13 +1,2186 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/index.ts
4
- import {
5
- loadConfig,
6
- ToobitRestClient,
7
- createToolRunner,
8
- toToolErrorPayload,
9
- configFilePath as configFilePath2
10
- } from "@toobit_agent/agent-toobitkit-core";
3
+ // ../core/dist/index.js
4
+ import { createHmac } from "crypto";
5
+ import * as fs from "fs";
6
+ import * as path from "path";
7
+ import * as os from "os";
8
+ import { readFileSync as readFileSync2, writeFileSync, mkdirSync, existsSync as existsSync2 } from "fs";
9
+ import { join as join2, dirname } from "path";
10
+ import { homedir as homedir2 } from "os";
11
+ import { parse, stringify } from "smol-toml";
12
+ function getTimestamp() {
13
+ return Date.now();
14
+ }
15
+ function signToobitPayload(queryString, secretKey) {
16
+ return createHmac("sha256", secretKey).update(queryString).digest("hex");
17
+ }
18
+ var ToobitMcpError = class extends Error {
19
+ type;
20
+ code;
21
+ suggestion;
22
+ endpoint;
23
+ constructor(type, message, options) {
24
+ super(message, options?.cause ? { cause: options.cause } : void 0);
25
+ this.name = type;
26
+ this.type = type;
27
+ this.code = options?.code;
28
+ this.suggestion = options?.suggestion;
29
+ this.endpoint = options?.endpoint;
30
+ }
31
+ };
32
+ var ConfigError = class extends ToobitMcpError {
33
+ constructor(message, suggestion) {
34
+ super("ConfigError", message, { suggestion });
35
+ }
36
+ };
37
+ var ValidationError = class extends ToobitMcpError {
38
+ constructor(message, suggestion) {
39
+ super("ValidationError", message, { suggestion });
40
+ }
41
+ };
42
+ var RateLimitError = class extends ToobitMcpError {
43
+ constructor(message, suggestion, endpoint) {
44
+ super("RateLimitError", message, { suggestion, endpoint });
45
+ }
46
+ };
47
+ var AuthenticationError = class extends ToobitMcpError {
48
+ constructor(message, suggestion, endpoint) {
49
+ super("AuthenticationError", message, { suggestion, endpoint });
50
+ }
51
+ };
52
+ var ToobitApiError = class extends ToobitMcpError {
53
+ constructor(message, options) {
54
+ super("ToobitApiError", message, options);
55
+ }
56
+ };
57
+ var NetworkError = class extends ToobitMcpError {
58
+ constructor(message, endpoint, cause) {
59
+ super("NetworkError", message, {
60
+ endpoint,
61
+ cause,
62
+ suggestion: "Please check network connectivity and retry the request in a few seconds."
63
+ });
64
+ }
65
+ };
66
+ function toToolErrorPayload(error, fallbackEndpoint) {
67
+ if (error instanceof ToobitMcpError) {
68
+ return {
69
+ error: true,
70
+ type: error.type,
71
+ code: error.code,
72
+ message: error.message,
73
+ suggestion: error.suggestion,
74
+ endpoint: error.endpoint ?? fallbackEndpoint,
75
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
76
+ };
77
+ }
78
+ const message = error instanceof Error ? error.message : String(error);
79
+ return {
80
+ error: true,
81
+ type: "InternalError",
82
+ message,
83
+ suggestion: "Unexpected server error. Check tool arguments and retry.",
84
+ endpoint: fallbackEndpoint,
85
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
86
+ };
87
+ }
88
+ function sleep(ms) {
89
+ return new Promise((resolve) => {
90
+ setTimeout(resolve, ms);
91
+ });
92
+ }
93
+ var RateLimiter = class {
94
+ buckets = /* @__PURE__ */ new Map();
95
+ maxWaitMs;
96
+ constructor(maxWaitMs = 3e4) {
97
+ this.maxWaitMs = maxWaitMs;
98
+ }
99
+ async consume(config, amount = 1) {
100
+ const bucket = this.getBucket(config);
101
+ this.refill(bucket);
102
+ if (bucket.tokens >= amount) {
103
+ bucket.tokens -= amount;
104
+ return;
105
+ }
106
+ const missing = amount - bucket.tokens;
107
+ const secondsToWait = missing / bucket.refillPerSecond;
108
+ const waitMs = Math.ceil(secondsToWait * 1e3);
109
+ if (waitMs > this.maxWaitMs) {
110
+ throw new RateLimitError(
111
+ `Client-side rate limit reached for ${config.key}. Required wait ${waitMs}ms exceeds allowed max ${this.maxWaitMs}ms.`,
112
+ "Reduce tool call frequency or retry later."
113
+ );
114
+ }
115
+ await sleep(waitMs);
116
+ this.refill(bucket);
117
+ if (bucket.tokens < amount) {
118
+ throw new RateLimitError(
119
+ `Rate limiter failed to acquire enough tokens for ${config.key}.`
120
+ );
121
+ }
122
+ bucket.tokens -= amount;
123
+ }
124
+ getBucket(config) {
125
+ const existing = this.buckets.get(config.key);
126
+ if (existing) {
127
+ if (existing.capacity !== config.capacity || existing.refillPerSecond !== config.refillPerSecond) {
128
+ existing.capacity = config.capacity;
129
+ existing.refillPerSecond = config.refillPerSecond;
130
+ existing.tokens = Math.min(existing.tokens, config.capacity);
131
+ }
132
+ return existing;
133
+ }
134
+ const now = Date.now();
135
+ const created = {
136
+ tokens: config.capacity,
137
+ lastRefillMs: now,
138
+ capacity: config.capacity,
139
+ refillPerSecond: config.refillPerSecond
140
+ };
141
+ this.buckets.set(config.key, created);
142
+ return created;
143
+ }
144
+ refill(bucket) {
145
+ const now = Date.now();
146
+ const elapsedMs = now - bucket.lastRefillMs;
147
+ if (elapsedMs <= 0) return;
148
+ const refillTokens = elapsedMs / 1e3 * bucket.refillPerSecond;
149
+ bucket.tokens = Math.min(bucket.capacity, bucket.tokens + refillTokens);
150
+ bucket.lastRefillMs = now;
151
+ }
152
+ };
153
+ var TOOBIT_CODE_BEHAVIORS = {
154
+ "-1000": { retry: true, suggestion: "Unknown error. Retry after a delay." },
155
+ "-1001": { retry: true, suggestion: "Disconnected / internal error. Retry." },
156
+ "-1003": { retry: true, suggestion: "Too many requests. Back off and retry." },
157
+ "-1006": { retry: true, suggestion: "Unexpected response. Retry later." },
158
+ "-1007": { retry: true, suggestion: "Timeout. Retry after a delay." },
159
+ "-1016": { retry: true, suggestion: "Service shutting down. Retry later." },
160
+ "-1002": { retry: false, suggestion: "Unauthorized. Check API key permissions." },
161
+ "-1015": { retry: false, suggestion: "Too many orders. Reduce order frequency." },
162
+ "-1020": { retry: false, suggestion: "Unsupported operation." },
163
+ "-1021": { retry: false, suggestion: "Invalid timestamp. Check system clock sync." },
164
+ "-1022": { retry: false, suggestion: "Invalid signature. Check API key and secret." },
165
+ "-2010": { retry: false, suggestion: "New order rejected. Check order parameters." },
166
+ "-2011": { retry: false, suggestion: "Cancel rejected. Order may already be filled." },
167
+ "-2013": { retry: false, suggestion: "Order does not exist." },
168
+ "-2014": { retry: false, suggestion: "Bad API key format." },
169
+ "-2015": { retry: false, suggestion: "Invalid API key, IP, or permission." },
170
+ "-2017": { retry: false, suggestion: "API key expired. Generate a new one." }
171
+ };
172
+ function isDefined(value) {
173
+ return value !== void 0 && value !== null;
174
+ }
175
+ function stringifyQueryValue(value) {
176
+ if (Array.isArray(value)) return value.map(String).join(",");
177
+ return String(value);
178
+ }
179
+ function buildQueryString(query) {
180
+ if (!query) return "";
181
+ const entries = Object.entries(query).filter(([, v]) => isDefined(v));
182
+ if (entries.length === 0) return "";
183
+ return entries.map(([k, v]) => `${k}=${stringifyQueryValue(v)}`).join("&");
184
+ }
185
+ var ToobitRestClient = class {
186
+ config;
187
+ rateLimiter = new RateLimiter();
188
+ constructor(config) {
189
+ this.config = config;
190
+ }
191
+ async publicGet(path4, query, rateLimit) {
192
+ return this.request({ method: "GET", path: path4, auth: "public", query, rateLimit });
193
+ }
194
+ async privateGet(path4, query, rateLimit) {
195
+ return this.request({ method: "GET", path: path4, auth: "private", query, rateLimit });
196
+ }
197
+ async privatePost(path4, body, rateLimit) {
198
+ return this.request({ method: "POST", path: path4, auth: "private", body, rateLimit });
199
+ }
200
+ async privateDelete(path4, query, rateLimit) {
201
+ return this.request({ method: "DELETE", path: path4, auth: "private", query, rateLimit });
202
+ }
203
+ async request(config) {
204
+ if (config.rateLimit) {
205
+ await this.rateLimiter.consume(config.rateLimit);
206
+ }
207
+ const timestamp = getTimestamp();
208
+ let allParams = { ...config.query ?? {} };
209
+ const isBodyMethod = config.method === "POST" || config.method === "PUT";
210
+ if (config.body) {
211
+ Object.assign(allParams, config.body);
212
+ }
213
+ if (config.auth === "private") {
214
+ if (!this.config.hasAuth) {
215
+ throw new ConfigError(
216
+ "Private endpoint requires API credentials.",
217
+ "Configure TOOBIT_API_KEY and TOOBIT_SECRET_KEY."
218
+ );
219
+ }
220
+ allParams.timestamp = String(timestamp);
221
+ const signPayload = buildQueryString(allParams);
222
+ const signature = signToobitPayload(signPayload, this.config.secretKey);
223
+ allParams.signature = signature;
224
+ }
225
+ const paramString = buildQueryString(allParams);
226
+ let url;
227
+ let fetchBody;
228
+ if (isBodyMethod && config.auth === "private") {
229
+ url = `${this.config.baseUrl}${config.path}`;
230
+ fetchBody = paramString || void 0;
231
+ } else {
232
+ const requestPath = paramString ? `${config.path}?${paramString}` : config.path;
233
+ url = `${this.config.baseUrl}${requestPath}`;
234
+ fetchBody = void 0;
235
+ }
236
+ const headers = new Headers({
237
+ Accept: "application/json"
238
+ });
239
+ if (isBodyMethod) {
240
+ headers.set("Content-Type", "application/x-www-form-urlencoded");
241
+ }
242
+ if (this.config.userAgent) {
243
+ headers.set("User-Agent", this.config.userAgent);
244
+ }
245
+ if (config.auth === "private" && this.config.apiKey) {
246
+ headers.set("X-BB-APIKEY", this.config.apiKey);
247
+ }
248
+ let response;
249
+ try {
250
+ response = await fetch(url, {
251
+ method: config.method,
252
+ headers,
253
+ body: fetchBody,
254
+ signal: AbortSignal.timeout(this.config.timeoutMs)
255
+ });
256
+ } catch (error) {
257
+ throw new NetworkError(
258
+ `Failed to call Toobit endpoint ${config.method} ${config.path}.`,
259
+ `${config.method} ${config.path}`,
260
+ error
261
+ );
262
+ }
263
+ const rawText = await response.text();
264
+ let parsed;
265
+ try {
266
+ parsed = rawText ? JSON.parse(rawText) : {};
267
+ } catch (error) {
268
+ if (!response.ok) {
269
+ const preview = rawText.slice(0, 160).replace(/\s+/g, " ").trim();
270
+ throw new ToobitApiError(
271
+ `HTTP ${response.status} from Toobit: ${preview || "Non-JSON response"}`,
272
+ { code: String(response.status), endpoint: `${config.method} ${config.path}` }
273
+ );
274
+ }
275
+ throw new NetworkError(
276
+ `Toobit returned non-JSON response for ${config.method} ${config.path}.`,
277
+ `${config.method} ${config.path}`,
278
+ error
279
+ );
280
+ }
281
+ if (response.status === 429) {
282
+ throw new RateLimitError(
283
+ "Rate limited by Toobit. Back off and retry.",
284
+ "Reduce request frequency.",
285
+ `${config.method} ${config.path}`
286
+ );
287
+ }
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
+ 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}`;
298
+ const codeStr = String(responseCode);
299
+ if (codeStr === "-1002" || codeStr === "-1022" || codeStr === "-2014" || codeStr === "-2015") {
300
+ throw new AuthenticationError(
301
+ message,
302
+ "Check API key, secret key and permissions.",
303
+ endpoint
304
+ );
305
+ }
306
+ if (codeStr === "-1003") {
307
+ throw new RateLimitError(message, "Too many requests. Back off.", endpoint);
308
+ }
309
+ const behavior = TOOBIT_CODE_BEHAVIORS[codeStr];
310
+ throw new ToobitApiError(message, {
311
+ code: codeStr,
312
+ endpoint,
313
+ suggestion: behavior?.suggestion
314
+ });
315
+ }
316
+ return {
317
+ endpoint: `${config.method} ${config.path}`,
318
+ requestTime: (/* @__PURE__ */ new Date()).toISOString(),
319
+ data: parsed,
320
+ raw: parsed
321
+ };
322
+ }
323
+ };
324
+ var TOOBIT_API_BASE_URL = "https://api.toobit.com";
325
+ var DEFAULT_SOURCE_TAG = "MCP";
326
+ var MODULES = [
327
+ "market",
328
+ "spot",
329
+ "futures",
330
+ "account"
331
+ ];
332
+ var DEFAULT_MODULES = ["spot", "futures", "account"];
333
+ function asRecord(value) {
334
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
335
+ return value;
336
+ }
337
+ function readString(args, key) {
338
+ const value = args[key];
339
+ if (value === void 0 || value === null) return void 0;
340
+ if (typeof value !== "string") throw new ValidationError(`Parameter "${key}" must be a string.`);
341
+ return value;
342
+ }
343
+ function readNumber(args, key) {
344
+ const value = args[key];
345
+ if (value === void 0 || value === null) return void 0;
346
+ if (typeof value !== "number" || Number.isNaN(value)) throw new ValidationError(`Parameter "${key}" must be a number.`);
347
+ return value;
348
+ }
349
+ function requireString(args, key) {
350
+ const value = readString(args, key);
351
+ if (!value || value.length === 0) throw new ValidationError(`Missing required parameter "${key}".`);
352
+ return value;
353
+ }
354
+ function compactObject(object) {
355
+ const next = {};
356
+ for (const [key, value] of Object.entries(object)) {
357
+ if (value !== void 0 && value !== null) next[key] = value;
358
+ }
359
+ return next;
360
+ }
361
+ var TOOBIT_CANDLE_BARS = [
362
+ "1m",
363
+ "3m",
364
+ "5m",
365
+ "15m",
366
+ "30m",
367
+ "1h",
368
+ "2h",
369
+ "4h",
370
+ "6h",
371
+ "8h",
372
+ "12h",
373
+ "1d",
374
+ "3d",
375
+ "1w",
376
+ "1M"
377
+ ];
378
+ function publicRateLimit(key, rps = 20) {
379
+ return { key: `public:${key}`, capacity: rps, refillPerSecond: rps };
380
+ }
381
+ function privateRateLimit(key, rps = 10) {
382
+ return { key: `private:${key}`, capacity: rps, refillPerSecond: rps };
383
+ }
384
+ function normalize(response) {
385
+ return { endpoint: response.endpoint, requestTime: response.requestTime, data: response.data };
386
+ }
387
+ function registerAccountTools() {
388
+ return [
389
+ {
390
+ name: "account_get_info",
391
+ module: "account",
392
+ description: "Get spot account information (balances for all assets). Private endpoint. Rate limit: 20 req/s.",
393
+ isWrite: false,
394
+ inputSchema: { type: "object", properties: {} },
395
+ handler: async (_rawArgs, context) => {
396
+ const response = await context.client.privateGet(
397
+ "/api/v1/account",
398
+ {},
399
+ privateRateLimit("account_get_info", 20)
400
+ );
401
+ return normalize(response);
402
+ }
403
+ },
404
+ {
405
+ name: "account_get_balance_flow",
406
+ module: "account",
407
+ description: "Get account balance flow (ledger). Private endpoint. Rate limit: 20 req/s.",
408
+ isWrite: false,
409
+ inputSchema: {
410
+ type: "object",
411
+ properties: {
412
+ accountType: { type: "number", description: "1=coin, 2=contract" },
413
+ tokenId: { type: "string" },
414
+ fromFlowId: { type: "string" },
415
+ endFlowId: { type: "string" },
416
+ startTime: { type: "number" },
417
+ endTime: { type: "number" },
418
+ limit: { type: "number" }
419
+ }
420
+ },
421
+ handler: async (rawArgs, context) => {
422
+ const args = asRecord(rawArgs);
423
+ const response = await context.client.privateGet(
424
+ "/api/v1/account/balanceFlow",
425
+ compactObject({
426
+ accountType: readNumber(args, "accountType"),
427
+ tokenId: readString(args, "tokenId"),
428
+ fromFlowId: readString(args, "fromFlowId"),
429
+ endFlowId: readString(args, "endFlowId"),
430
+ startTime: readNumber(args, "startTime"),
431
+ endTime: readNumber(args, "endTime"),
432
+ limit: readNumber(args, "limit")
433
+ }),
434
+ privateRateLimit("account_get_balance_flow", 20)
435
+ );
436
+ return normalize(response);
437
+ }
438
+ },
439
+ {
440
+ name: "account_get_sub_accounts",
441
+ module: "account",
442
+ description: "Get sub-account list. Private endpoint. Rate limit: 10 req/s.",
443
+ isWrite: false,
444
+ inputSchema: { type: "object", properties: {} },
445
+ handler: async (_rawArgs, context) => {
446
+ const response = await context.client.privateGet(
447
+ "/api/v1/account/subAccount",
448
+ {},
449
+ privateRateLimit("account_get_sub_accounts", 10)
450
+ );
451
+ return normalize(response);
452
+ }
453
+ },
454
+ {
455
+ name: "account_sub_transfer",
456
+ module: "account",
457
+ description: "Transfer funds between main and sub accounts. [CAUTION] Private endpoint. Rate limit: 5 req/s.",
458
+ isWrite: true,
459
+ inputSchema: {
460
+ type: "object",
461
+ 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" }
467
+ },
468
+ required: ["tokenId", "amount"]
469
+ },
470
+ handler: async (rawArgs, context) => {
471
+ const args = asRecord(rawArgs);
472
+ const response = await context.client.privatePost(
473
+ "/api/v1/subAccount/transfer",
474
+ 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")
480
+ }),
481
+ privateRateLimit("account_sub_transfer", 5)
482
+ );
483
+ return normalize(response);
484
+ }
485
+ },
486
+ {
487
+ name: "account_check_api_key",
488
+ module: "account",
489
+ description: "Check API key type and permissions. Private endpoint. Rate limit: 20 req/s.",
490
+ isWrite: false,
491
+ inputSchema: { type: "object", properties: {} },
492
+ handler: async (_rawArgs, context) => {
493
+ const response = await context.client.privateGet(
494
+ "/api/v1/account/checkApiKey",
495
+ {},
496
+ privateRateLimit("account_check_api_key", 20)
497
+ );
498
+ return normalize(response);
499
+ }
500
+ },
501
+ {
502
+ name: "account_withdraw",
503
+ module: "account",
504
+ description: "Submit a withdrawal request. [CAUTION] Moves real funds. Private endpoint. Rate limit: 5 req/s.",
505
+ isWrite: true,
506
+ inputSchema: {
507
+ type: "object",
508
+ properties: {
509
+ tokenId: { type: "string", description: "e.g. USDT" },
510
+ address: { type: "string" },
511
+ addressExt: { type: "string", description: "Memo/tag if required" },
512
+ chainType: { type: "string" },
513
+ withdrawQuantity: { type: "string" },
514
+ clientOrderId: { type: "string" }
515
+ },
516
+ required: ["tokenId", "address", "chainType", "withdrawQuantity"]
517
+ },
518
+ handler: async (rawArgs, context) => {
519
+ const args = asRecord(rawArgs);
520
+ const response = await context.client.privatePost(
521
+ "/api/v1/account/withdraw",
522
+ compactObject({
523
+ tokenId: requireString(args, "tokenId"),
524
+ address: requireString(args, "address"),
525
+ addressExt: readString(args, "addressExt"),
526
+ chainType: requireString(args, "chainType"),
527
+ withdrawQuantity: requireString(args, "withdrawQuantity"),
528
+ clientOrderId: readString(args, "clientOrderId")
529
+ }),
530
+ privateRateLimit("account_withdraw", 5)
531
+ );
532
+ return normalize(response);
533
+ }
534
+ },
535
+ {
536
+ name: "account_get_withdraw_orders",
537
+ module: "account",
538
+ description: "Get withdrawal records. Private endpoint. Rate limit: 20 req/s.",
539
+ isWrite: false,
540
+ inputSchema: {
541
+ type: "object",
542
+ properties: {
543
+ tokenId: { type: "string" },
544
+ startTime: { type: "number" },
545
+ endTime: { type: "number" },
546
+ fromId: { type: "string" },
547
+ limit: { type: "number" }
548
+ }
549
+ },
550
+ handler: async (rawArgs, context) => {
551
+ const args = asRecord(rawArgs);
552
+ const response = await context.client.privateGet(
553
+ "/api/v1/account/withdrawOrders",
554
+ compactObject({
555
+ tokenId: readString(args, "tokenId"),
556
+ startTime: readNumber(args, "startTime"),
557
+ endTime: readNumber(args, "endTime"),
558
+ fromId: readString(args, "fromId"),
559
+ limit: readNumber(args, "limit")
560
+ }),
561
+ privateRateLimit("account_get_withdraw_orders", 20)
562
+ );
563
+ return normalize(response);
564
+ }
565
+ },
566
+ {
567
+ name: "account_get_deposit_address",
568
+ module: "account",
569
+ description: "Get deposit address for a token. Private endpoint. Rate limit: 20 req/s.",
570
+ isWrite: false,
571
+ inputSchema: {
572
+ type: "object",
573
+ properties: {
574
+ tokenId: { type: "string", description: "e.g. USDT" },
575
+ chainType: { type: "string" }
576
+ },
577
+ required: ["tokenId"]
578
+ },
579
+ handler: async (rawArgs, context) => {
580
+ const args = asRecord(rawArgs);
581
+ const response = await context.client.privateGet(
582
+ "/api/v1/account/deposit/address",
583
+ compactObject({
584
+ tokenId: requireString(args, "tokenId"),
585
+ chainType: readString(args, "chainType")
586
+ }),
587
+ privateRateLimit("account_get_deposit_address", 20)
588
+ );
589
+ return normalize(response);
590
+ }
591
+ },
592
+ {
593
+ name: "account_get_deposit_orders",
594
+ module: "account",
595
+ description: "Get deposit records. Private endpoint. Rate limit: 20 req/s.",
596
+ isWrite: false,
597
+ inputSchema: {
598
+ type: "object",
599
+ properties: {
600
+ tokenId: { type: "string" },
601
+ startTime: { type: "number" },
602
+ endTime: { type: "number" },
603
+ fromId: { type: "string" },
604
+ limit: { type: "number" }
605
+ }
606
+ },
607
+ handler: async (rawArgs, context) => {
608
+ const args = asRecord(rawArgs);
609
+ const response = await context.client.privateGet(
610
+ "/api/v1/account/depositOrders",
611
+ compactObject({
612
+ tokenId: readString(args, "tokenId"),
613
+ startTime: readNumber(args, "startTime"),
614
+ endTime: readNumber(args, "endTime"),
615
+ fromId: readString(args, "fromId"),
616
+ limit: readNumber(args, "limit")
617
+ }),
618
+ privateRateLimit("account_get_deposit_orders", 20)
619
+ );
620
+ return normalize(response);
621
+ }
622
+ }
623
+ ];
624
+ }
625
+ function registerAuditTools() {
626
+ return [
627
+ {
628
+ name: "trade_get_history",
629
+ module: "account",
630
+ description: "Read local audit log entries from ~/.toobit/logs/. Returns recent tool call records. No API call.",
631
+ isWrite: false,
632
+ inputSchema: {
633
+ type: "object",
634
+ properties: {
635
+ date: { type: "string", description: "Date in YYYY-MM-DD format. Defaults to today." },
636
+ limit: { type: "number", description: "Max entries to return (default 50)" }
637
+ }
638
+ },
639
+ handler: async (rawArgs) => {
640
+ const args = asRecord(rawArgs);
641
+ const dateStr = readString(args, "date") ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
642
+ const limit = readNumber(args, "limit") ?? 50;
643
+ const logPath = path.join(os.homedir(), ".toobit", "logs", `trade-${dateStr}.log`);
644
+ if (!fs.existsSync(logPath)) {
645
+ return { endpoint: "local", requestTime: (/* @__PURE__ */ new Date()).toISOString(), data: [] };
646
+ }
647
+ const lines = fs.readFileSync(logPath, "utf-8").trim().split("\n").filter(Boolean);
648
+ const entries = lines.slice(-limit).map((line) => {
649
+ try {
650
+ return JSON.parse(line);
651
+ } catch {
652
+ return { raw: line };
653
+ }
654
+ });
655
+ return { endpoint: "local", requestTime: (/* @__PURE__ */ new Date()).toISOString(), data: entries };
656
+ }
657
+ }
658
+ ];
659
+ }
660
+ function normalize2(response) {
661
+ return { endpoint: response.endpoint, requestTime: response.requestTime, data: response.data };
662
+ }
663
+ function registerFuturesTools() {
664
+ return [
665
+ {
666
+ name: "futures_place_order",
667
+ module: "futures",
668
+ description: "Place a USDT-M futures order. [CAUTION] Executes real trades. Private endpoint. Rate limit: 20 req/s.",
669
+ isWrite: true,
670
+ inputSchema: {
671
+ type: "object",
672
+ properties: {
673
+ symbol: { type: "string", description: "e.g. BTCUSDT" },
674
+ side: { type: "string", enum: ["BUY_OPEN", "SELL_OPEN", "BUY_CLOSE", "SELL_CLOSE"] },
675
+ orderType: { type: "string", enum: ["LIMIT", "MARKET"], description: "Order type" },
676
+ quantity: { type: "string", description: "Order quantity (contracts)" },
677
+ price: { type: "string", description: "Required for LIMIT" },
678
+ 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"] }
683
+ },
684
+ required: ["symbol", "side", "orderType", "quantity"]
685
+ },
686
+ handler: async (rawArgs, context) => {
687
+ const args = asRecord(rawArgs);
688
+ const response = await context.client.privatePost(
689
+ "/api/v1/futures/order",
690
+ compactObject({
691
+ symbol: requireString(args, "symbol"),
692
+ side: requireString(args, "side"),
693
+ orderType: requireString(args, "orderType"),
694
+ quantity: requireString(args, "quantity"),
695
+ price: readString(args, "price"),
696
+ leverage: readString(args, "leverage"),
697
+ clientOrderId: readString(args, "clientOrderId"),
698
+ priceType: readString(args, "priceType"),
699
+ triggerPrice: readString(args, "triggerPrice"),
700
+ timeInForce: readString(args, "timeInForce")
701
+ }),
702
+ privateRateLimit("futures_place_order", 20)
703
+ );
704
+ return normalize2(response);
705
+ }
706
+ },
707
+ {
708
+ name: "futures_batch_orders",
709
+ module: "futures",
710
+ description: "[CAUTION] Batch place futures orders. Private endpoint. Rate limit: 20 req/s.",
711
+ isWrite: true,
712
+ inputSchema: {
713
+ type: "object",
714
+ properties: {
715
+ orders: {
716
+ type: "array",
717
+ description: "Array of order objects",
718
+ items: { type: "object" }
719
+ }
720
+ },
721
+ required: ["orders"]
722
+ },
723
+ handler: async (rawArgs, context) => {
724
+ const args = asRecord(rawArgs);
725
+ const orders = args.orders;
726
+ if (!Array.isArray(orders) || orders.length === 0) throw new Error("orders must be a non-empty array.");
727
+ const response = await context.client.privatePost(
728
+ "/api/v1/futures/batchOrders",
729
+ orders,
730
+ privateRateLimit("futures_batch_orders", 20)
731
+ );
732
+ return normalize2(response);
733
+ }
734
+ },
735
+ {
736
+ name: "futures_cancel_order",
737
+ module: "futures",
738
+ description: "Cancel a futures order. Private endpoint. Rate limit: 20 req/s.",
739
+ isWrite: true,
740
+ inputSchema: {
741
+ type: "object",
742
+ properties: {
743
+ orderId: { type: "string" },
744
+ clientOrderId: { type: "string" },
745
+ orderType: { type: "string", description: "LIMIT or condition type" }
746
+ }
747
+ },
748
+ handler: async (rawArgs, context) => {
749
+ const args = asRecord(rawArgs);
750
+ const response = await context.client.privateDelete(
751
+ "/api/v1/futures/order",
752
+ compactObject({
753
+ orderId: readString(args, "orderId"),
754
+ clientOrderId: readString(args, "clientOrderId"),
755
+ orderType: readString(args, "orderType")
756
+ }),
757
+ privateRateLimit("futures_cancel_order", 20)
758
+ );
759
+ return normalize2(response);
760
+ }
761
+ },
762
+ {
763
+ name: "futures_cancel_all_orders",
764
+ module: "futures",
765
+ description: "Cancel all futures open orders for a symbol. [CAUTION] Private endpoint. Rate limit: 10 req/s.",
766
+ isWrite: true,
767
+ inputSchema: {
768
+ type: "object",
769
+ properties: {
770
+ symbol: { type: "string", description: "e.g. BTCUSDT" }
771
+ },
772
+ required: ["symbol"]
773
+ },
774
+ handler: async (rawArgs, context) => {
775
+ const args = asRecord(rawArgs);
776
+ const response = await context.client.privateDelete(
777
+ "/api/v1/futures/batchOrders",
778
+ { symbol: requireString(args, "symbol") },
779
+ privateRateLimit("futures_cancel_all_orders", 10)
780
+ );
781
+ return normalize2(response);
782
+ }
783
+ },
784
+ {
785
+ name: "futures_cancel_order_by_ids",
786
+ module: "futures",
787
+ description: "Batch cancel futures orders by IDs. Private endpoint. Rate limit: 20 req/s.",
788
+ isWrite: true,
789
+ inputSchema: {
790
+ type: "object",
791
+ properties: {
792
+ orderIds: { type: "string", description: "Comma-separated order IDs" }
793
+ },
794
+ required: ["orderIds"]
795
+ },
796
+ handler: async (rawArgs, context) => {
797
+ const args = asRecord(rawArgs);
798
+ const response = await context.client.privateDelete(
799
+ "/api/v1/futures/cancelOrderByIds",
800
+ { orderIds: requireString(args, "orderIds") },
801
+ privateRateLimit("futures_cancel_order_by_ids", 20)
802
+ );
803
+ return normalize2(response);
804
+ }
805
+ },
806
+ {
807
+ name: "futures_amend_order",
808
+ module: "futures",
809
+ description: "Modify a futures order (price/quantity). Private endpoint. Rate limit: 20 req/s.",
810
+ isWrite: true,
811
+ inputSchema: {
812
+ type: "object",
813
+ properties: {
814
+ orderId: { type: "string" },
815
+ quantity: { type: "string" },
816
+ price: { type: "string" }
817
+ },
818
+ required: ["orderId"]
819
+ },
820
+ handler: async (rawArgs, context) => {
821
+ const args = asRecord(rawArgs);
822
+ const response = await context.client.privatePost(
823
+ "/api/v1/futures/order/update",
824
+ compactObject({
825
+ orderId: requireString(args, "orderId"),
826
+ quantity: readString(args, "quantity"),
827
+ price: readString(args, "price")
828
+ }),
829
+ privateRateLimit("futures_amend_order", 20)
830
+ );
831
+ return normalize2(response);
832
+ }
833
+ },
834
+ {
835
+ name: "futures_get_order",
836
+ module: "futures",
837
+ description: "Get details of a single futures order. Private endpoint. Rate limit: 20 req/s.",
838
+ isWrite: false,
839
+ inputSchema: {
840
+ type: "object",
841
+ properties: {
842
+ orderId: { type: "string" },
843
+ clientOrderId: { type: "string" },
844
+ orderType: { type: "string" }
845
+ }
846
+ },
847
+ handler: async (rawArgs, context) => {
848
+ const args = asRecord(rawArgs);
849
+ const response = await context.client.privateGet(
850
+ "/api/v1/futures/order",
851
+ compactObject({
852
+ orderId: readString(args, "orderId"),
853
+ clientOrderId: readString(args, "clientOrderId"),
854
+ orderType: readString(args, "orderType")
855
+ }),
856
+ privateRateLimit("futures_get_order", 20)
857
+ );
858
+ return normalize2(response);
859
+ }
860
+ },
861
+ {
862
+ name: "futures_get_open_orders",
863
+ module: "futures",
864
+ description: "Get current open futures orders. Private endpoint. Rate limit: 20 req/s.",
865
+ isWrite: false,
866
+ inputSchema: {
867
+ type: "object",
868
+ properties: {
869
+ symbol: { type: "string" },
870
+ orderId: { type: "string" },
871
+ orderType: { type: "string" },
872
+ limit: { type: "number" }
873
+ }
874
+ },
875
+ handler: async (rawArgs, context) => {
876
+ const args = asRecord(rawArgs);
877
+ const response = await context.client.privateGet(
878
+ "/api/v1/futures/openOrders",
879
+ compactObject({
880
+ symbol: readString(args, "symbol"),
881
+ orderId: readString(args, "orderId"),
882
+ orderType: readString(args, "orderType"),
883
+ limit: readNumber(args, "limit")
884
+ }),
885
+ privateRateLimit("futures_get_open_orders", 20)
886
+ );
887
+ return normalize2(response);
888
+ }
889
+ },
890
+ {
891
+ name: "futures_get_history_orders",
892
+ module: "futures",
893
+ description: "Get futures order history. Private endpoint. Rate limit: 20 req/s.",
894
+ isWrite: false,
895
+ inputSchema: {
896
+ type: "object",
897
+ properties: {
898
+ symbol: { type: "string" },
899
+ orderId: { type: "string" },
900
+ orderType: { type: "string" },
901
+ startTime: { type: "number" },
902
+ endTime: { type: "number" },
903
+ limit: { type: "number" }
904
+ }
905
+ },
906
+ handler: async (rawArgs, context) => {
907
+ const args = asRecord(rawArgs);
908
+ const response = await context.client.privateGet(
909
+ "/api/v1/futures/historyOrders",
910
+ compactObject({
911
+ symbol: readString(args, "symbol"),
912
+ orderId: readString(args, "orderId"),
913
+ orderType: readString(args, "orderType"),
914
+ startTime: readNumber(args, "startTime"),
915
+ endTime: readNumber(args, "endTime"),
916
+ limit: readNumber(args, "limit")
917
+ }),
918
+ privateRateLimit("futures_get_history_orders", 20)
919
+ );
920
+ return normalize2(response);
921
+ }
922
+ },
923
+ {
924
+ name: "futures_get_positions",
925
+ module: "futures",
926
+ description: "Get current futures positions. Private endpoint. Rate limit: 20 req/s.",
927
+ isWrite: false,
928
+ inputSchema: {
929
+ type: "object",
930
+ properties: {
931
+ symbol: { type: "string", description: "Omit for all positions" }
932
+ }
933
+ },
934
+ handler: async (rawArgs, context) => {
935
+ const args = asRecord(rawArgs);
936
+ const response = await context.client.privateGet(
937
+ "/api/v1/futures/positions",
938
+ compactObject({ symbol: readString(args, "symbol") }),
939
+ privateRateLimit("futures_get_positions", 20)
940
+ );
941
+ return normalize2(response);
942
+ }
943
+ },
944
+ {
945
+ name: "futures_get_history_positions",
946
+ module: "futures",
947
+ description: "Get futures closed position history. Private endpoint. Rate limit: 20 req/s.",
948
+ isWrite: false,
949
+ inputSchema: {
950
+ type: "object",
951
+ properties: {
952
+ symbol: { type: "string" },
953
+ startTime: { type: "number" },
954
+ endTime: { type: "number" },
955
+ limit: { type: "number" }
956
+ }
957
+ },
958
+ handler: async (rawArgs, context) => {
959
+ const args = asRecord(rawArgs);
960
+ const response = await context.client.privateGet(
961
+ "/api/v1/futures/historyPositions",
962
+ compactObject({
963
+ symbol: readString(args, "symbol"),
964
+ startTime: readNumber(args, "startTime"),
965
+ endTime: readNumber(args, "endTime"),
966
+ limit: readNumber(args, "limit")
967
+ }),
968
+ privateRateLimit("futures_get_history_positions", 20)
969
+ );
970
+ return normalize2(response);
971
+ }
972
+ },
973
+ {
974
+ name: "futures_set_leverage",
975
+ module: "futures",
976
+ description: "Set leverage for a futures symbol. [CAUTION] Private endpoint. Rate limit: 10 req/s.",
977
+ isWrite: true,
978
+ inputSchema: {
979
+ type: "object",
980
+ properties: {
981
+ symbol: { type: "string", description: "e.g. BTCUSDT" },
982
+ leverage: { type: "number", description: "Leverage value, e.g. 10" }
983
+ },
984
+ required: ["symbol", "leverage"]
985
+ },
986
+ handler: async (rawArgs, context) => {
987
+ const args = asRecord(rawArgs);
988
+ const response = await context.client.privatePost(
989
+ "/api/v1/futures/leverage",
990
+ compactObject({
991
+ symbol: requireString(args, "symbol"),
992
+ leverage: readNumber(args, "leverage")
993
+ }),
994
+ privateRateLimit("futures_set_leverage", 10)
995
+ );
996
+ return normalize2(response);
997
+ }
998
+ },
999
+ {
1000
+ name: "futures_get_leverage",
1001
+ module: "futures",
1002
+ description: "Get current leverage and position mode for a futures symbol. Private endpoint. Rate limit: 20 req/s.",
1003
+ isWrite: false,
1004
+ inputSchema: {
1005
+ type: "object",
1006
+ properties: {
1007
+ symbol: { type: "string", description: "e.g. BTCUSDT" }
1008
+ },
1009
+ required: ["symbol"]
1010
+ },
1011
+ handler: async (rawArgs, context) => {
1012
+ const args = asRecord(rawArgs);
1013
+ const response = await context.client.privateGet(
1014
+ "/api/v1/futures/accountLeverage",
1015
+ { symbol: requireString(args, "symbol") },
1016
+ privateRateLimit("futures_get_leverage", 20)
1017
+ );
1018
+ return normalize2(response);
1019
+ }
1020
+ },
1021
+ {
1022
+ name: "futures_set_margin_type",
1023
+ module: "futures",
1024
+ description: "Switch between cross and isolated margin mode. [CAUTION] Private endpoint. Rate limit: 10 req/s.",
1025
+ isWrite: true,
1026
+ inputSchema: {
1027
+ type: "object",
1028
+ properties: {
1029
+ symbol: { type: "string", description: "e.g. BTCUSDT" },
1030
+ marginType: { type: "string", enum: ["1", "2"], description: "1=cross, 2=isolated" }
1031
+ },
1032
+ required: ["symbol", "marginType"]
1033
+ },
1034
+ handler: async (rawArgs, context) => {
1035
+ const args = asRecord(rawArgs);
1036
+ const response = await context.client.privatePost(
1037
+ "/api/v1/futures/marginType",
1038
+ compactObject({
1039
+ symbol: requireString(args, "symbol"),
1040
+ marginType: requireString(args, "marginType")
1041
+ }),
1042
+ privateRateLimit("futures_set_margin_type", 10)
1043
+ );
1044
+ return normalize2(response);
1045
+ }
1046
+ },
1047
+ {
1048
+ name: "futures_set_trading_stop",
1049
+ module: "futures",
1050
+ description: "Set take-profit/stop-loss for a futures position. [CAUTION] Private endpoint. Rate limit: 10 req/s.",
1051
+ isWrite: true,
1052
+ inputSchema: {
1053
+ type: "object",
1054
+ properties: {
1055
+ symbol: { type: "string" },
1056
+ side: { type: "string", enum: ["LONG", "SHORT"] },
1057
+ stopLossPrice: { type: "string" },
1058
+ takeProfitPrice: { type: "string" }
1059
+ },
1060
+ required: ["symbol", "side"]
1061
+ },
1062
+ handler: async (rawArgs, context) => {
1063
+ const args = asRecord(rawArgs);
1064
+ const response = await context.client.privatePost(
1065
+ "/api/v1/futures/position/trading-stop",
1066
+ compactObject({
1067
+ symbol: requireString(args, "symbol"),
1068
+ side: requireString(args, "side"),
1069
+ stopLossPrice: readString(args, "stopLossPrice"),
1070
+ takeProfitPrice: readString(args, "takeProfitPrice")
1071
+ }),
1072
+ privateRateLimit("futures_set_trading_stop", 10)
1073
+ );
1074
+ return normalize2(response);
1075
+ }
1076
+ },
1077
+ {
1078
+ name: "futures_flash_close",
1079
+ module: "futures",
1080
+ description: "Flash close a futures position (market close). [CAUTION] Private endpoint. Rate limit: 10 req/s.",
1081
+ isWrite: true,
1082
+ inputSchema: {
1083
+ type: "object",
1084
+ properties: {
1085
+ symbol: { type: "string" },
1086
+ side: { type: "string", enum: ["LONG", "SHORT"] }
1087
+ },
1088
+ required: ["symbol", "side"]
1089
+ },
1090
+ handler: async (rawArgs, context) => {
1091
+ const args = asRecord(rawArgs);
1092
+ const response = await context.client.privatePost(
1093
+ "/api/v1/futures/flashClose",
1094
+ compactObject({
1095
+ symbol: requireString(args, "symbol"),
1096
+ side: requireString(args, "side")
1097
+ }),
1098
+ privateRateLimit("futures_flash_close", 10)
1099
+ );
1100
+ return normalize2(response);
1101
+ }
1102
+ },
1103
+ {
1104
+ name: "futures_reverse_position",
1105
+ module: "futures",
1106
+ description: "Reverse a futures position (one-click reverse). [CAUTION] Private endpoint. Rate limit: 10 req/s.",
1107
+ isWrite: true,
1108
+ inputSchema: {
1109
+ type: "object",
1110
+ properties: {
1111
+ symbol: { type: "string" },
1112
+ side: { type: "string", enum: ["LONG", "SHORT"], description: "Current position side to reverse" }
1113
+ },
1114
+ required: ["symbol", "side"]
1115
+ },
1116
+ handler: async (rawArgs, context) => {
1117
+ const args = asRecord(rawArgs);
1118
+ const response = await context.client.privatePost(
1119
+ "/api/v1/futures/reversePosition",
1120
+ compactObject({
1121
+ symbol: requireString(args, "symbol"),
1122
+ side: requireString(args, "side")
1123
+ }),
1124
+ privateRateLimit("futures_reverse_position", 10)
1125
+ );
1126
+ return normalize2(response);
1127
+ }
1128
+ },
1129
+ {
1130
+ name: "futures_adjust_margin",
1131
+ module: "futures",
1132
+ description: "Adjust isolated margin for a position. [CAUTION] Private endpoint. Rate limit: 10 req/s.",
1133
+ isWrite: true,
1134
+ inputSchema: {
1135
+ type: "object",
1136
+ properties: {
1137
+ symbol: { type: "string" },
1138
+ side: { type: "string", enum: ["LONG", "SHORT"] },
1139
+ amount: { type: "string", description: "Positive=add, negative=reduce" }
1140
+ },
1141
+ required: ["symbol", "side", "amount"]
1142
+ },
1143
+ handler: async (rawArgs, context) => {
1144
+ const args = asRecord(rawArgs);
1145
+ const response = await context.client.privatePost(
1146
+ "/api/v1/futures/positionMargin",
1147
+ compactObject({
1148
+ symbol: requireString(args, "symbol"),
1149
+ side: requireString(args, "side"),
1150
+ amount: requireString(args, "amount")
1151
+ }),
1152
+ privateRateLimit("futures_adjust_margin", 10)
1153
+ );
1154
+ return normalize2(response);
1155
+ }
1156
+ },
1157
+ {
1158
+ name: "futures_get_fills",
1159
+ module: "futures",
1160
+ description: "Get futures trade history (fills). Private endpoint. Rate limit: 20 req/s.",
1161
+ isWrite: false,
1162
+ inputSchema: {
1163
+ type: "object",
1164
+ properties: {
1165
+ symbol: { type: "string" },
1166
+ startTime: { type: "number" },
1167
+ endTime: { type: "number" },
1168
+ fromId: { type: "string" },
1169
+ limit: { type: "number" }
1170
+ }
1171
+ },
1172
+ handler: async (rawArgs, context) => {
1173
+ const args = asRecord(rawArgs);
1174
+ const response = await context.client.privateGet(
1175
+ "/api/v1/futures/userTrades",
1176
+ compactObject({
1177
+ symbol: readString(args, "symbol"),
1178
+ startTime: readNumber(args, "startTime"),
1179
+ endTime: readNumber(args, "endTime"),
1180
+ fromId: readString(args, "fromId"),
1181
+ limit: readNumber(args, "limit")
1182
+ }),
1183
+ privateRateLimit("futures_get_fills", 20)
1184
+ );
1185
+ return normalize2(response);
1186
+ }
1187
+ },
1188
+ {
1189
+ name: "futures_get_balance",
1190
+ module: "futures",
1191
+ description: "Get futures account balance. Private endpoint. Rate limit: 20 req/s.",
1192
+ isWrite: false,
1193
+ inputSchema: { type: "object", properties: {} },
1194
+ handler: async (_rawArgs, context) => {
1195
+ const response = await context.client.privateGet(
1196
+ "/api/v1/futures/balance",
1197
+ {},
1198
+ privateRateLimit("futures_get_balance", 20)
1199
+ );
1200
+ return normalize2(response);
1201
+ }
1202
+ },
1203
+ {
1204
+ name: "futures_get_commission_rate",
1205
+ module: "futures",
1206
+ description: "Get futures commission rate for a symbol. Private endpoint. Rate limit: 20 req/s.",
1207
+ isWrite: false,
1208
+ inputSchema: {
1209
+ type: "object",
1210
+ properties: {
1211
+ symbol: { type: "string", description: "e.g. BTCUSDT" }
1212
+ },
1213
+ required: ["symbol"]
1214
+ },
1215
+ handler: async (rawArgs, context) => {
1216
+ const args = asRecord(rawArgs);
1217
+ const response = await context.client.privateGet(
1218
+ "/api/v1/futures/commissionRate",
1219
+ { symbol: requireString(args, "symbol") },
1220
+ privateRateLimit("futures_get_commission_rate", 20)
1221
+ );
1222
+ return normalize2(response);
1223
+ }
1224
+ },
1225
+ {
1226
+ name: "futures_get_today_pnl",
1227
+ module: "futures",
1228
+ description: "Get today's realized PnL for futures. Private endpoint. Rate limit: 20 req/s.",
1229
+ isWrite: false,
1230
+ inputSchema: { type: "object", properties: {} },
1231
+ handler: async (_rawArgs, context) => {
1232
+ const response = await context.client.privateGet(
1233
+ "/api/v1/futures/todayPnl",
1234
+ {},
1235
+ privateRateLimit("futures_get_today_pnl", 20)
1236
+ );
1237
+ return normalize2(response);
1238
+ }
1239
+ },
1240
+ {
1241
+ name: "futures_get_balance_flow",
1242
+ module: "futures",
1243
+ description: "Get futures balance flow (ledger). Private endpoint. Rate limit: 20 req/s.",
1244
+ isWrite: false,
1245
+ inputSchema: {
1246
+ type: "object",
1247
+ properties: {
1248
+ symbol: { type: "string" },
1249
+ startTime: { type: "number" },
1250
+ endTime: { type: "number" },
1251
+ limit: { type: "number" },
1252
+ fromId: { type: "string" }
1253
+ }
1254
+ },
1255
+ handler: async (rawArgs, context) => {
1256
+ const args = asRecord(rawArgs);
1257
+ const response = await context.client.privateGet(
1258
+ "/api/v1/futures/balanceFlow",
1259
+ compactObject({
1260
+ symbol: readString(args, "symbol"),
1261
+ startTime: readNumber(args, "startTime"),
1262
+ endTime: readNumber(args, "endTime"),
1263
+ limit: readNumber(args, "limit"),
1264
+ fromId: readString(args, "fromId")
1265
+ }),
1266
+ privateRateLimit("futures_get_balance_flow", 20)
1267
+ );
1268
+ return normalize2(response);
1269
+ }
1270
+ },
1271
+ {
1272
+ name: "futures_auto_add_margin",
1273
+ module: "futures",
1274
+ description: "Enable/disable auto add margin for isolated positions. [CAUTION] Private endpoint. Rate limit: 10 req/s.",
1275
+ isWrite: true,
1276
+ inputSchema: {
1277
+ type: "object",
1278
+ properties: {
1279
+ symbol: { type: "string" },
1280
+ side: { type: "string", enum: ["LONG", "SHORT"] },
1281
+ autoAddMargin: { type: "string", enum: ["true", "false"] }
1282
+ },
1283
+ required: ["symbol", "side", "autoAddMargin"]
1284
+ },
1285
+ handler: async (rawArgs, context) => {
1286
+ const args = asRecord(rawArgs);
1287
+ const response = await context.client.privatePost(
1288
+ "/api/v1/futures/autoAddMargin",
1289
+ compactObject({
1290
+ symbol: requireString(args, "symbol"),
1291
+ side: requireString(args, "side"),
1292
+ autoAddMargin: requireString(args, "autoAddMargin")
1293
+ }),
1294
+ privateRateLimit("futures_auto_add_margin", 10)
1295
+ );
1296
+ return normalize2(response);
1297
+ }
1298
+ }
1299
+ ];
1300
+ }
1301
+ function normalize3(response) {
1302
+ return { endpoint: response.endpoint, requestTime: response.requestTime, data: response.data };
1303
+ }
1304
+ function registerMarketTools() {
1305
+ return [
1306
+ {
1307
+ name: "market_get_server_time",
1308
+ module: "market",
1309
+ description: "Get Toobit server time. Public endpoint. Rate limit: 20 req/s.",
1310
+ isWrite: false,
1311
+ inputSchema: { type: "object", properties: {} },
1312
+ handler: async (_rawArgs, context) => {
1313
+ const response = await context.client.publicGet("/api/v1/time", {}, publicRateLimit("market_get_server_time", 20));
1314
+ return normalize3(response);
1315
+ }
1316
+ },
1317
+ {
1318
+ name: "market_get_exchange_info",
1319
+ module: "market",
1320
+ description: "Get exchange info including trading rules, symbol list, rate limits. Public endpoint. Rate limit: 10 req/s.",
1321
+ isWrite: false,
1322
+ inputSchema: { type: "object", properties: {} },
1323
+ handler: async (_rawArgs, context) => {
1324
+ const response = await context.client.publicGet("/api/v1/exchangeInfo", {}, publicRateLimit("market_get_exchange_info", 10));
1325
+ return normalize3(response);
1326
+ }
1327
+ },
1328
+ {
1329
+ name: "market_get_depth",
1330
+ module: "market",
1331
+ description: "Get order book depth for a symbol. Public endpoint. Rate limit: 20 req/s.",
1332
+ isWrite: false,
1333
+ inputSchema: {
1334
+ type: "object",
1335
+ properties: {
1336
+ symbol: { type: "string", description: "e.g. BTCUSDT" },
1337
+ limit: { type: "number", description: "Depth per side, default 100, max 100" }
1338
+ },
1339
+ required: ["symbol"]
1340
+ },
1341
+ handler: async (rawArgs, context) => {
1342
+ const args = asRecord(rawArgs);
1343
+ const response = await context.client.publicGet(
1344
+ "/quote/v1/depth",
1345
+ compactObject({ symbol: requireString(args, "symbol"), limit: readNumber(args, "limit") }),
1346
+ publicRateLimit("market_get_depth", 20)
1347
+ );
1348
+ return normalize3(response);
1349
+ }
1350
+ },
1351
+ {
1352
+ name: "market_get_merged_depth",
1353
+ module: "market",
1354
+ description: "Get merged order book depth for a symbol. Public endpoint. Rate limit: 20 req/s.",
1355
+ isWrite: false,
1356
+ inputSchema: {
1357
+ type: "object",
1358
+ properties: {
1359
+ symbol: { type: "string", description: "e.g. BTCUSDT" },
1360
+ scale: { type: "number", description: "Price merge precision" },
1361
+ limit: { type: "number", description: "Default 40, max 100" }
1362
+ },
1363
+ required: ["symbol"]
1364
+ },
1365
+ handler: async (rawArgs, context) => {
1366
+ const args = asRecord(rawArgs);
1367
+ const response = await context.client.publicGet(
1368
+ "/quote/v1/depth/merged",
1369
+ compactObject({ symbol: requireString(args, "symbol"), scale: readNumber(args, "scale"), limit: readNumber(args, "limit") }),
1370
+ publicRateLimit("market_get_merged_depth", 20)
1371
+ );
1372
+ return normalize3(response);
1373
+ }
1374
+ },
1375
+ {
1376
+ name: "market_get_trades",
1377
+ module: "market",
1378
+ description: "Get recent trades for a symbol. Default 60 records, max 60. Public endpoint. Rate limit: 20 req/s.",
1379
+ isWrite: false,
1380
+ inputSchema: {
1381
+ type: "object",
1382
+ properties: {
1383
+ symbol: { type: "string", description: "e.g. BTCUSDT" },
1384
+ limit: { type: "number", description: "Default 60, max 60" }
1385
+ },
1386
+ required: ["symbol"]
1387
+ },
1388
+ handler: async (rawArgs, context) => {
1389
+ const args = asRecord(rawArgs);
1390
+ const response = await context.client.publicGet(
1391
+ "/quote/v1/trades",
1392
+ compactObject({ symbol: requireString(args, "symbol"), limit: readNumber(args, "limit") }),
1393
+ publicRateLimit("market_get_trades", 20)
1394
+ );
1395
+ return normalize3(response);
1396
+ }
1397
+ },
1398
+ {
1399
+ name: "market_get_klines",
1400
+ module: "market",
1401
+ description: "Get candlestick (OHLCV) data for a symbol. Public endpoint. Rate limit: 20 req/s.",
1402
+ isWrite: false,
1403
+ inputSchema: {
1404
+ type: "object",
1405
+ properties: {
1406
+ symbol: { type: "string", description: "e.g. BTCUSDT" },
1407
+ interval: { type: "string", enum: [...TOOBIT_CANDLE_BARS], description: "K-line interval, e.g. 1m, 1h, 1d" },
1408
+ startTime: { type: "number", description: "Start time in ms" },
1409
+ endTime: { type: "number", description: "End time in ms" },
1410
+ limit: { type: "number", description: "Default 500, max 1000" }
1411
+ },
1412
+ required: ["symbol", "interval"]
1413
+ },
1414
+ handler: async (rawArgs, context) => {
1415
+ const args = asRecord(rawArgs);
1416
+ const response = await context.client.publicGet(
1417
+ "/quote/v1/klines",
1418
+ compactObject({
1419
+ symbol: requireString(args, "symbol"),
1420
+ interval: requireString(args, "interval"),
1421
+ startTime: readNumber(args, "startTime"),
1422
+ endTime: readNumber(args, "endTime"),
1423
+ limit: readNumber(args, "limit")
1424
+ }),
1425
+ publicRateLimit("market_get_klines", 20)
1426
+ );
1427
+ return normalize3(response);
1428
+ }
1429
+ },
1430
+ {
1431
+ name: "market_get_ticker_24hr",
1432
+ module: "market",
1433
+ description: "Get 24h price change statistics for a spot symbol. Public endpoint. Rate limit: 20 req/s.",
1434
+ isWrite: false,
1435
+ inputSchema: {
1436
+ type: "object",
1437
+ properties: {
1438
+ symbol: { type: "string", description: "e.g. BTCUSDT. Omit for all symbols." }
1439
+ }
1440
+ },
1441
+ handler: async (rawArgs, context) => {
1442
+ const args = asRecord(rawArgs);
1443
+ const response = await context.client.publicGet(
1444
+ "/quote/v1/ticker/24hr",
1445
+ compactObject({ symbol: readString(args, "symbol") }),
1446
+ publicRateLimit("market_get_ticker_24hr", 20)
1447
+ );
1448
+ return normalize3(response);
1449
+ }
1450
+ },
1451
+ {
1452
+ name: "market_get_ticker_price",
1453
+ module: "market",
1454
+ description: "Get latest price for a spot symbol. Public endpoint. Rate limit: 20 req/s.",
1455
+ isWrite: false,
1456
+ inputSchema: {
1457
+ type: "object",
1458
+ properties: {
1459
+ symbol: { type: "string", description: "e.g. BTCUSDT. Omit for all symbols." }
1460
+ }
1461
+ },
1462
+ handler: async (rawArgs, context) => {
1463
+ const args = asRecord(rawArgs);
1464
+ const response = await context.client.publicGet(
1465
+ "/quote/v1/ticker/price",
1466
+ compactObject({ symbol: readString(args, "symbol") }),
1467
+ publicRateLimit("market_get_ticker_price", 20)
1468
+ );
1469
+ return normalize3(response);
1470
+ }
1471
+ },
1472
+ {
1473
+ name: "market_get_book_ticker",
1474
+ module: "market",
1475
+ description: "Get best bid/ask price for a spot symbol. Public endpoint. Rate limit: 20 req/s.",
1476
+ isWrite: false,
1477
+ inputSchema: {
1478
+ type: "object",
1479
+ properties: {
1480
+ symbol: { type: "string", description: "e.g. BTCUSDT. Omit for all symbols." }
1481
+ }
1482
+ },
1483
+ handler: async (rawArgs, context) => {
1484
+ const args = asRecord(rawArgs);
1485
+ const response = await context.client.publicGet(
1486
+ "/quote/v1/ticker/bookTicker",
1487
+ compactObject({ symbol: readString(args, "symbol") }),
1488
+ publicRateLimit("market_get_book_ticker", 20)
1489
+ );
1490
+ return normalize3(response);
1491
+ }
1492
+ },
1493
+ {
1494
+ name: "market_get_index_klines",
1495
+ module: "market",
1496
+ description: "Get index K-line data. Public endpoint. Rate limit: 20 req/s.",
1497
+ isWrite: false,
1498
+ inputSchema: {
1499
+ type: "object",
1500
+ properties: {
1501
+ symbol: { type: "string", description: "e.g. BTCUSDT" },
1502
+ interval: { type: "string", enum: [...TOOBIT_CANDLE_BARS] },
1503
+ startTime: { type: "number" },
1504
+ endTime: { type: "number" },
1505
+ limit: { type: "number", description: "Default 500, max 1000" }
1506
+ },
1507
+ required: ["symbol", "interval"]
1508
+ },
1509
+ handler: async (rawArgs, context) => {
1510
+ const args = asRecord(rawArgs);
1511
+ const response = await context.client.publicGet(
1512
+ "/quote/v1/index/klines",
1513
+ compactObject({
1514
+ symbol: requireString(args, "symbol"),
1515
+ interval: requireString(args, "interval"),
1516
+ startTime: readNumber(args, "startTime"),
1517
+ endTime: readNumber(args, "endTime"),
1518
+ limit: readNumber(args, "limit")
1519
+ }),
1520
+ publicRateLimit("market_get_index_klines", 20)
1521
+ );
1522
+ return normalize3(response);
1523
+ }
1524
+ },
1525
+ {
1526
+ name: "market_get_mark_price",
1527
+ module: "market",
1528
+ description: "Get latest mark price for a futures symbol. Public endpoint. Rate limit: 20 req/s.",
1529
+ isWrite: false,
1530
+ inputSchema: {
1531
+ type: "object",
1532
+ properties: {
1533
+ symbol: { type: "string", description: "e.g. BTCUSDT. Omit for all." }
1534
+ }
1535
+ },
1536
+ handler: async (rawArgs, context) => {
1537
+ const args = asRecord(rawArgs);
1538
+ const response = await context.client.publicGet(
1539
+ "/quote/v1/markPrice",
1540
+ compactObject({ symbol: readString(args, "symbol") }),
1541
+ publicRateLimit("market_get_mark_price", 20)
1542
+ );
1543
+ return normalize3(response);
1544
+ }
1545
+ },
1546
+ {
1547
+ name: "market_get_mark_price_klines",
1548
+ module: "market",
1549
+ description: "Get mark price K-line data. Public endpoint. Rate limit: 20 req/s.",
1550
+ isWrite: false,
1551
+ inputSchema: {
1552
+ type: "object",
1553
+ properties: {
1554
+ symbol: { type: "string", description: "e.g. BTCUSDT" },
1555
+ interval: { type: "string", enum: [...TOOBIT_CANDLE_BARS] },
1556
+ startTime: { type: "number" },
1557
+ endTime: { type: "number" },
1558
+ limit: { type: "number" }
1559
+ },
1560
+ required: ["symbol", "interval"]
1561
+ },
1562
+ handler: async (rawArgs, context) => {
1563
+ const args = asRecord(rawArgs);
1564
+ const response = await context.client.publicGet(
1565
+ "/quote/v1/markPrice/klines",
1566
+ compactObject({
1567
+ symbol: requireString(args, "symbol"),
1568
+ interval: requireString(args, "interval"),
1569
+ startTime: readNumber(args, "startTime"),
1570
+ endTime: readNumber(args, "endTime"),
1571
+ limit: readNumber(args, "limit")
1572
+ }),
1573
+ publicRateLimit("market_get_mark_price_klines", 20)
1574
+ );
1575
+ return normalize3(response);
1576
+ }
1577
+ },
1578
+ {
1579
+ name: "market_get_funding_rate",
1580
+ module: "market",
1581
+ description: "Get current funding rate for a futures symbol. Public endpoint. Rate limit: 20 req/s.",
1582
+ isWrite: false,
1583
+ inputSchema: {
1584
+ type: "object",
1585
+ properties: {
1586
+ symbol: { type: "string", description: "e.g. BTCUSDT. Omit for all." }
1587
+ }
1588
+ },
1589
+ handler: async (rawArgs, context) => {
1590
+ const args = asRecord(rawArgs);
1591
+ const response = await context.client.publicGet(
1592
+ "/api/v1/futures/fundingRate",
1593
+ compactObject({ symbol: readString(args, "symbol") }),
1594
+ publicRateLimit("market_get_funding_rate", 20)
1595
+ );
1596
+ return normalize3(response);
1597
+ }
1598
+ },
1599
+ {
1600
+ name: "market_get_funding_rate_history",
1601
+ module: "market",
1602
+ description: "Get historical funding rates for a futures symbol. Public endpoint. Rate limit: 20 req/s.",
1603
+ isWrite: false,
1604
+ inputSchema: {
1605
+ type: "object",
1606
+ properties: {
1607
+ symbol: { type: "string", description: "e.g. BTCUSDT" },
1608
+ startTime: { type: "number" },
1609
+ endTime: { type: "number" },
1610
+ limit: { type: "number", description: "Default 100, max 1000" }
1611
+ },
1612
+ required: ["symbol"]
1613
+ },
1614
+ handler: async (rawArgs, context) => {
1615
+ const args = asRecord(rawArgs);
1616
+ const response = await context.client.publicGet(
1617
+ "/api/v1/futures/historyFundingRate",
1618
+ compactObject({
1619
+ symbol: requireString(args, "symbol"),
1620
+ startTime: readNumber(args, "startTime"),
1621
+ endTime: readNumber(args, "endTime"),
1622
+ limit: readNumber(args, "limit")
1623
+ }),
1624
+ publicRateLimit("market_get_funding_rate_history", 20)
1625
+ );
1626
+ return normalize3(response);
1627
+ }
1628
+ },
1629
+ {
1630
+ name: "market_get_open_interest",
1631
+ module: "market",
1632
+ description: "Get total open interest for a futures symbol. Public endpoint. Rate limit: 20 req/s.",
1633
+ isWrite: false,
1634
+ inputSchema: {
1635
+ type: "object",
1636
+ properties: {
1637
+ symbol: { type: "string", description: "e.g. BTCUSDT. Omit for all." }
1638
+ }
1639
+ },
1640
+ handler: async (rawArgs, context) => {
1641
+ const args = asRecord(rawArgs);
1642
+ const response = await context.client.publicGet(
1643
+ "/quote/v1/openInterest",
1644
+ compactObject({ symbol: readString(args, "symbol") }),
1645
+ publicRateLimit("market_get_open_interest", 20)
1646
+ );
1647
+ return normalize3(response);
1648
+ }
1649
+ },
1650
+ {
1651
+ name: "market_get_long_short_ratio",
1652
+ module: "market",
1653
+ description: "Get global long/short account ratio. Public endpoint. Rate limit: 20 req/s.",
1654
+ isWrite: false,
1655
+ inputSchema: {
1656
+ type: "object",
1657
+ properties: {
1658
+ symbol: { type: "string", description: "e.g. BTCUSDT" },
1659
+ period: { type: "string", description: "e.g. 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d" },
1660
+ startTime: { type: "number" },
1661
+ endTime: { type: "number" },
1662
+ limit: { type: "number" }
1663
+ },
1664
+ required: ["symbol", "period"]
1665
+ },
1666
+ handler: async (rawArgs, context) => {
1667
+ const args = asRecord(rawArgs);
1668
+ const response = await context.client.publicGet(
1669
+ "/quote/v1/globalLongShortAccountRatio",
1670
+ compactObject({
1671
+ symbol: requireString(args, "symbol"),
1672
+ period: requireString(args, "period"),
1673
+ startTime: readNumber(args, "startTime"),
1674
+ endTime: readNumber(args, "endTime"),
1675
+ limit: readNumber(args, "limit")
1676
+ }),
1677
+ publicRateLimit("market_get_long_short_ratio", 20)
1678
+ );
1679
+ return normalize3(response);
1680
+ }
1681
+ },
1682
+ {
1683
+ name: "market_get_contract_ticker_24hr",
1684
+ module: "market",
1685
+ description: "Get 24h price change statistics for a futures contract. Public endpoint. Rate limit: 20 req/s.",
1686
+ isWrite: false,
1687
+ inputSchema: {
1688
+ type: "object",
1689
+ properties: {
1690
+ symbol: { type: "string", description: "e.g. BTCUSDT. Omit for all." }
1691
+ }
1692
+ },
1693
+ handler: async (rawArgs, context) => {
1694
+ const args = asRecord(rawArgs);
1695
+ const response = await context.client.publicGet(
1696
+ "/quote/v1/contract/ticker/24hr",
1697
+ compactObject({ symbol: readString(args, "symbol") }),
1698
+ publicRateLimit("market_get_contract_ticker_24hr", 20)
1699
+ );
1700
+ return normalize3(response);
1701
+ }
1702
+ },
1703
+ {
1704
+ name: "market_get_contract_ticker_price",
1705
+ module: "market",
1706
+ description: "Get latest price for a futures contract. Public endpoint. Rate limit: 20 req/s.",
1707
+ isWrite: false,
1708
+ inputSchema: {
1709
+ type: "object",
1710
+ properties: {
1711
+ symbol: { type: "string", description: "e.g. BTCUSDT. Omit for all." }
1712
+ }
1713
+ },
1714
+ handler: async (rawArgs, context) => {
1715
+ const args = asRecord(rawArgs);
1716
+ const response = await context.client.publicGet(
1717
+ "/quote/v1/contract/ticker/price",
1718
+ compactObject({ symbol: readString(args, "symbol") }),
1719
+ publicRateLimit("market_get_contract_ticker_price", 20)
1720
+ );
1721
+ return normalize3(response);
1722
+ }
1723
+ },
1724
+ {
1725
+ name: "market_get_index_price",
1726
+ module: "market",
1727
+ description: "Get index price for a futures symbol. Public endpoint. Rate limit: 20 req/s.",
1728
+ isWrite: false,
1729
+ inputSchema: {
1730
+ type: "object",
1731
+ properties: {
1732
+ symbol: { type: "string", description: "e.g. BTCUSDT. Omit for all." }
1733
+ }
1734
+ },
1735
+ handler: async (rawArgs, context) => {
1736
+ const args = asRecord(rawArgs);
1737
+ const response = await context.client.publicGet(
1738
+ "/quote/v1/index",
1739
+ compactObject({ symbol: readString(args, "symbol") }),
1740
+ publicRateLimit("market_get_index_price", 20)
1741
+ );
1742
+ return normalize3(response);
1743
+ }
1744
+ },
1745
+ {
1746
+ name: "market_get_insurance_fund",
1747
+ module: "market",
1748
+ description: "Get insurance fund balance for a symbol. Public endpoint. Rate limit: 20 req/s.",
1749
+ isWrite: false,
1750
+ inputSchema: {
1751
+ type: "object",
1752
+ properties: {
1753
+ symbol: { type: "string", description: "e.g. BTCUSDT" }
1754
+ },
1755
+ required: ["symbol"]
1756
+ },
1757
+ handler: async (rawArgs, context) => {
1758
+ const args = asRecord(rawArgs);
1759
+ const response = await context.client.publicGet(
1760
+ "/api/v1/futures/insuranceBySymbol",
1761
+ { symbol: requireString(args, "symbol") },
1762
+ publicRateLimit("market_get_insurance_fund", 20)
1763
+ );
1764
+ return normalize3(response);
1765
+ }
1766
+ },
1767
+ {
1768
+ name: "market_get_risk_limits",
1769
+ module: "market",
1770
+ description: "Get risk limits configuration for a futures symbol. Public endpoint. Rate limit: 20 req/s.",
1771
+ isWrite: false,
1772
+ inputSchema: {
1773
+ type: "object",
1774
+ properties: {
1775
+ symbol: { type: "string", description: "e.g. BTCUSDT" }
1776
+ },
1777
+ required: ["symbol"]
1778
+ },
1779
+ handler: async (rawArgs, context) => {
1780
+ const args = asRecord(rawArgs);
1781
+ const response = await context.client.publicGet(
1782
+ "/api/v1/futures/riskLimits",
1783
+ { symbol: requireString(args, "symbol") },
1784
+ publicRateLimit("market_get_risk_limits", 20)
1785
+ );
1786
+ return normalize3(response);
1787
+ }
1788
+ }
1789
+ ];
1790
+ }
1791
+ function normalize4(response) {
1792
+ return { endpoint: response.endpoint, requestTime: response.requestTime, data: response.data };
1793
+ }
1794
+ function registerSpotTradeTools() {
1795
+ return [
1796
+ {
1797
+ name: "spot_place_order",
1798
+ module: "spot",
1799
+ description: "Place a spot order (LIMIT, MARKET, LIMIT_MAKER). [CAUTION] Executes real trades. Private endpoint. Rate limit: 50 req/s.",
1800
+ isWrite: true,
1801
+ inputSchema: {
1802
+ type: "object",
1803
+ properties: {
1804
+ symbol: { type: "string", description: "e.g. BTCUSDT" },
1805
+ side: { type: "string", enum: ["BUY", "SELL"] },
1806
+ type: { type: "string", enum: ["LIMIT", "MARKET", "LIMIT_MAKER"], description: "Order type" },
1807
+ quantity: { type: "string", description: "Order quantity" },
1808
+ price: { type: "string", description: "Order price (required for LIMIT)" },
1809
+ newClientOrderId: { type: "string", description: "Client order ID" },
1810
+ timeInForce: { type: "string", enum: ["GTC", "IOC", "FOK"], description: "Default GTC" }
1811
+ },
1812
+ required: ["symbol", "side", "type", "quantity"]
1813
+ },
1814
+ handler: async (rawArgs, context) => {
1815
+ const args = asRecord(rawArgs);
1816
+ const response = await context.client.privatePost(
1817
+ "/api/v1/spot/order",
1818
+ compactObject({
1819
+ symbol: requireString(args, "symbol"),
1820
+ side: requireString(args, "side"),
1821
+ type: requireString(args, "type"),
1822
+ quantity: requireString(args, "quantity"),
1823
+ price: readString(args, "price"),
1824
+ newClientOrderId: readString(args, "newClientOrderId"),
1825
+ timeInForce: readString(args, "timeInForce")
1826
+ }),
1827
+ privateRateLimit("spot_place_order", 50)
1828
+ );
1829
+ return normalize4(response);
1830
+ }
1831
+ },
1832
+ {
1833
+ name: "spot_place_order_test",
1834
+ module: "spot",
1835
+ description: "Test spot order placement without actually submitting. Private endpoint. Rate limit: 50 req/s.",
1836
+ isWrite: false,
1837
+ inputSchema: {
1838
+ type: "object",
1839
+ properties: {
1840
+ symbol: { type: "string", description: "e.g. BTCUSDT" },
1841
+ side: { type: "string", enum: ["BUY", "SELL"] },
1842
+ type: { type: "string", enum: ["LIMIT", "MARKET", "LIMIT_MAKER"] },
1843
+ quantity: { type: "string" },
1844
+ price: { type: "string" }
1845
+ },
1846
+ required: ["symbol", "side", "type", "quantity"]
1847
+ },
1848
+ handler: async (rawArgs, context) => {
1849
+ const args = asRecord(rawArgs);
1850
+ const response = await context.client.privatePost(
1851
+ "/api/v1/spot/orderTest",
1852
+ compactObject({
1853
+ symbol: requireString(args, "symbol"),
1854
+ side: requireString(args, "side"),
1855
+ type: requireString(args, "type"),
1856
+ quantity: requireString(args, "quantity"),
1857
+ price: readString(args, "price")
1858
+ }),
1859
+ privateRateLimit("spot_place_order_test", 50)
1860
+ );
1861
+ return normalize4(response);
1862
+ }
1863
+ },
1864
+ {
1865
+ name: "spot_batch_orders",
1866
+ module: "spot",
1867
+ description: "[CAUTION] Batch place spot orders. Private endpoint. Rate limit: 50 req/s.",
1868
+ isWrite: true,
1869
+ inputSchema: {
1870
+ type: "object",
1871
+ properties: {
1872
+ orders: {
1873
+ type: "array",
1874
+ description: "Array of orders: [{symbol, side, type, quantity, price?, newClientOrderId?, timeInForce?}]",
1875
+ items: { type: "object" }
1876
+ }
1877
+ },
1878
+ required: ["orders"]
1879
+ },
1880
+ handler: async (rawArgs, context) => {
1881
+ const args = asRecord(rawArgs);
1882
+ const orders = args.orders;
1883
+ if (!Array.isArray(orders) || orders.length === 0) throw new Error("orders must be a non-empty array.");
1884
+ const response = await context.client.privatePost(
1885
+ "/api/v1/spot/batchOrders",
1886
+ orders,
1887
+ privateRateLimit("spot_batch_orders", 50)
1888
+ );
1889
+ return normalize4(response);
1890
+ }
1891
+ },
1892
+ {
1893
+ name: "spot_cancel_order",
1894
+ module: "spot",
1895
+ description: "Cancel a spot order by orderId or clientOrderId. Private endpoint. Rate limit: 50 req/s.",
1896
+ isWrite: true,
1897
+ inputSchema: {
1898
+ type: "object",
1899
+ properties: {
1900
+ orderId: { type: "string", description: "Order ID" },
1901
+ clientOrderId: { type: "string", description: "Client order ID" }
1902
+ }
1903
+ },
1904
+ handler: async (rawArgs, context) => {
1905
+ const args = asRecord(rawArgs);
1906
+ const response = await context.client.privateDelete(
1907
+ "/api/v1/spot/order",
1908
+ compactObject({
1909
+ orderId: readString(args, "orderId"),
1910
+ clientOrderId: readString(args, "clientOrderId")
1911
+ }),
1912
+ privateRateLimit("spot_cancel_order", 50)
1913
+ );
1914
+ return normalize4(response);
1915
+ }
1916
+ },
1917
+ {
1918
+ name: "spot_cancel_open_orders",
1919
+ module: "spot",
1920
+ description: "Cancel all open spot orders for a symbol. [CAUTION] Private endpoint. Rate limit: 10 req/s.",
1921
+ isWrite: true,
1922
+ inputSchema: {
1923
+ type: "object",
1924
+ properties: {
1925
+ symbol: { type: "string", description: "e.g. BTCUSDT. Omit for all symbols." }
1926
+ }
1927
+ },
1928
+ handler: async (rawArgs, context) => {
1929
+ const args = asRecord(rawArgs);
1930
+ const response = await context.client.privateDelete(
1931
+ "/api/v1/spot/openOrders",
1932
+ compactObject({ symbol: readString(args, "symbol") }),
1933
+ privateRateLimit("spot_cancel_open_orders", 10)
1934
+ );
1935
+ return normalize4(response);
1936
+ }
1937
+ },
1938
+ {
1939
+ name: "spot_cancel_order_by_ids",
1940
+ module: "spot",
1941
+ description: "Batch cancel spot orders by IDs. [CAUTION] Private endpoint. Rate limit: 50 req/s.",
1942
+ isWrite: true,
1943
+ inputSchema: {
1944
+ type: "object",
1945
+ properties: {
1946
+ orderIds: { type: "string", description: "Comma-separated order IDs" }
1947
+ },
1948
+ required: ["orderIds"]
1949
+ },
1950
+ handler: async (rawArgs, context) => {
1951
+ const args = asRecord(rawArgs);
1952
+ const response = await context.client.privateDelete(
1953
+ "/api/v1/spot/cancelOrderByIds",
1954
+ { orderIds: requireString(args, "orderIds") },
1955
+ privateRateLimit("spot_cancel_order_by_ids", 50)
1956
+ );
1957
+ return normalize4(response);
1958
+ }
1959
+ },
1960
+ {
1961
+ name: "spot_get_order",
1962
+ module: "spot",
1963
+ description: "Get details of a single spot order. Private endpoint. Rate limit: 20 req/s.",
1964
+ isWrite: false,
1965
+ inputSchema: {
1966
+ type: "object",
1967
+ properties: {
1968
+ orderId: { type: "string" },
1969
+ clientOrderId: { type: "string" }
1970
+ }
1971
+ },
1972
+ handler: async (rawArgs, context) => {
1973
+ const args = asRecord(rawArgs);
1974
+ const response = await context.client.privateGet(
1975
+ "/api/v1/spot/order",
1976
+ compactObject({ orderId: readString(args, "orderId"), clientOrderId: readString(args, "clientOrderId") }),
1977
+ privateRateLimit("spot_get_order", 20)
1978
+ );
1979
+ return normalize4(response);
1980
+ }
1981
+ },
1982
+ {
1983
+ name: "spot_get_open_orders",
1984
+ module: "spot",
1985
+ description: "Get current open spot orders. Private endpoint. Rate limit: 20 req/s.",
1986
+ isWrite: false,
1987
+ inputSchema: {
1988
+ type: "object",
1989
+ properties: {
1990
+ symbol: { type: "string" },
1991
+ orderId: { type: "string", description: "Filter orders >= this ID" },
1992
+ limit: { type: "number", description: "Default 500, max 1000" }
1993
+ }
1994
+ },
1995
+ handler: async (rawArgs, context) => {
1996
+ const args = asRecord(rawArgs);
1997
+ const response = await context.client.privateGet(
1998
+ "/api/v1/spot/openOrders",
1999
+ compactObject({ symbol: readString(args, "symbol"), orderId: readString(args, "orderId"), limit: readNumber(args, "limit") }),
2000
+ privateRateLimit("spot_get_open_orders", 20)
2001
+ );
2002
+ return normalize4(response);
2003
+ }
2004
+ },
2005
+ {
2006
+ name: "spot_get_trade_orders",
2007
+ module: "spot",
2008
+ description: "Get all spot orders (history). Private endpoint. Rate limit: 20 req/s.",
2009
+ isWrite: false,
2010
+ inputSchema: {
2011
+ type: "object",
2012
+ properties: {
2013
+ symbol: { type: "string" },
2014
+ orderId: { type: "string" },
2015
+ startTime: { type: "number" },
2016
+ endTime: { type: "number" },
2017
+ limit: { type: "number", description: "Default 500, max 1000" }
2018
+ }
2019
+ },
2020
+ handler: async (rawArgs, context) => {
2021
+ const args = asRecord(rawArgs);
2022
+ const response = await context.client.privateGet(
2023
+ "/api/v1/spot/tradeOrders",
2024
+ compactObject({
2025
+ symbol: readString(args, "symbol"),
2026
+ orderId: readString(args, "orderId"),
2027
+ startTime: readNumber(args, "startTime"),
2028
+ endTime: readNumber(args, "endTime"),
2029
+ limit: readNumber(args, "limit")
2030
+ }),
2031
+ privateRateLimit("spot_get_trade_orders", 20)
2032
+ );
2033
+ return normalize4(response);
2034
+ }
2035
+ },
2036
+ {
2037
+ name: "spot_get_fills",
2038
+ module: "spot",
2039
+ description: "Get spot trade history (fills). Private endpoint. Rate limit: 20 req/s.",
2040
+ isWrite: false,
2041
+ inputSchema: {
2042
+ type: "object",
2043
+ properties: {
2044
+ symbol: { type: "string" },
2045
+ fromId: { type: "string" },
2046
+ toId: { type: "string" },
2047
+ startTime: { type: "number" },
2048
+ endTime: { type: "number" },
2049
+ limit: { type: "number", description: "Default 500, max 1000" }
2050
+ }
2051
+ },
2052
+ handler: async (rawArgs, context) => {
2053
+ const args = asRecord(rawArgs);
2054
+ const response = await context.client.privateGet(
2055
+ "/api/v1/account/trades",
2056
+ compactObject({
2057
+ symbol: readString(args, "symbol"),
2058
+ fromId: readString(args, "fromId"),
2059
+ toId: readString(args, "toId"),
2060
+ startTime: readNumber(args, "startTime"),
2061
+ endTime: readNumber(args, "endTime"),
2062
+ limit: readNumber(args, "limit")
2063
+ }),
2064
+ privateRateLimit("spot_get_fills", 20)
2065
+ );
2066
+ return normalize4(response);
2067
+ }
2068
+ }
2069
+ ];
2070
+ }
2071
+ function allToolSpecs() {
2072
+ return [
2073
+ ...registerMarketTools(),
2074
+ ...registerSpotTradeTools(),
2075
+ ...registerFuturesTools(),
2076
+ ...registerAccountTools(),
2077
+ ...registerAuditTools()
2078
+ ];
2079
+ }
2080
+ function createToolRunner(client, config) {
2081
+ const fullConfig = { ...config, modules: [...MODULES], readOnly: false };
2082
+ const tools = allToolSpecs();
2083
+ const toolMap = new Map(tools.map((t) => [t.name, t]));
2084
+ return async (toolName, args) => {
2085
+ const tool = toolMap.get(toolName);
2086
+ if (!tool) throw new Error(`Unknown tool: ${toolName}`);
2087
+ const result = await tool.handler(args, { config: fullConfig, client });
2088
+ return result;
2089
+ };
2090
+ }
2091
+ function configFilePath() {
2092
+ return join2(homedir2(), ".toobit", "config.toml");
2093
+ }
2094
+ function readFullConfig() {
2095
+ const path4 = configFilePath();
2096
+ if (!existsSync2(path4)) return { profiles: {} };
2097
+ const raw = readFileSync2(path4, "utf-8");
2098
+ return parse(raw);
2099
+ }
2100
+ function readTomlProfile(profileName) {
2101
+ const config = readFullConfig();
2102
+ const name = profileName ?? config.default_profile ?? "default";
2103
+ return config.profiles?.[name] ?? {};
2104
+ }
2105
+ function writeFullConfig(config) {
2106
+ const path4 = configFilePath();
2107
+ const dir = dirname(path4);
2108
+ if (!existsSync2(dir)) {
2109
+ mkdirSync(dir, { recursive: true });
2110
+ }
2111
+ writeFileSync(path4, stringify(config), "utf-8");
2112
+ }
2113
+ function parseModuleList(rawModules) {
2114
+ if (!rawModules || rawModules.trim().length === 0) {
2115
+ return [...DEFAULT_MODULES];
2116
+ }
2117
+ const trimmed = rawModules.trim().toLowerCase();
2118
+ if (trimmed === "all") {
2119
+ return [...MODULES];
2120
+ }
2121
+ const requested = trimmed.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
2122
+ if (requested.length === 0) {
2123
+ return [...DEFAULT_MODULES];
2124
+ }
2125
+ const deduped = /* @__PURE__ */ new Set();
2126
+ for (const moduleId of requested) {
2127
+ if (!MODULES.includes(moduleId)) {
2128
+ throw new ConfigError(
2129
+ `Unknown module "${moduleId}".`,
2130
+ `Use one of: ${MODULES.join(", ")}, or "all".`
2131
+ );
2132
+ }
2133
+ deduped.add(moduleId);
2134
+ }
2135
+ return Array.from(deduped);
2136
+ }
2137
+ function loadConfig(cli) {
2138
+ const toml = readTomlProfile(cli.profile);
2139
+ const apiKey = process.env.TOOBIT_API_KEY?.trim() ?? toml.api_key;
2140
+ const secretKey = process.env.TOOBIT_SECRET_KEY?.trim() ?? toml.secret_key;
2141
+ const hasAuth = Boolean(apiKey && secretKey);
2142
+ const partialAuth = Boolean(apiKey) || Boolean(secretKey);
2143
+ if (partialAuth && !hasAuth) {
2144
+ throw new ConfigError(
2145
+ "Partial API credentials detected.",
2146
+ "Set TOOBIT_API_KEY and TOOBIT_SECRET_KEY together (env vars or config.toml profile)."
2147
+ );
2148
+ }
2149
+ const rawBaseUrl = process.env.TOOBIT_API_BASE_URL?.trim() ?? toml.base_url ?? TOOBIT_API_BASE_URL;
2150
+ if (!rawBaseUrl.startsWith("http://") && !rawBaseUrl.startsWith("https://")) {
2151
+ throw new ConfigError(
2152
+ `Invalid base URL "${rawBaseUrl}".`,
2153
+ "TOOBIT_API_BASE_URL must start with http:// or https://"
2154
+ );
2155
+ }
2156
+ const baseUrl = rawBaseUrl.replace(/\/+$/, "");
2157
+ const rawTimeout = process.env.TOOBIT_TIMEOUT_MS ? Number(process.env.TOOBIT_TIMEOUT_MS) : toml.timeout_ms ?? 15e3;
2158
+ if (!Number.isFinite(rawTimeout) || rawTimeout <= 0) {
2159
+ throw new ConfigError(
2160
+ `Invalid timeout value "${rawTimeout}".`,
2161
+ "Set TOOBIT_TIMEOUT_MS as a positive integer in milliseconds."
2162
+ );
2163
+ }
2164
+ return {
2165
+ apiKey,
2166
+ secretKey,
2167
+ hasAuth,
2168
+ baseUrl,
2169
+ timeoutMs: Math.floor(rawTimeout),
2170
+ modules: parseModuleList(cli.modules),
2171
+ readOnly: cli.readOnly,
2172
+ userAgent: cli.userAgent,
2173
+ sourceTag: cli.sourceTag ?? DEFAULT_SOURCE_TAG
2174
+ };
2175
+ }
2176
+ var CLIENT_NAMES = {
2177
+ "claude-desktop": "Claude Desktop",
2178
+ cursor: "Cursor",
2179
+ windsurf: "Windsurf",
2180
+ vscode: "VS Code",
2181
+ "claude-code": "Claude Code CLI"
2182
+ };
2183
+ var SUPPORTED_CLIENTS = Object.keys(CLIENT_NAMES);
11
2184
 
12
2185
  // src/parser.ts
13
2186
  import { parseArgs } from "util";
@@ -345,11 +2518,6 @@ Available: balance, info, balance-flow, sub-accounts, check-api-key, deposit-add
345
2518
 
346
2519
  // src/commands/config.ts
347
2520
  import * as readline from "readline";
348
- import {
349
- configFilePath,
350
- readFullConfig,
351
- writeFullConfig
352
- } from "@toobit_agent/agent-toobitkit-core";
353
2521
  function prompt(question) {
354
2522
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
355
2523
  return new Promise((resolve) => {
@@ -429,7 +2597,7 @@ Commands:
429
2597
  config Configuration management (init, show, list)
430
2598
 
431
2599
  Global Options:
432
- --profile <name> Profile from ${configFilePath2()}
2600
+ --profile <name> Profile from ${configFilePath()}
433
2601
  --json Output raw JSON
434
2602
  --read-only Disable write operations
435
2603
  --help Show this help