@nuno1026/bithumb-cli 0.1.0
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/account-QQC3UZDP.js +110 -0
- package/dist/account-QQC3UZDP.js.map +1 -0
- package/dist/chunk-6NIRYFQU.js +3482 -0
- package/dist/chunk-6NIRYFQU.js.map +1 -0
- package/dist/chunk-FYO6WLZI.js +72 -0
- package/dist/chunk-FYO6WLZI.js.map +1 -0
- package/dist/chunk-XOKGESZ3.js +139 -0
- package/dist/chunk-XOKGESZ3.js.map +1 -0
- package/dist/config-5T2Z7X5D.js +172 -0
- package/dist/config-5T2Z7X5D.js.map +1 -0
- package/dist/deposit-PBP2KDXP.js +169 -0
- package/dist/deposit-PBP2KDXP.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +455 -0
- package/dist/index.js.map +1 -0
- package/dist/market-B32IKWHH.js +304 -0
- package/dist/market-B32IKWHH.js.map +1 -0
- package/dist/trade-ZZU6PXJL.js +233 -0
- package/dist/trade-ZZU6PXJL.js.map +1 -0
- package/dist/twap-U3LDHACU.js +78 -0
- package/dist/twap-U3LDHACU.js.map +1 -0
- package/dist/withdraw-7C657PEC.js +161 -0
- package/dist/withdraw-7C657PEC.js.map +1 -0
- package/package.json +25 -0
|
@@ -0,0 +1,3482 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ../core/dist/index.js
|
|
4
|
+
import { createHash, createHmac, randomUUID } from "crypto";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import os from "os";
|
|
8
|
+
import { existsSync } from "fs";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
import { homedir } from "os";
|
|
11
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync as existsSync2 } from "fs";
|
|
12
|
+
import { join as join2, dirname } from "path";
|
|
13
|
+
import { homedir as homedir2 } from "os";
|
|
14
|
+
|
|
15
|
+
// ../../node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/error.js
|
|
16
|
+
function getLineColFromPtr(string, ptr) {
|
|
17
|
+
let lines = string.slice(0, ptr).split(/\r\n|\n|\r/g);
|
|
18
|
+
return [lines.length, lines.pop().length + 1];
|
|
19
|
+
}
|
|
20
|
+
function makeCodeBlock(string, line, column) {
|
|
21
|
+
let lines = string.split(/\r\n|\n|\r/g);
|
|
22
|
+
let codeblock = "";
|
|
23
|
+
let numberLen = (Math.log10(line + 1) | 0) + 1;
|
|
24
|
+
for (let i = line - 1; i <= line + 1; i++) {
|
|
25
|
+
let l = lines[i - 1];
|
|
26
|
+
if (!l)
|
|
27
|
+
continue;
|
|
28
|
+
codeblock += i.toString().padEnd(numberLen, " ");
|
|
29
|
+
codeblock += ": ";
|
|
30
|
+
codeblock += l;
|
|
31
|
+
codeblock += "\n";
|
|
32
|
+
if (i === line) {
|
|
33
|
+
codeblock += " ".repeat(numberLen + column + 2);
|
|
34
|
+
codeblock += "^\n";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return codeblock;
|
|
38
|
+
}
|
|
39
|
+
var TomlError = class extends Error {
|
|
40
|
+
line;
|
|
41
|
+
column;
|
|
42
|
+
codeblock;
|
|
43
|
+
constructor(message, options) {
|
|
44
|
+
const [line, column] = getLineColFromPtr(options.toml, options.ptr);
|
|
45
|
+
const codeblock = makeCodeBlock(options.toml, line, column);
|
|
46
|
+
super(`Invalid TOML document: ${message}
|
|
47
|
+
|
|
48
|
+
${codeblock}`, options);
|
|
49
|
+
this.line = line;
|
|
50
|
+
this.column = column;
|
|
51
|
+
this.codeblock = codeblock;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// ../../node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/util.js
|
|
56
|
+
function isEscaped(str, ptr) {
|
|
57
|
+
let i = 0;
|
|
58
|
+
while (str[ptr - ++i] === "\\")
|
|
59
|
+
;
|
|
60
|
+
return --i && i % 2;
|
|
61
|
+
}
|
|
62
|
+
function indexOfNewline(str, start = 0, end = str.length) {
|
|
63
|
+
let idx = str.indexOf("\n", start);
|
|
64
|
+
if (str[idx - 1] === "\r")
|
|
65
|
+
idx--;
|
|
66
|
+
return idx <= end ? idx : -1;
|
|
67
|
+
}
|
|
68
|
+
function skipComment(str, ptr) {
|
|
69
|
+
for (let i = ptr; i < str.length; i++) {
|
|
70
|
+
let c = str[i];
|
|
71
|
+
if (c === "\n")
|
|
72
|
+
return i;
|
|
73
|
+
if (c === "\r" && str[i + 1] === "\n")
|
|
74
|
+
return i + 1;
|
|
75
|
+
if (c < " " && c !== " " || c === "\x7F") {
|
|
76
|
+
throw new TomlError("control characters are not allowed in comments", {
|
|
77
|
+
toml: str,
|
|
78
|
+
ptr
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return str.length;
|
|
83
|
+
}
|
|
84
|
+
function skipVoid(str, ptr, banNewLines, banComments) {
|
|
85
|
+
let c;
|
|
86
|
+
while (1) {
|
|
87
|
+
while ((c = str[ptr]) === " " || c === " " || !banNewLines && (c === "\n" || c === "\r" && str[ptr + 1] === "\n"))
|
|
88
|
+
ptr++;
|
|
89
|
+
if (banComments || c !== "#")
|
|
90
|
+
break;
|
|
91
|
+
ptr = skipComment(str, ptr);
|
|
92
|
+
}
|
|
93
|
+
return ptr;
|
|
94
|
+
}
|
|
95
|
+
function skipUntil(str, ptr, sep, end, banNewLines = false) {
|
|
96
|
+
if (!end) {
|
|
97
|
+
ptr = indexOfNewline(str, ptr);
|
|
98
|
+
return ptr < 0 ? str.length : ptr;
|
|
99
|
+
}
|
|
100
|
+
for (let i = ptr; i < str.length; i++) {
|
|
101
|
+
let c = str[i];
|
|
102
|
+
if (c === "#") {
|
|
103
|
+
i = indexOfNewline(str, i);
|
|
104
|
+
} else if (c === sep) {
|
|
105
|
+
return i + 1;
|
|
106
|
+
} else if (c === end || banNewLines && (c === "\n" || c === "\r" && str[i + 1] === "\n")) {
|
|
107
|
+
return i;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
throw new TomlError("cannot find end of structure", {
|
|
111
|
+
toml: str,
|
|
112
|
+
ptr
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function getStringEnd(str, seek) {
|
|
116
|
+
let first = str[seek];
|
|
117
|
+
let target = first === str[seek + 1] && str[seek + 1] === str[seek + 2] ? str.slice(seek, seek + 3) : first;
|
|
118
|
+
seek += target.length - 1;
|
|
119
|
+
do
|
|
120
|
+
seek = str.indexOf(target, ++seek);
|
|
121
|
+
while (seek > -1 && first !== "'" && isEscaped(str, seek));
|
|
122
|
+
if (seek > -1) {
|
|
123
|
+
seek += target.length;
|
|
124
|
+
if (target.length > 1) {
|
|
125
|
+
if (str[seek] === first)
|
|
126
|
+
seek++;
|
|
127
|
+
if (str[seek] === first)
|
|
128
|
+
seek++;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return seek;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ../../node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/date.js
|
|
135
|
+
var DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[T ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|[-+]\d{2}:\d{2})?$/i;
|
|
136
|
+
var TomlDate = class _TomlDate extends Date {
|
|
137
|
+
#hasDate = false;
|
|
138
|
+
#hasTime = false;
|
|
139
|
+
#offset = null;
|
|
140
|
+
constructor(date) {
|
|
141
|
+
let hasDate = true;
|
|
142
|
+
let hasTime = true;
|
|
143
|
+
let offset = "Z";
|
|
144
|
+
if (typeof date === "string") {
|
|
145
|
+
let match = date.match(DATE_TIME_RE);
|
|
146
|
+
if (match) {
|
|
147
|
+
if (!match[1]) {
|
|
148
|
+
hasDate = false;
|
|
149
|
+
date = `0000-01-01T${date}`;
|
|
150
|
+
}
|
|
151
|
+
hasTime = !!match[2];
|
|
152
|
+
hasTime && date[10] === " " && (date = date.replace(" ", "T"));
|
|
153
|
+
if (match[2] && +match[2] > 23) {
|
|
154
|
+
date = "";
|
|
155
|
+
} else {
|
|
156
|
+
offset = match[3] || null;
|
|
157
|
+
date = date.toUpperCase();
|
|
158
|
+
if (!offset && hasTime)
|
|
159
|
+
date += "Z";
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
date = "";
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
super(date);
|
|
166
|
+
if (!isNaN(this.getTime())) {
|
|
167
|
+
this.#hasDate = hasDate;
|
|
168
|
+
this.#hasTime = hasTime;
|
|
169
|
+
this.#offset = offset;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
isDateTime() {
|
|
173
|
+
return this.#hasDate && this.#hasTime;
|
|
174
|
+
}
|
|
175
|
+
isLocal() {
|
|
176
|
+
return !this.#hasDate || !this.#hasTime || !this.#offset;
|
|
177
|
+
}
|
|
178
|
+
isDate() {
|
|
179
|
+
return this.#hasDate && !this.#hasTime;
|
|
180
|
+
}
|
|
181
|
+
isTime() {
|
|
182
|
+
return this.#hasTime && !this.#hasDate;
|
|
183
|
+
}
|
|
184
|
+
isValid() {
|
|
185
|
+
return this.#hasDate || this.#hasTime;
|
|
186
|
+
}
|
|
187
|
+
toISOString() {
|
|
188
|
+
let iso = super.toISOString();
|
|
189
|
+
if (this.isDate())
|
|
190
|
+
return iso.slice(0, 10);
|
|
191
|
+
if (this.isTime())
|
|
192
|
+
return iso.slice(11, 23);
|
|
193
|
+
if (this.#offset === null)
|
|
194
|
+
return iso.slice(0, -1);
|
|
195
|
+
if (this.#offset === "Z")
|
|
196
|
+
return iso;
|
|
197
|
+
let offset = +this.#offset.slice(1, 3) * 60 + +this.#offset.slice(4, 6);
|
|
198
|
+
offset = this.#offset[0] === "-" ? offset : -offset;
|
|
199
|
+
let offsetDate = new Date(this.getTime() - offset * 6e4);
|
|
200
|
+
return offsetDate.toISOString().slice(0, -1) + this.#offset;
|
|
201
|
+
}
|
|
202
|
+
static wrapAsOffsetDateTime(jsDate, offset = "Z") {
|
|
203
|
+
let date = new _TomlDate(jsDate);
|
|
204
|
+
date.#offset = offset;
|
|
205
|
+
return date;
|
|
206
|
+
}
|
|
207
|
+
static wrapAsLocalDateTime(jsDate) {
|
|
208
|
+
let date = new _TomlDate(jsDate);
|
|
209
|
+
date.#offset = null;
|
|
210
|
+
return date;
|
|
211
|
+
}
|
|
212
|
+
static wrapAsLocalDate(jsDate) {
|
|
213
|
+
let date = new _TomlDate(jsDate);
|
|
214
|
+
date.#hasTime = false;
|
|
215
|
+
date.#offset = null;
|
|
216
|
+
return date;
|
|
217
|
+
}
|
|
218
|
+
static wrapAsLocalTime(jsDate) {
|
|
219
|
+
let date = new _TomlDate(jsDate);
|
|
220
|
+
date.#hasDate = false;
|
|
221
|
+
date.#offset = null;
|
|
222
|
+
return date;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// ../../node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/primitive.js
|
|
227
|
+
var INT_REGEX = /^((0x[0-9a-fA-F](_?[0-9a-fA-F])*)|(([+-]|0[ob])?\d(_?\d)*))$/;
|
|
228
|
+
var FLOAT_REGEX = /^[+-]?\d(_?\d)*(\.\d(_?\d)*)?([eE][+-]?\d(_?\d)*)?$/;
|
|
229
|
+
var LEADING_ZERO = /^[+-]?0[0-9_]/;
|
|
230
|
+
var ESCAPE_REGEX = /^[0-9a-f]{2,8}$/i;
|
|
231
|
+
var ESC_MAP = {
|
|
232
|
+
b: "\b",
|
|
233
|
+
t: " ",
|
|
234
|
+
n: "\n",
|
|
235
|
+
f: "\f",
|
|
236
|
+
r: "\r",
|
|
237
|
+
e: "\x1B",
|
|
238
|
+
'"': '"',
|
|
239
|
+
"\\": "\\"
|
|
240
|
+
};
|
|
241
|
+
function parseString(str, ptr = 0, endPtr = str.length) {
|
|
242
|
+
let isLiteral = str[ptr] === "'";
|
|
243
|
+
let isMultiline = str[ptr++] === str[ptr] && str[ptr] === str[ptr + 1];
|
|
244
|
+
if (isMultiline) {
|
|
245
|
+
endPtr -= 2;
|
|
246
|
+
if (str[ptr += 2] === "\r")
|
|
247
|
+
ptr++;
|
|
248
|
+
if (str[ptr] === "\n")
|
|
249
|
+
ptr++;
|
|
250
|
+
}
|
|
251
|
+
let tmp = 0;
|
|
252
|
+
let isEscape;
|
|
253
|
+
let parsed = "";
|
|
254
|
+
let sliceStart = ptr;
|
|
255
|
+
while (ptr < endPtr - 1) {
|
|
256
|
+
let c = str[ptr++];
|
|
257
|
+
if (c === "\n" || c === "\r" && str[ptr] === "\n") {
|
|
258
|
+
if (!isMultiline) {
|
|
259
|
+
throw new TomlError("newlines are not allowed in strings", {
|
|
260
|
+
toml: str,
|
|
261
|
+
ptr: ptr - 1
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
} else if (c < " " && c !== " " || c === "\x7F") {
|
|
265
|
+
throw new TomlError("control characters are not allowed in strings", {
|
|
266
|
+
toml: str,
|
|
267
|
+
ptr: ptr - 1
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
if (isEscape) {
|
|
271
|
+
isEscape = false;
|
|
272
|
+
if (c === "x" || c === "u" || c === "U") {
|
|
273
|
+
let code = str.slice(ptr, ptr += c === "x" ? 2 : c === "u" ? 4 : 8);
|
|
274
|
+
if (!ESCAPE_REGEX.test(code)) {
|
|
275
|
+
throw new TomlError("invalid unicode escape", {
|
|
276
|
+
toml: str,
|
|
277
|
+
ptr: tmp
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
parsed += String.fromCodePoint(parseInt(code, 16));
|
|
282
|
+
} catch {
|
|
283
|
+
throw new TomlError("invalid unicode escape", {
|
|
284
|
+
toml: str,
|
|
285
|
+
ptr: tmp
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
} else if (isMultiline && (c === "\n" || c === " " || c === " " || c === "\r")) {
|
|
289
|
+
ptr = skipVoid(str, ptr - 1, true);
|
|
290
|
+
if (str[ptr] !== "\n" && str[ptr] !== "\r") {
|
|
291
|
+
throw new TomlError("invalid escape: only line-ending whitespace may be escaped", {
|
|
292
|
+
toml: str,
|
|
293
|
+
ptr: tmp
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
ptr = skipVoid(str, ptr);
|
|
297
|
+
} else if (c in ESC_MAP) {
|
|
298
|
+
parsed += ESC_MAP[c];
|
|
299
|
+
} else {
|
|
300
|
+
throw new TomlError("unrecognized escape sequence", {
|
|
301
|
+
toml: str,
|
|
302
|
+
ptr: tmp
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
sliceStart = ptr;
|
|
306
|
+
} else if (!isLiteral && c === "\\") {
|
|
307
|
+
tmp = ptr - 1;
|
|
308
|
+
isEscape = true;
|
|
309
|
+
parsed += str.slice(sliceStart, tmp);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return parsed + str.slice(sliceStart, endPtr - 1);
|
|
313
|
+
}
|
|
314
|
+
function parseValue(value, toml, ptr, integersAsBigInt) {
|
|
315
|
+
if (value === "true")
|
|
316
|
+
return true;
|
|
317
|
+
if (value === "false")
|
|
318
|
+
return false;
|
|
319
|
+
if (value === "-inf")
|
|
320
|
+
return -Infinity;
|
|
321
|
+
if (value === "inf" || value === "+inf")
|
|
322
|
+
return Infinity;
|
|
323
|
+
if (value === "nan" || value === "+nan" || value === "-nan")
|
|
324
|
+
return NaN;
|
|
325
|
+
if (value === "-0")
|
|
326
|
+
return integersAsBigInt ? 0n : 0;
|
|
327
|
+
let isInt = INT_REGEX.test(value);
|
|
328
|
+
if (isInt || FLOAT_REGEX.test(value)) {
|
|
329
|
+
if (LEADING_ZERO.test(value)) {
|
|
330
|
+
throw new TomlError("leading zeroes are not allowed", {
|
|
331
|
+
toml,
|
|
332
|
+
ptr
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
value = value.replace(/_/g, "");
|
|
336
|
+
let numeric = +value;
|
|
337
|
+
if (isNaN(numeric)) {
|
|
338
|
+
throw new TomlError("invalid number", {
|
|
339
|
+
toml,
|
|
340
|
+
ptr
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
if (isInt) {
|
|
344
|
+
if ((isInt = !Number.isSafeInteger(numeric)) && !integersAsBigInt) {
|
|
345
|
+
throw new TomlError("integer value cannot be represented losslessly", {
|
|
346
|
+
toml,
|
|
347
|
+
ptr
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
if (isInt || integersAsBigInt === true)
|
|
351
|
+
numeric = BigInt(value);
|
|
352
|
+
}
|
|
353
|
+
return numeric;
|
|
354
|
+
}
|
|
355
|
+
const date = new TomlDate(value);
|
|
356
|
+
if (!date.isValid()) {
|
|
357
|
+
throw new TomlError("invalid value", {
|
|
358
|
+
toml,
|
|
359
|
+
ptr
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
return date;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ../../node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/extract.js
|
|
366
|
+
function sliceAndTrimEndOf(str, startPtr, endPtr) {
|
|
367
|
+
let value = str.slice(startPtr, endPtr);
|
|
368
|
+
let commentIdx = value.indexOf("#");
|
|
369
|
+
if (commentIdx > -1) {
|
|
370
|
+
skipComment(str, commentIdx);
|
|
371
|
+
value = value.slice(0, commentIdx);
|
|
372
|
+
}
|
|
373
|
+
return [value.trimEnd(), commentIdx];
|
|
374
|
+
}
|
|
375
|
+
function extractValue(str, ptr, end, depth, integersAsBigInt) {
|
|
376
|
+
if (depth === 0) {
|
|
377
|
+
throw new TomlError("document contains excessively nested structures. aborting.", {
|
|
378
|
+
toml: str,
|
|
379
|
+
ptr
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
let c = str[ptr];
|
|
383
|
+
if (c === "[" || c === "{") {
|
|
384
|
+
let [value, endPtr2] = c === "[" ? parseArray(str, ptr, depth, integersAsBigInt) : parseInlineTable(str, ptr, depth, integersAsBigInt);
|
|
385
|
+
if (end) {
|
|
386
|
+
endPtr2 = skipVoid(str, endPtr2);
|
|
387
|
+
if (str[endPtr2] === ",")
|
|
388
|
+
endPtr2++;
|
|
389
|
+
else if (str[endPtr2] !== end) {
|
|
390
|
+
throw new TomlError("expected comma or end of structure", {
|
|
391
|
+
toml: str,
|
|
392
|
+
ptr: endPtr2
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return [value, endPtr2];
|
|
397
|
+
}
|
|
398
|
+
let endPtr;
|
|
399
|
+
if (c === '"' || c === "'") {
|
|
400
|
+
endPtr = getStringEnd(str, ptr);
|
|
401
|
+
let parsed = parseString(str, ptr, endPtr);
|
|
402
|
+
if (end) {
|
|
403
|
+
endPtr = skipVoid(str, endPtr);
|
|
404
|
+
if (str[endPtr] && str[endPtr] !== "," && str[endPtr] !== end && str[endPtr] !== "\n" && str[endPtr] !== "\r") {
|
|
405
|
+
throw new TomlError("unexpected character encountered", {
|
|
406
|
+
toml: str,
|
|
407
|
+
ptr: endPtr
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
endPtr += +(str[endPtr] === ",");
|
|
411
|
+
}
|
|
412
|
+
return [parsed, endPtr];
|
|
413
|
+
}
|
|
414
|
+
endPtr = skipUntil(str, ptr, ",", end);
|
|
415
|
+
let slice = sliceAndTrimEndOf(str, ptr, endPtr - +(str[endPtr - 1] === ","));
|
|
416
|
+
if (!slice[0]) {
|
|
417
|
+
throw new TomlError("incomplete key-value declaration: no value specified", {
|
|
418
|
+
toml: str,
|
|
419
|
+
ptr
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
if (end && slice[1] > -1) {
|
|
423
|
+
endPtr = skipVoid(str, ptr + slice[1]);
|
|
424
|
+
endPtr += +(str[endPtr] === ",");
|
|
425
|
+
}
|
|
426
|
+
return [
|
|
427
|
+
parseValue(slice[0], str, ptr, integersAsBigInt),
|
|
428
|
+
endPtr
|
|
429
|
+
];
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// ../../node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/struct.js
|
|
433
|
+
var KEY_PART_RE = /^[a-zA-Z0-9-_]+[ \t]*$/;
|
|
434
|
+
function parseKey(str, ptr, end = "=") {
|
|
435
|
+
let dot = ptr - 1;
|
|
436
|
+
let parsed = [];
|
|
437
|
+
let endPtr = str.indexOf(end, ptr);
|
|
438
|
+
if (endPtr < 0) {
|
|
439
|
+
throw new TomlError("incomplete key-value: cannot find end of key", {
|
|
440
|
+
toml: str,
|
|
441
|
+
ptr
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
do {
|
|
445
|
+
let c = str[ptr = ++dot];
|
|
446
|
+
if (c !== " " && c !== " ") {
|
|
447
|
+
if (c === '"' || c === "'") {
|
|
448
|
+
if (c === str[ptr + 1] && c === str[ptr + 2]) {
|
|
449
|
+
throw new TomlError("multiline strings are not allowed in keys", {
|
|
450
|
+
toml: str,
|
|
451
|
+
ptr
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
let eos = getStringEnd(str, ptr);
|
|
455
|
+
if (eos < 0) {
|
|
456
|
+
throw new TomlError("unfinished string encountered", {
|
|
457
|
+
toml: str,
|
|
458
|
+
ptr
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
dot = str.indexOf(".", eos);
|
|
462
|
+
let strEnd = str.slice(eos, dot < 0 || dot > endPtr ? endPtr : dot);
|
|
463
|
+
let newLine = indexOfNewline(strEnd);
|
|
464
|
+
if (newLine > -1) {
|
|
465
|
+
throw new TomlError("newlines are not allowed in keys", {
|
|
466
|
+
toml: str,
|
|
467
|
+
ptr: ptr + dot + newLine
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
if (strEnd.trimStart()) {
|
|
471
|
+
throw new TomlError("found extra tokens after the string part", {
|
|
472
|
+
toml: str,
|
|
473
|
+
ptr: eos
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
if (endPtr < eos) {
|
|
477
|
+
endPtr = str.indexOf(end, eos);
|
|
478
|
+
if (endPtr < 0) {
|
|
479
|
+
throw new TomlError("incomplete key-value: cannot find end of key", {
|
|
480
|
+
toml: str,
|
|
481
|
+
ptr
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
parsed.push(parseString(str, ptr, eos));
|
|
486
|
+
} else {
|
|
487
|
+
dot = str.indexOf(".", ptr);
|
|
488
|
+
let part = str.slice(ptr, dot < 0 || dot > endPtr ? endPtr : dot);
|
|
489
|
+
if (!KEY_PART_RE.test(part)) {
|
|
490
|
+
throw new TomlError("only letter, numbers, dashes and underscores are allowed in keys", {
|
|
491
|
+
toml: str,
|
|
492
|
+
ptr
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
parsed.push(part.trimEnd());
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
} while (dot + 1 && dot < endPtr);
|
|
499
|
+
return [parsed, skipVoid(str, endPtr + 1, true, true)];
|
|
500
|
+
}
|
|
501
|
+
function parseInlineTable(str, ptr, depth, integersAsBigInt) {
|
|
502
|
+
let res = {};
|
|
503
|
+
let seen = /* @__PURE__ */ new Set();
|
|
504
|
+
let c;
|
|
505
|
+
ptr++;
|
|
506
|
+
while ((c = str[ptr++]) !== "}" && c) {
|
|
507
|
+
if (c === ",") {
|
|
508
|
+
throw new TomlError("expected value, found comma", {
|
|
509
|
+
toml: str,
|
|
510
|
+
ptr: ptr - 1
|
|
511
|
+
});
|
|
512
|
+
} else if (c === "#")
|
|
513
|
+
ptr = skipComment(str, ptr);
|
|
514
|
+
else if (c !== " " && c !== " " && c !== "\n" && c !== "\r") {
|
|
515
|
+
let k;
|
|
516
|
+
let t = res;
|
|
517
|
+
let hasOwn = false;
|
|
518
|
+
let [key, keyEndPtr] = parseKey(str, ptr - 1);
|
|
519
|
+
for (let i = 0; i < key.length; i++) {
|
|
520
|
+
if (i)
|
|
521
|
+
t = hasOwn ? t[k] : t[k] = {};
|
|
522
|
+
k = key[i];
|
|
523
|
+
if ((hasOwn = Object.hasOwn(t, k)) && (typeof t[k] !== "object" || seen.has(t[k]))) {
|
|
524
|
+
throw new TomlError("trying to redefine an already defined value", {
|
|
525
|
+
toml: str,
|
|
526
|
+
ptr
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
if (!hasOwn && k === "__proto__") {
|
|
530
|
+
Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
if (hasOwn) {
|
|
534
|
+
throw new TomlError("trying to redefine an already defined value", {
|
|
535
|
+
toml: str,
|
|
536
|
+
ptr
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
let [value, valueEndPtr] = extractValue(str, keyEndPtr, "}", depth - 1, integersAsBigInt);
|
|
540
|
+
seen.add(value);
|
|
541
|
+
t[k] = value;
|
|
542
|
+
ptr = valueEndPtr;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (!c) {
|
|
546
|
+
throw new TomlError("unfinished table encountered", {
|
|
547
|
+
toml: str,
|
|
548
|
+
ptr
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
return [res, ptr];
|
|
552
|
+
}
|
|
553
|
+
function parseArray(str, ptr, depth, integersAsBigInt) {
|
|
554
|
+
let res = [];
|
|
555
|
+
let c;
|
|
556
|
+
ptr++;
|
|
557
|
+
while ((c = str[ptr++]) !== "]" && c) {
|
|
558
|
+
if (c === ",") {
|
|
559
|
+
throw new TomlError("expected value, found comma", {
|
|
560
|
+
toml: str,
|
|
561
|
+
ptr: ptr - 1
|
|
562
|
+
});
|
|
563
|
+
} else if (c === "#")
|
|
564
|
+
ptr = skipComment(str, ptr);
|
|
565
|
+
else if (c !== " " && c !== " " && c !== "\n" && c !== "\r") {
|
|
566
|
+
let e = extractValue(str, ptr - 1, "]", depth - 1, integersAsBigInt);
|
|
567
|
+
res.push(e[0]);
|
|
568
|
+
ptr = e[1];
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
if (!c) {
|
|
572
|
+
throw new TomlError("unfinished array encountered", {
|
|
573
|
+
toml: str,
|
|
574
|
+
ptr
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
return [res, ptr];
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// ../../node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/parse.js
|
|
581
|
+
function peekTable(key, table, meta, type) {
|
|
582
|
+
let t = table;
|
|
583
|
+
let m = meta;
|
|
584
|
+
let k;
|
|
585
|
+
let hasOwn = false;
|
|
586
|
+
let state;
|
|
587
|
+
for (let i = 0; i < key.length; i++) {
|
|
588
|
+
if (i) {
|
|
589
|
+
t = hasOwn ? t[k] : t[k] = {};
|
|
590
|
+
m = (state = m[k]).c;
|
|
591
|
+
if (type === 0 && (state.t === 1 || state.t === 2)) {
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
if (state.t === 2) {
|
|
595
|
+
let l = t.length - 1;
|
|
596
|
+
t = t[l];
|
|
597
|
+
m = m[l].c;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
k = key[i];
|
|
601
|
+
if ((hasOwn = Object.hasOwn(t, k)) && m[k]?.t === 0 && m[k]?.d) {
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
if (!hasOwn) {
|
|
605
|
+
if (k === "__proto__") {
|
|
606
|
+
Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
|
|
607
|
+
Object.defineProperty(m, k, { enumerable: true, configurable: true, writable: true });
|
|
608
|
+
}
|
|
609
|
+
m[k] = {
|
|
610
|
+
t: i < key.length - 1 && type === 2 ? 3 : type,
|
|
611
|
+
d: false,
|
|
612
|
+
i: 0,
|
|
613
|
+
c: {}
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
state = m[k];
|
|
618
|
+
if (state.t !== type && !(type === 1 && state.t === 3)) {
|
|
619
|
+
return null;
|
|
620
|
+
}
|
|
621
|
+
if (type === 2) {
|
|
622
|
+
if (!state.d) {
|
|
623
|
+
state.d = true;
|
|
624
|
+
t[k] = [];
|
|
625
|
+
}
|
|
626
|
+
t[k].push(t = {});
|
|
627
|
+
state.c[state.i++] = state = { t: 1, d: false, i: 0, c: {} };
|
|
628
|
+
}
|
|
629
|
+
if (state.d) {
|
|
630
|
+
return null;
|
|
631
|
+
}
|
|
632
|
+
state.d = true;
|
|
633
|
+
if (type === 1) {
|
|
634
|
+
t = hasOwn ? t[k] : t[k] = {};
|
|
635
|
+
} else if (type === 0 && hasOwn) {
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
638
|
+
return [k, t, state.c];
|
|
639
|
+
}
|
|
640
|
+
function parse(toml, { maxDepth = 1e3, integersAsBigInt } = {}) {
|
|
641
|
+
let res = {};
|
|
642
|
+
let meta = {};
|
|
643
|
+
let tbl = res;
|
|
644
|
+
let m = meta;
|
|
645
|
+
for (let ptr = skipVoid(toml, 0); ptr < toml.length; ) {
|
|
646
|
+
if (toml[ptr] === "[") {
|
|
647
|
+
let isTableArray = toml[++ptr] === "[";
|
|
648
|
+
let k = parseKey(toml, ptr += +isTableArray, "]");
|
|
649
|
+
if (isTableArray) {
|
|
650
|
+
if (toml[k[1] - 1] !== "]") {
|
|
651
|
+
throw new TomlError("expected end of table declaration", {
|
|
652
|
+
toml,
|
|
653
|
+
ptr: k[1] - 1
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
k[1]++;
|
|
657
|
+
}
|
|
658
|
+
let p = peekTable(
|
|
659
|
+
k[0],
|
|
660
|
+
res,
|
|
661
|
+
meta,
|
|
662
|
+
isTableArray ? 2 : 1
|
|
663
|
+
/* Type.EXPLICIT */
|
|
664
|
+
);
|
|
665
|
+
if (!p) {
|
|
666
|
+
throw new TomlError("trying to redefine an already defined table or value", {
|
|
667
|
+
toml,
|
|
668
|
+
ptr
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
m = p[2];
|
|
672
|
+
tbl = p[1];
|
|
673
|
+
ptr = k[1];
|
|
674
|
+
} else {
|
|
675
|
+
let k = parseKey(toml, ptr);
|
|
676
|
+
let p = peekTable(
|
|
677
|
+
k[0],
|
|
678
|
+
tbl,
|
|
679
|
+
m,
|
|
680
|
+
0
|
|
681
|
+
/* Type.DOTTED */
|
|
682
|
+
);
|
|
683
|
+
if (!p) {
|
|
684
|
+
throw new TomlError("trying to redefine an already defined table or value", {
|
|
685
|
+
toml,
|
|
686
|
+
ptr
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
let v = extractValue(toml, k[1], void 0, maxDepth, integersAsBigInt);
|
|
690
|
+
p[1][p[0]] = v[0];
|
|
691
|
+
ptr = v[1];
|
|
692
|
+
}
|
|
693
|
+
ptr = skipVoid(toml, ptr, true);
|
|
694
|
+
if (toml[ptr] && toml[ptr] !== "\n" && toml[ptr] !== "\r") {
|
|
695
|
+
throw new TomlError("each key-value declaration must be followed by an end-of-line", {
|
|
696
|
+
toml,
|
|
697
|
+
ptr
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
ptr = skipVoid(toml, ptr);
|
|
701
|
+
}
|
|
702
|
+
return res;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// ../../node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/stringify.js
|
|
706
|
+
var BARE_KEY = /^[a-z0-9-_]+$/i;
|
|
707
|
+
function extendedTypeOf(obj) {
|
|
708
|
+
let type = typeof obj;
|
|
709
|
+
if (type === "object") {
|
|
710
|
+
if (Array.isArray(obj))
|
|
711
|
+
return "array";
|
|
712
|
+
if (obj instanceof Date)
|
|
713
|
+
return "date";
|
|
714
|
+
}
|
|
715
|
+
return type;
|
|
716
|
+
}
|
|
717
|
+
function isArrayOfTables(obj) {
|
|
718
|
+
for (let i = 0; i < obj.length; i++) {
|
|
719
|
+
if (extendedTypeOf(obj[i]) !== "object")
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
return obj.length != 0;
|
|
723
|
+
}
|
|
724
|
+
function formatString(s) {
|
|
725
|
+
return JSON.stringify(s).replace(/\x7f/g, "\\u007f");
|
|
726
|
+
}
|
|
727
|
+
function stringifyValue(val, type, depth, numberAsFloat) {
|
|
728
|
+
if (depth === 0) {
|
|
729
|
+
throw new Error("Could not stringify the object: maximum object depth exceeded");
|
|
730
|
+
}
|
|
731
|
+
if (type === "number") {
|
|
732
|
+
if (isNaN(val))
|
|
733
|
+
return "nan";
|
|
734
|
+
if (val === Infinity)
|
|
735
|
+
return "inf";
|
|
736
|
+
if (val === -Infinity)
|
|
737
|
+
return "-inf";
|
|
738
|
+
if (numberAsFloat && Number.isInteger(val))
|
|
739
|
+
return val.toFixed(1);
|
|
740
|
+
return val.toString();
|
|
741
|
+
}
|
|
742
|
+
if (type === "bigint" || type === "boolean") {
|
|
743
|
+
return val.toString();
|
|
744
|
+
}
|
|
745
|
+
if (type === "string") {
|
|
746
|
+
return formatString(val);
|
|
747
|
+
}
|
|
748
|
+
if (type === "date") {
|
|
749
|
+
if (isNaN(val.getTime())) {
|
|
750
|
+
throw new TypeError("cannot serialize invalid date");
|
|
751
|
+
}
|
|
752
|
+
return val.toISOString();
|
|
753
|
+
}
|
|
754
|
+
if (type === "object") {
|
|
755
|
+
return stringifyInlineTable(val, depth, numberAsFloat);
|
|
756
|
+
}
|
|
757
|
+
if (type === "array") {
|
|
758
|
+
return stringifyArray(val, depth, numberAsFloat);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
function stringifyInlineTable(obj, depth, numberAsFloat) {
|
|
762
|
+
let keys = Object.keys(obj);
|
|
763
|
+
if (keys.length === 0)
|
|
764
|
+
return "{}";
|
|
765
|
+
let res = "{ ";
|
|
766
|
+
for (let i = 0; i < keys.length; i++) {
|
|
767
|
+
let k = keys[i];
|
|
768
|
+
if (i)
|
|
769
|
+
res += ", ";
|
|
770
|
+
res += BARE_KEY.test(k) ? k : formatString(k);
|
|
771
|
+
res += " = ";
|
|
772
|
+
res += stringifyValue(obj[k], extendedTypeOf(obj[k]), depth - 1, numberAsFloat);
|
|
773
|
+
}
|
|
774
|
+
return res + " }";
|
|
775
|
+
}
|
|
776
|
+
function stringifyArray(array, depth, numberAsFloat) {
|
|
777
|
+
if (array.length === 0)
|
|
778
|
+
return "[]";
|
|
779
|
+
let res = "[ ";
|
|
780
|
+
for (let i = 0; i < array.length; i++) {
|
|
781
|
+
if (i)
|
|
782
|
+
res += ", ";
|
|
783
|
+
if (array[i] === null || array[i] === void 0) {
|
|
784
|
+
throw new TypeError("arrays cannot contain null or undefined values");
|
|
785
|
+
}
|
|
786
|
+
res += stringifyValue(array[i], extendedTypeOf(array[i]), depth - 1, numberAsFloat);
|
|
787
|
+
}
|
|
788
|
+
return res + " ]";
|
|
789
|
+
}
|
|
790
|
+
function stringifyArrayTable(array, key, depth, numberAsFloat) {
|
|
791
|
+
if (depth === 0) {
|
|
792
|
+
throw new Error("Could not stringify the object: maximum object depth exceeded");
|
|
793
|
+
}
|
|
794
|
+
let res = "";
|
|
795
|
+
for (let i = 0; i < array.length; i++) {
|
|
796
|
+
res += `${res && "\n"}[[${key}]]
|
|
797
|
+
`;
|
|
798
|
+
res += stringifyTable(0, array[i], key, depth, numberAsFloat);
|
|
799
|
+
}
|
|
800
|
+
return res;
|
|
801
|
+
}
|
|
802
|
+
function stringifyTable(tableKey, obj, prefix, depth, numberAsFloat) {
|
|
803
|
+
if (depth === 0) {
|
|
804
|
+
throw new Error("Could not stringify the object: maximum object depth exceeded");
|
|
805
|
+
}
|
|
806
|
+
let preamble = "";
|
|
807
|
+
let tables = "";
|
|
808
|
+
let keys = Object.keys(obj);
|
|
809
|
+
for (let i = 0; i < keys.length; i++) {
|
|
810
|
+
let k = keys[i];
|
|
811
|
+
if (obj[k] !== null && obj[k] !== void 0) {
|
|
812
|
+
let type = extendedTypeOf(obj[k]);
|
|
813
|
+
if (type === "symbol" || type === "function") {
|
|
814
|
+
throw new TypeError(`cannot serialize values of type '${type}'`);
|
|
815
|
+
}
|
|
816
|
+
let key = BARE_KEY.test(k) ? k : formatString(k);
|
|
817
|
+
if (type === "array" && isArrayOfTables(obj[k])) {
|
|
818
|
+
tables += (tables && "\n") + stringifyArrayTable(obj[k], prefix ? `${prefix}.${key}` : key, depth - 1, numberAsFloat);
|
|
819
|
+
} else if (type === "object") {
|
|
820
|
+
let tblKey = prefix ? `${prefix}.${key}` : key;
|
|
821
|
+
tables += (tables && "\n") + stringifyTable(tblKey, obj[k], tblKey, depth - 1, numberAsFloat);
|
|
822
|
+
} else {
|
|
823
|
+
preamble += key;
|
|
824
|
+
preamble += " = ";
|
|
825
|
+
preamble += stringifyValue(obj[k], type, depth, numberAsFloat);
|
|
826
|
+
preamble += "\n";
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
if (tableKey && (preamble || !tables))
|
|
831
|
+
preamble = preamble ? `[${tableKey}]
|
|
832
|
+
${preamble}` : `[${tableKey}]`;
|
|
833
|
+
return preamble && tables ? `${preamble}
|
|
834
|
+
${tables}` : preamble || tables;
|
|
835
|
+
}
|
|
836
|
+
function stringify(obj, { maxDepth = 1e3, numbersAsFloat = false } = {}) {
|
|
837
|
+
if (extendedTypeOf(obj) !== "object") {
|
|
838
|
+
throw new TypeError("stringify can only be called with an object");
|
|
839
|
+
}
|
|
840
|
+
let str = stringifyTable(0, obj, "", maxDepth, numbersAsFloat);
|
|
841
|
+
if (str[str.length - 1] !== "\n")
|
|
842
|
+
return str + "\n";
|
|
843
|
+
return str;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// ../core/dist/index.js
|
|
847
|
+
import { mkdirSync as mkdirSync2, appendFileSync } from "fs";
|
|
848
|
+
import { homedir as homedir3 } from "os";
|
|
849
|
+
import { join as join3 } from "path";
|
|
850
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
851
|
+
import { homedir as homedir4 } from "os";
|
|
852
|
+
import { join as join4 } from "path";
|
|
853
|
+
import * as fs2 from "fs";
|
|
854
|
+
import * as path2 from "path";
|
|
855
|
+
import * as os2 from "os";
|
|
856
|
+
import { execFileSync } from "child_process";
|
|
857
|
+
function base64url(input) {
|
|
858
|
+
const buf = typeof input === "string" ? Buffer.from(input) : input;
|
|
859
|
+
return buf.toString("base64url");
|
|
860
|
+
}
|
|
861
|
+
function hashQuery(queryString) {
|
|
862
|
+
return createHash("sha512").update(queryString, "utf8").digest("hex");
|
|
863
|
+
}
|
|
864
|
+
function createJwt(payload, secretKey) {
|
|
865
|
+
const header = { alg: "HS256", typ: "JWT" };
|
|
866
|
+
const headerB64 = base64url(JSON.stringify(header));
|
|
867
|
+
const payloadB64 = base64url(JSON.stringify(payload));
|
|
868
|
+
const signature = createHmac("sha256", secretKey).update(`${headerB64}.${payloadB64}`).digest("base64url");
|
|
869
|
+
return `${headerB64}.${payloadB64}.${signature}`;
|
|
870
|
+
}
|
|
871
|
+
function signRequest(accessKey, secretKey, queryString) {
|
|
872
|
+
const payload = {
|
|
873
|
+
access_key: accessKey,
|
|
874
|
+
nonce: randomUUID(),
|
|
875
|
+
timestamp: Date.now()
|
|
876
|
+
};
|
|
877
|
+
if (queryString && queryString.length > 0) {
|
|
878
|
+
payload.query_hash = hashQuery(queryString);
|
|
879
|
+
payload.query_hash_alg = "SHA512";
|
|
880
|
+
}
|
|
881
|
+
return createJwt(payload, secretKey);
|
|
882
|
+
}
|
|
883
|
+
var BithumbMcpError = class extends Error {
|
|
884
|
+
type;
|
|
885
|
+
code;
|
|
886
|
+
suggestion;
|
|
887
|
+
endpoint;
|
|
888
|
+
constructor(type, message, options) {
|
|
889
|
+
super(message, options?.cause ? { cause: options.cause } : void 0);
|
|
890
|
+
this.name = type;
|
|
891
|
+
this.type = type;
|
|
892
|
+
this.code = options?.code;
|
|
893
|
+
this.suggestion = options?.suggestion;
|
|
894
|
+
this.endpoint = options?.endpoint;
|
|
895
|
+
}
|
|
896
|
+
};
|
|
897
|
+
var ConfigError = class extends BithumbMcpError {
|
|
898
|
+
constructor(message, suggestion) {
|
|
899
|
+
super("ConfigError", message, { suggestion });
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
var ValidationError = class extends BithumbMcpError {
|
|
903
|
+
constructor(message, suggestion) {
|
|
904
|
+
super("ValidationError", message, { suggestion });
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
var RateLimitError = class extends BithumbMcpError {
|
|
908
|
+
constructor(message, suggestion, endpoint) {
|
|
909
|
+
super("RateLimitError", message, { suggestion, endpoint });
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
var AuthenticationError = class extends BithumbMcpError {
|
|
913
|
+
constructor(message, suggestion, endpoint) {
|
|
914
|
+
super("AuthenticationError", message, { suggestion, endpoint });
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
var BithumbApiError = class extends BithumbMcpError {
|
|
918
|
+
constructor(message, options) {
|
|
919
|
+
super("BithumbApiError", message, options);
|
|
920
|
+
}
|
|
921
|
+
};
|
|
922
|
+
var NetworkError = class extends BithumbMcpError {
|
|
923
|
+
constructor(message, endpoint, cause) {
|
|
924
|
+
super("NetworkError", message, {
|
|
925
|
+
endpoint,
|
|
926
|
+
cause,
|
|
927
|
+
suggestion: "Check network connectivity and try again."
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
var RateLimiter = class {
|
|
932
|
+
buckets = /* @__PURE__ */ new Map();
|
|
933
|
+
cleanupMs;
|
|
934
|
+
verbose;
|
|
935
|
+
lastCleanup;
|
|
936
|
+
constructor(cleanupMs = 3e4, verbose = false) {
|
|
937
|
+
this.cleanupMs = cleanupMs;
|
|
938
|
+
this.verbose = verbose;
|
|
939
|
+
this.lastCleanup = Date.now();
|
|
940
|
+
}
|
|
941
|
+
async consume(config, amount = 1) {
|
|
942
|
+
this.maybeCleanup();
|
|
943
|
+
const bucket = this.getBucket(config);
|
|
944
|
+
this.refill(bucket);
|
|
945
|
+
if (bucket.tokens >= amount) {
|
|
946
|
+
bucket.tokens -= amount;
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
const deficit = amount - bucket.tokens;
|
|
950
|
+
const waitMs = Math.ceil(deficit / bucket.refillPerSecond * 1e3);
|
|
951
|
+
if (waitMs > 1e4) {
|
|
952
|
+
throw new RateLimitError(
|
|
953
|
+
`Rate limit exceeded for "${config.key}". Need ${deficit.toFixed(1)} tokens, wait ~${waitMs}ms.`,
|
|
954
|
+
"Reduce request frequency or wait."
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
if (this.verbose) {
|
|
958
|
+
process.stderr.write(
|
|
959
|
+
`[rate-limiter] ${config.key}: waiting ${waitMs}ms for ${deficit.toFixed(1)} tokens
|
|
960
|
+
`
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
964
|
+
this.refill(bucket);
|
|
965
|
+
if (bucket.tokens >= amount) {
|
|
966
|
+
bucket.tokens -= amount;
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
throw new RateLimitError(
|
|
970
|
+
`Rate limit exceeded for "${config.key}" after waiting.`,
|
|
971
|
+
"Reduce request frequency."
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
getBucket(config) {
|
|
975
|
+
let bucket = this.buckets.get(config.key);
|
|
976
|
+
if (!bucket) {
|
|
977
|
+
bucket = {
|
|
978
|
+
tokens: config.capacity,
|
|
979
|
+
capacity: config.capacity,
|
|
980
|
+
refillPerSecond: config.refillPerSecond,
|
|
981
|
+
lastRefill: Date.now()
|
|
982
|
+
};
|
|
983
|
+
this.buckets.set(config.key, bucket);
|
|
984
|
+
}
|
|
985
|
+
return bucket;
|
|
986
|
+
}
|
|
987
|
+
refill(bucket) {
|
|
988
|
+
const now = Date.now();
|
|
989
|
+
const elapsed = (now - bucket.lastRefill) / 1e3;
|
|
990
|
+
if (elapsed <= 0) return;
|
|
991
|
+
bucket.tokens = Math.min(
|
|
992
|
+
bucket.capacity,
|
|
993
|
+
bucket.tokens + elapsed * bucket.refillPerSecond
|
|
994
|
+
);
|
|
995
|
+
bucket.lastRefill = now;
|
|
996
|
+
}
|
|
997
|
+
maybeCleanup() {
|
|
998
|
+
const now = Date.now();
|
|
999
|
+
if (now - this.lastCleanup < this.cleanupMs) return;
|
|
1000
|
+
this.lastCleanup = now;
|
|
1001
|
+
for (const [key, bucket] of this.buckets) {
|
|
1002
|
+
const idle = (now - bucket.lastRefill) / 1e3;
|
|
1003
|
+
if (idle > 60 && bucket.tokens >= bucket.capacity) {
|
|
1004
|
+
this.buckets.delete(key);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
function isDefined(value) {
|
|
1010
|
+
return value !== void 0 && value !== null;
|
|
1011
|
+
}
|
|
1012
|
+
function buildQueryString(query) {
|
|
1013
|
+
if (!query) return "";
|
|
1014
|
+
const entries = Object.entries(query).filter(([, v]) => isDefined(v));
|
|
1015
|
+
if (entries.length === 0) return "";
|
|
1016
|
+
const parts = [];
|
|
1017
|
+
for (const [key, value] of entries) {
|
|
1018
|
+
if (Array.isArray(value)) {
|
|
1019
|
+
for (let i = 0; i < value.length; i++) {
|
|
1020
|
+
const item = value[i];
|
|
1021
|
+
if (item && typeof item === "object" && !Array.isArray(item)) {
|
|
1022
|
+
for (const [subKey, subVal] of Object.entries(item)) {
|
|
1023
|
+
parts.push(
|
|
1024
|
+
`${encodeURIComponent(key)}[${i}][${encodeURIComponent(subKey)}]=${encodeURIComponent(String(subVal))}`
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
} else {
|
|
1028
|
+
parts.push(
|
|
1029
|
+
`${encodeURIComponent(key)}[]=${encodeURIComponent(String(item))}`
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
} else {
|
|
1034
|
+
parts.push(
|
|
1035
|
+
`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
return parts.join("&");
|
|
1040
|
+
}
|
|
1041
|
+
function buildHashString(query, body) {
|
|
1042
|
+
const source = body ?? query;
|
|
1043
|
+
if (!source) return "";
|
|
1044
|
+
const entries = Object.entries(source).filter(([, v]) => isDefined(v));
|
|
1045
|
+
if (entries.length === 0) return "";
|
|
1046
|
+
const parts = [];
|
|
1047
|
+
for (const [key, value] of entries) {
|
|
1048
|
+
if (Array.isArray(value)) {
|
|
1049
|
+
for (let i = 0; i < value.length; i++) {
|
|
1050
|
+
const item = value[i];
|
|
1051
|
+
if (item && typeof item === "object" && !Array.isArray(item)) {
|
|
1052
|
+
for (const [subKey, subVal] of Object.entries(item)) {
|
|
1053
|
+
parts.push(`${key}[${i}][${subKey}]=${String(subVal)}`);
|
|
1054
|
+
}
|
|
1055
|
+
} else {
|
|
1056
|
+
parts.push(`${key}[]=${String(item)}`);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
} else {
|
|
1060
|
+
parts.push(`${key}=${String(value)}`);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
return parts.join("&");
|
|
1064
|
+
}
|
|
1065
|
+
function vlog(message) {
|
|
1066
|
+
process.stderr.write(`[verbose] ${message}
|
|
1067
|
+
`);
|
|
1068
|
+
}
|
|
1069
|
+
var BithumbRestClient = class {
|
|
1070
|
+
config;
|
|
1071
|
+
rateLimiter;
|
|
1072
|
+
constructor(config) {
|
|
1073
|
+
this.config = config;
|
|
1074
|
+
this.rateLimiter = new RateLimiter(3e4, config.verbose);
|
|
1075
|
+
}
|
|
1076
|
+
async publicGet(path3, query, rateLimit) {
|
|
1077
|
+
return this.request({
|
|
1078
|
+
method: "GET",
|
|
1079
|
+
path: path3,
|
|
1080
|
+
auth: "public",
|
|
1081
|
+
query,
|
|
1082
|
+
rateLimit
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
async privateGet(path3, query, rateLimit) {
|
|
1086
|
+
return this.request({
|
|
1087
|
+
method: "GET",
|
|
1088
|
+
path: path3,
|
|
1089
|
+
auth: "private",
|
|
1090
|
+
query,
|
|
1091
|
+
rateLimit
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
async privatePost(path3, body, rateLimit) {
|
|
1095
|
+
return this.request({
|
|
1096
|
+
method: "POST",
|
|
1097
|
+
path: path3,
|
|
1098
|
+
auth: "private",
|
|
1099
|
+
body,
|
|
1100
|
+
rateLimit
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
async privateDelete(path3, query, rateLimit) {
|
|
1104
|
+
return this.request({
|
|
1105
|
+
method: "DELETE",
|
|
1106
|
+
path: path3,
|
|
1107
|
+
auth: "private",
|
|
1108
|
+
query,
|
|
1109
|
+
rateLimit
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
// ─── Core request ────────────────────────────────────────────────
|
|
1113
|
+
async request(reqConfig) {
|
|
1114
|
+
const queryString = buildQueryString(reqConfig.query);
|
|
1115
|
+
const requestPath = queryString.length > 0 ? `${reqConfig.path}?${queryString}` : reqConfig.path;
|
|
1116
|
+
const url = `${this.config.baseUrl}${requestPath}`;
|
|
1117
|
+
if (this.config.verbose) vlog(`\u2192 ${reqConfig.method} ${url}`);
|
|
1118
|
+
if (reqConfig.rateLimit) await this.rateLimiter.consume(reqConfig.rateLimit);
|
|
1119
|
+
const headers = {
|
|
1120
|
+
"Content-Type": "application/json",
|
|
1121
|
+
Accept: "application/json"
|
|
1122
|
+
};
|
|
1123
|
+
if (reqConfig.auth === "private") {
|
|
1124
|
+
if (!this.config.hasAuth || !this.config.accessKey || !this.config.secretKey) {
|
|
1125
|
+
throw new ConfigError(
|
|
1126
|
+
"Private endpoint requires API credentials.",
|
|
1127
|
+
"Set BITHUMB_ACCESS_KEY and BITHUMB_SECRET_KEY."
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
const hashStr = buildHashString(reqConfig.query, reqConfig.body);
|
|
1131
|
+
const token = signRequest(
|
|
1132
|
+
this.config.accessKey,
|
|
1133
|
+
this.config.secretKey,
|
|
1134
|
+
hashStr
|
|
1135
|
+
);
|
|
1136
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
1137
|
+
}
|
|
1138
|
+
const t0 = Date.now();
|
|
1139
|
+
let response;
|
|
1140
|
+
try {
|
|
1141
|
+
const fetchOptions = {
|
|
1142
|
+
method: reqConfig.method,
|
|
1143
|
+
headers,
|
|
1144
|
+
signal: AbortSignal.timeout(this.config.timeoutMs)
|
|
1145
|
+
};
|
|
1146
|
+
if (reqConfig.body && reqConfig.method === "POST") {
|
|
1147
|
+
fetchOptions.body = JSON.stringify(reqConfig.body);
|
|
1148
|
+
}
|
|
1149
|
+
response = await fetch(url, fetchOptions);
|
|
1150
|
+
} catch (error) {
|
|
1151
|
+
if (this.config.verbose) {
|
|
1152
|
+
vlog(`\u2717 NetworkError after ${Date.now() - t0}ms`);
|
|
1153
|
+
}
|
|
1154
|
+
throw new NetworkError(
|
|
1155
|
+
`Failed to call ${reqConfig.method} ${reqConfig.path}.`,
|
|
1156
|
+
`${reqConfig.method} ${reqConfig.path}`,
|
|
1157
|
+
error
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
const rawText = await response.text();
|
|
1161
|
+
const elapsed = Date.now() - t0;
|
|
1162
|
+
if (this.config.verbose) {
|
|
1163
|
+
vlog(`\u2190 ${response.status} | ${rawText.length}B | ${elapsed}ms`);
|
|
1164
|
+
}
|
|
1165
|
+
let parsed;
|
|
1166
|
+
try {
|
|
1167
|
+
parsed = rawText ? JSON.parse(rawText) : null;
|
|
1168
|
+
} catch {
|
|
1169
|
+
throw new NetworkError(
|
|
1170
|
+
`Non-JSON response from ${reqConfig.method} ${reqConfig.path}.`,
|
|
1171
|
+
`${reqConfig.method} ${reqConfig.path}`
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
if (parsed && typeof parsed === "object" && "error" in parsed) {
|
|
1175
|
+
const errResp = parsed;
|
|
1176
|
+
const errName = errResp.error?.name ?? "unknown";
|
|
1177
|
+
const errMsg = errResp.error?.message ?? "Unknown error";
|
|
1178
|
+
const endpoint = `${reqConfig.method} ${reqConfig.path}`;
|
|
1179
|
+
if (response.status === 401 || errName === "unauthorized") {
|
|
1180
|
+
throw new AuthenticationError(
|
|
1181
|
+
errMsg,
|
|
1182
|
+
"Check BITHUMB_ACCESS_KEY and BITHUMB_SECRET_KEY.",
|
|
1183
|
+
endpoint
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
if (response.status === 429) {
|
|
1187
|
+
throw new RateLimitError(
|
|
1188
|
+
errMsg,
|
|
1189
|
+
"Reduce request frequency.",
|
|
1190
|
+
endpoint
|
|
1191
|
+
);
|
|
1192
|
+
}
|
|
1193
|
+
throw new BithumbApiError(errMsg, { code: errName, endpoint });
|
|
1194
|
+
}
|
|
1195
|
+
if (!response.ok) {
|
|
1196
|
+
throw new BithumbApiError(`HTTP ${response.status}`, {
|
|
1197
|
+
code: String(response.status),
|
|
1198
|
+
endpoint: `${reqConfig.method} ${reqConfig.path}`
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
return {
|
|
1202
|
+
endpoint: `${reqConfig.method} ${reqConfig.path}`,
|
|
1203
|
+
requestTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1204
|
+
data: parsed
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
};
|
|
1208
|
+
function asRecord(args) {
|
|
1209
|
+
if (args && typeof args === "object" && !Array.isArray(args)) {
|
|
1210
|
+
return args;
|
|
1211
|
+
}
|
|
1212
|
+
return {};
|
|
1213
|
+
}
|
|
1214
|
+
function readString(args, key) {
|
|
1215
|
+
const v = args[key];
|
|
1216
|
+
if (v === void 0 || v === null || v === "") return void 0;
|
|
1217
|
+
return String(v);
|
|
1218
|
+
}
|
|
1219
|
+
function readNumber(args, key) {
|
|
1220
|
+
const v = args[key];
|
|
1221
|
+
if (v === void 0 || v === null || v === "") return void 0;
|
|
1222
|
+
const n = Number(v);
|
|
1223
|
+
if (!Number.isFinite(n)) return void 0;
|
|
1224
|
+
return n;
|
|
1225
|
+
}
|
|
1226
|
+
function readBoolean(args, key) {
|
|
1227
|
+
const v = args[key];
|
|
1228
|
+
if (v === void 0 || v === null) return void 0;
|
|
1229
|
+
if (typeof v === "boolean") return v;
|
|
1230
|
+
if (v === "true" || v === "1") return true;
|
|
1231
|
+
if (v === "false" || v === "0") return false;
|
|
1232
|
+
return void 0;
|
|
1233
|
+
}
|
|
1234
|
+
function readStringArray(args, key) {
|
|
1235
|
+
const v = args[key];
|
|
1236
|
+
if (v === void 0 || v === null) return void 0;
|
|
1237
|
+
if (Array.isArray(v)) return v.map(String);
|
|
1238
|
+
if (typeof v === "string") {
|
|
1239
|
+
return v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1240
|
+
}
|
|
1241
|
+
return void 0;
|
|
1242
|
+
}
|
|
1243
|
+
function requireString(args, key) {
|
|
1244
|
+
const v = readString(args, key);
|
|
1245
|
+
if (!v) {
|
|
1246
|
+
throw new ValidationError(
|
|
1247
|
+
`Missing required parameter "${key}".`,
|
|
1248
|
+
`Provide a non-empty "${key}" string.`
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
1251
|
+
return v;
|
|
1252
|
+
}
|
|
1253
|
+
function compactObject(obj) {
|
|
1254
|
+
const result = {};
|
|
1255
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1256
|
+
if (value !== void 0 && value !== null) {
|
|
1257
|
+
result[key] = value;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
return result;
|
|
1261
|
+
}
|
|
1262
|
+
function normalizeResponse(data) {
|
|
1263
|
+
if (data === null || data === void 0) return {};
|
|
1264
|
+
return data;
|
|
1265
|
+
}
|
|
1266
|
+
function publicRateLimit(key, rps = 150) {
|
|
1267
|
+
return { key: `public:${key}`, capacity: rps, refillPerSecond: rps };
|
|
1268
|
+
}
|
|
1269
|
+
function privateRateLimit(key, rps = 140) {
|
|
1270
|
+
return { key: `private:${key}`, capacity: rps, refillPerSecond: rps };
|
|
1271
|
+
}
|
|
1272
|
+
function orderRateLimit(key, rps = 10) {
|
|
1273
|
+
return { key: `order:${key}`, capacity: rps, refillPerSecond: rps };
|
|
1274
|
+
}
|
|
1275
|
+
function registerMarketTools() {
|
|
1276
|
+
return [
|
|
1277
|
+
// ── 1. market_get_markets ──────────────────────────────────────
|
|
1278
|
+
{
|
|
1279
|
+
name: "market_get_markets",
|
|
1280
|
+
module: "market",
|
|
1281
|
+
description: "\uBE57\uC378 \uAC70\uB798 \uAC00\uB2A5\uD55C \uB9C8\uCF13(\uAC70\uB798 \uD398\uC5B4) \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get all available trading pairs on Bithumb.",
|
|
1282
|
+
isWrite: false,
|
|
1283
|
+
inputSchema: {
|
|
1284
|
+
type: "object",
|
|
1285
|
+
properties: {
|
|
1286
|
+
isDetails: {
|
|
1287
|
+
type: "boolean",
|
|
1288
|
+
description: "Include detailed market info"
|
|
1289
|
+
}
|
|
1290
|
+
},
|
|
1291
|
+
required: []
|
|
1292
|
+
},
|
|
1293
|
+
handler: async (rawArgs, context) => {
|
|
1294
|
+
const args = asRecord(rawArgs);
|
|
1295
|
+
const response = await context.client.publicGet(
|
|
1296
|
+
"/v1/market/all",
|
|
1297
|
+
compactObject({ isDetails: readBoolean(args, "isDetails") }),
|
|
1298
|
+
publicRateLimit("market_get_markets")
|
|
1299
|
+
);
|
|
1300
|
+
return normalizeResponse(response);
|
|
1301
|
+
}
|
|
1302
|
+
},
|
|
1303
|
+
// ── 2. market_get_ticker ───────────────────────────────────────
|
|
1304
|
+
{
|
|
1305
|
+
name: "market_get_ticker",
|
|
1306
|
+
module: "market",
|
|
1307
|
+
description: "\uD604\uC7AC\uAC00(Ticker) \uC815\uBCF4\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get current price ticker for specified markets.",
|
|
1308
|
+
isWrite: false,
|
|
1309
|
+
inputSchema: {
|
|
1310
|
+
type: "object",
|
|
1311
|
+
properties: {
|
|
1312
|
+
markets: {
|
|
1313
|
+
type: "string",
|
|
1314
|
+
description: "Comma-separated market codes, e.g. KRW-BTC,KRW-ETH"
|
|
1315
|
+
}
|
|
1316
|
+
},
|
|
1317
|
+
required: ["markets"]
|
|
1318
|
+
},
|
|
1319
|
+
handler: async (rawArgs, context) => {
|
|
1320
|
+
const args = asRecord(rawArgs);
|
|
1321
|
+
const response = await context.client.publicGet(
|
|
1322
|
+
"/v1/ticker",
|
|
1323
|
+
compactObject({ markets: requireString(args, "markets") }),
|
|
1324
|
+
publicRateLimit("market_get_ticker")
|
|
1325
|
+
);
|
|
1326
|
+
return normalizeResponse(response);
|
|
1327
|
+
}
|
|
1328
|
+
},
|
|
1329
|
+
// ── 3. market_get_orderbook ────────────────────────────────────
|
|
1330
|
+
{
|
|
1331
|
+
name: "market_get_orderbook",
|
|
1332
|
+
module: "market",
|
|
1333
|
+
description: "\uD638\uAC00(Orderbook) \uC815\uBCF4\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get orderbook (bids/asks) for specified markets.",
|
|
1334
|
+
isWrite: false,
|
|
1335
|
+
inputSchema: {
|
|
1336
|
+
type: "object",
|
|
1337
|
+
properties: {
|
|
1338
|
+
markets: {
|
|
1339
|
+
type: "string",
|
|
1340
|
+
description: "Comma-separated market codes, e.g. KRW-BTC,KRW-ETH"
|
|
1341
|
+
}
|
|
1342
|
+
},
|
|
1343
|
+
required: ["markets"]
|
|
1344
|
+
},
|
|
1345
|
+
handler: async (rawArgs, context) => {
|
|
1346
|
+
const args = asRecord(rawArgs);
|
|
1347
|
+
const response = await context.client.publicGet(
|
|
1348
|
+
"/v1/orderbook",
|
|
1349
|
+
compactObject({ markets: requireString(args, "markets") }),
|
|
1350
|
+
publicRateLimit("market_get_orderbook")
|
|
1351
|
+
);
|
|
1352
|
+
return normalizeResponse(response);
|
|
1353
|
+
}
|
|
1354
|
+
},
|
|
1355
|
+
// ── 4. market_get_trades ───────────────────────────────────────
|
|
1356
|
+
{
|
|
1357
|
+
name: "market_get_trades",
|
|
1358
|
+
module: "market",
|
|
1359
|
+
description: "\uCD5C\uADFC \uCCB4\uACB0 \uB0B4\uC5ED\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get recent trades for a market.",
|
|
1360
|
+
isWrite: false,
|
|
1361
|
+
inputSchema: {
|
|
1362
|
+
type: "object",
|
|
1363
|
+
properties: {
|
|
1364
|
+
market: {
|
|
1365
|
+
type: "string",
|
|
1366
|
+
description: "Market code, e.g. KRW-BTC"
|
|
1367
|
+
},
|
|
1368
|
+
to: {
|
|
1369
|
+
type: "string",
|
|
1370
|
+
description: "Return trades before this timestamp (exclusive)"
|
|
1371
|
+
},
|
|
1372
|
+
count: {
|
|
1373
|
+
type: "number",
|
|
1374
|
+
description: "Number of trades to return (1-500)"
|
|
1375
|
+
},
|
|
1376
|
+
cursor: {
|
|
1377
|
+
type: "string",
|
|
1378
|
+
description: "Pagination cursor from previous response"
|
|
1379
|
+
},
|
|
1380
|
+
daysAgo: {
|
|
1381
|
+
type: "number",
|
|
1382
|
+
description: "Filter trades from N days ago (1-7)"
|
|
1383
|
+
}
|
|
1384
|
+
},
|
|
1385
|
+
required: ["market"]
|
|
1386
|
+
},
|
|
1387
|
+
handler: async (rawArgs, context) => {
|
|
1388
|
+
const args = asRecord(rawArgs);
|
|
1389
|
+
const response = await context.client.publicGet(
|
|
1390
|
+
"/v1/trades/ticks",
|
|
1391
|
+
compactObject({
|
|
1392
|
+
market: requireString(args, "market"),
|
|
1393
|
+
to: readString(args, "to"),
|
|
1394
|
+
count: readNumber(args, "count"),
|
|
1395
|
+
cursor: readString(args, "cursor"),
|
|
1396
|
+
daysAgo: readNumber(args, "daysAgo")
|
|
1397
|
+
}),
|
|
1398
|
+
publicRateLimit("market_get_trades")
|
|
1399
|
+
);
|
|
1400
|
+
return normalizeResponse(response);
|
|
1401
|
+
}
|
|
1402
|
+
},
|
|
1403
|
+
// ── 5. market_get_candles_minutes ──────────────────────────────
|
|
1404
|
+
{
|
|
1405
|
+
name: "market_get_candles_minutes",
|
|
1406
|
+
module: "market",
|
|
1407
|
+
description: "\uBD84(minute) \uCE94\uB4E4\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get minute candles (OHLCV).",
|
|
1408
|
+
isWrite: false,
|
|
1409
|
+
inputSchema: {
|
|
1410
|
+
type: "object",
|
|
1411
|
+
properties: {
|
|
1412
|
+
unit: {
|
|
1413
|
+
type: "number",
|
|
1414
|
+
enum: [1, 3, 5, 10, 15, 30, 60, 240],
|
|
1415
|
+
description: "Candle unit in minutes"
|
|
1416
|
+
},
|
|
1417
|
+
market: {
|
|
1418
|
+
type: "string",
|
|
1419
|
+
description: "Market code, e.g. KRW-BTC"
|
|
1420
|
+
},
|
|
1421
|
+
to: {
|
|
1422
|
+
type: "string",
|
|
1423
|
+
description: "Return candles before this timestamp"
|
|
1424
|
+
},
|
|
1425
|
+
count: {
|
|
1426
|
+
type: "number",
|
|
1427
|
+
description: "Number of candles to return (max 200)"
|
|
1428
|
+
}
|
|
1429
|
+
},
|
|
1430
|
+
required: ["unit", "market"]
|
|
1431
|
+
},
|
|
1432
|
+
handler: async (rawArgs, context) => {
|
|
1433
|
+
const args = asRecord(rawArgs);
|
|
1434
|
+
const unit = readNumber(args, "unit");
|
|
1435
|
+
if (!unit) throw new Error('Missing required parameter "unit".');
|
|
1436
|
+
const response = await context.client.publicGet(
|
|
1437
|
+
`/v1/candles/minutes/${unit}`,
|
|
1438
|
+
compactObject({
|
|
1439
|
+
market: requireString(args, "market"),
|
|
1440
|
+
to: readString(args, "to"),
|
|
1441
|
+
count: readNumber(args, "count")
|
|
1442
|
+
}),
|
|
1443
|
+
publicRateLimit("market_get_candles_minutes")
|
|
1444
|
+
);
|
|
1445
|
+
return normalizeResponse(response);
|
|
1446
|
+
}
|
|
1447
|
+
},
|
|
1448
|
+
// ── 6. market_get_candles_days ─────────────────────────────────
|
|
1449
|
+
{
|
|
1450
|
+
name: "market_get_candles_days",
|
|
1451
|
+
module: "market",
|
|
1452
|
+
description: "\uC77C(day) \uCE94\uB4E4\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get daily candles.",
|
|
1453
|
+
isWrite: false,
|
|
1454
|
+
inputSchema: {
|
|
1455
|
+
type: "object",
|
|
1456
|
+
properties: {
|
|
1457
|
+
market: {
|
|
1458
|
+
type: "string",
|
|
1459
|
+
description: "Market code, e.g. KRW-BTC"
|
|
1460
|
+
},
|
|
1461
|
+
to: {
|
|
1462
|
+
type: "string",
|
|
1463
|
+
description: "Return candles before this timestamp"
|
|
1464
|
+
},
|
|
1465
|
+
count: {
|
|
1466
|
+
type: "number",
|
|
1467
|
+
description: "Number of candles to return (max 200)"
|
|
1468
|
+
},
|
|
1469
|
+
convertingPriceUnit: {
|
|
1470
|
+
type: "string",
|
|
1471
|
+
description: "Price unit for conversion"
|
|
1472
|
+
}
|
|
1473
|
+
},
|
|
1474
|
+
required: ["market"]
|
|
1475
|
+
},
|
|
1476
|
+
handler: async (rawArgs, context) => {
|
|
1477
|
+
const args = asRecord(rawArgs);
|
|
1478
|
+
const response = await context.client.publicGet(
|
|
1479
|
+
"/v1/candles/days",
|
|
1480
|
+
compactObject({
|
|
1481
|
+
market: requireString(args, "market"),
|
|
1482
|
+
to: readString(args, "to"),
|
|
1483
|
+
count: readNumber(args, "count"),
|
|
1484
|
+
convertingPriceUnit: readString(args, "convertingPriceUnit")
|
|
1485
|
+
}),
|
|
1486
|
+
publicRateLimit("market_get_candles_days")
|
|
1487
|
+
);
|
|
1488
|
+
return normalizeResponse(response);
|
|
1489
|
+
}
|
|
1490
|
+
},
|
|
1491
|
+
// ── 7. market_get_candles_weeks ────────────────────────────────
|
|
1492
|
+
{
|
|
1493
|
+
name: "market_get_candles_weeks",
|
|
1494
|
+
module: "market",
|
|
1495
|
+
description: "\uC8FC(week) \uCE94\uB4E4\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get weekly candles.",
|
|
1496
|
+
isWrite: false,
|
|
1497
|
+
inputSchema: {
|
|
1498
|
+
type: "object",
|
|
1499
|
+
properties: {
|
|
1500
|
+
market: {
|
|
1501
|
+
type: "string",
|
|
1502
|
+
description: "Market code, e.g. KRW-BTC"
|
|
1503
|
+
},
|
|
1504
|
+
to: {
|
|
1505
|
+
type: "string",
|
|
1506
|
+
description: "Return candles before this timestamp"
|
|
1507
|
+
},
|
|
1508
|
+
count: {
|
|
1509
|
+
type: "number",
|
|
1510
|
+
description: "Number of candles to return (max 200)"
|
|
1511
|
+
}
|
|
1512
|
+
},
|
|
1513
|
+
required: ["market"]
|
|
1514
|
+
},
|
|
1515
|
+
handler: async (rawArgs, context) => {
|
|
1516
|
+
const args = asRecord(rawArgs);
|
|
1517
|
+
const response = await context.client.publicGet(
|
|
1518
|
+
"/v1/candles/weeks",
|
|
1519
|
+
compactObject({
|
|
1520
|
+
market: requireString(args, "market"),
|
|
1521
|
+
to: readString(args, "to"),
|
|
1522
|
+
count: readNumber(args, "count")
|
|
1523
|
+
}),
|
|
1524
|
+
publicRateLimit("market_get_candles_weeks")
|
|
1525
|
+
);
|
|
1526
|
+
return normalizeResponse(response);
|
|
1527
|
+
}
|
|
1528
|
+
},
|
|
1529
|
+
// ── 8. market_get_candles_months ───────────────────────────────
|
|
1530
|
+
{
|
|
1531
|
+
name: "market_get_candles_months",
|
|
1532
|
+
module: "market",
|
|
1533
|
+
description: "\uC6D4(month) \uCE94\uB4E4\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get monthly candles.",
|
|
1534
|
+
isWrite: false,
|
|
1535
|
+
inputSchema: {
|
|
1536
|
+
type: "object",
|
|
1537
|
+
properties: {
|
|
1538
|
+
market: {
|
|
1539
|
+
type: "string",
|
|
1540
|
+
description: "Market code, e.g. KRW-BTC"
|
|
1541
|
+
},
|
|
1542
|
+
to: {
|
|
1543
|
+
type: "string",
|
|
1544
|
+
description: "Return candles before this timestamp"
|
|
1545
|
+
},
|
|
1546
|
+
count: {
|
|
1547
|
+
type: "number",
|
|
1548
|
+
description: "Number of candles to return (max 200)"
|
|
1549
|
+
}
|
|
1550
|
+
},
|
|
1551
|
+
required: ["market"]
|
|
1552
|
+
},
|
|
1553
|
+
handler: async (rawArgs, context) => {
|
|
1554
|
+
const args = asRecord(rawArgs);
|
|
1555
|
+
const response = await context.client.publicGet(
|
|
1556
|
+
"/v1/candles/months",
|
|
1557
|
+
compactObject({
|
|
1558
|
+
market: requireString(args, "market"),
|
|
1559
|
+
to: readString(args, "to"),
|
|
1560
|
+
count: readNumber(args, "count")
|
|
1561
|
+
}),
|
|
1562
|
+
publicRateLimit("market_get_candles_months")
|
|
1563
|
+
);
|
|
1564
|
+
return normalizeResponse(response);
|
|
1565
|
+
}
|
|
1566
|
+
},
|
|
1567
|
+
// ── 9. market_get_warnings ─────────────────────────────────────
|
|
1568
|
+
{
|
|
1569
|
+
name: "market_get_warnings",
|
|
1570
|
+
module: "market",
|
|
1571
|
+
description: "\uD22C\uC790\uACBD\uBCF4 \uB9C8\uCF13 \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get virtual asset warning market list.",
|
|
1572
|
+
isWrite: false,
|
|
1573
|
+
inputSchema: {
|
|
1574
|
+
type: "object",
|
|
1575
|
+
properties: {},
|
|
1576
|
+
required: []
|
|
1577
|
+
},
|
|
1578
|
+
handler: async (_rawArgs, context) => {
|
|
1579
|
+
const response = await context.client.publicGet(
|
|
1580
|
+
"/v1/market/virtual_asset_warning",
|
|
1581
|
+
{},
|
|
1582
|
+
publicRateLimit("market_get_warnings")
|
|
1583
|
+
);
|
|
1584
|
+
return normalizeResponse(response);
|
|
1585
|
+
}
|
|
1586
|
+
},
|
|
1587
|
+
// ── 10. market_get_notices ─────────────────────────────────────
|
|
1588
|
+
{
|
|
1589
|
+
name: "market_get_notices",
|
|
1590
|
+
module: "market",
|
|
1591
|
+
description: "\uACF5\uC9C0\uC0AC\uD56D \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get notice list.",
|
|
1592
|
+
isWrite: false,
|
|
1593
|
+
inputSchema: {
|
|
1594
|
+
type: "object",
|
|
1595
|
+
properties: {
|
|
1596
|
+
count: {
|
|
1597
|
+
type: "number",
|
|
1598
|
+
description: "Number of notices to return (min 1, max 20, default 5)"
|
|
1599
|
+
}
|
|
1600
|
+
},
|
|
1601
|
+
required: []
|
|
1602
|
+
},
|
|
1603
|
+
handler: async (rawArgs, context) => {
|
|
1604
|
+
const args = asRecord(rawArgs);
|
|
1605
|
+
const count = readNumber(args, "count");
|
|
1606
|
+
const response = await context.client.publicGet(
|
|
1607
|
+
"/v1/notices",
|
|
1608
|
+
compactObject({ count }),
|
|
1609
|
+
publicRateLimit("market_get_notices")
|
|
1610
|
+
);
|
|
1611
|
+
return normalizeResponse(response);
|
|
1612
|
+
}
|
|
1613
|
+
},
|
|
1614
|
+
// ── 11. market_get_fee_inout ───────────────────────────────────
|
|
1615
|
+
{
|
|
1616
|
+
name: "market_get_fee_inout",
|
|
1617
|
+
module: "market",
|
|
1618
|
+
description: "\uC785\uCD9C\uAE08 \uC218\uC218\uB8CC\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get deposit/withdrawal fee info for a currency.",
|
|
1619
|
+
isWrite: false,
|
|
1620
|
+
inputSchema: {
|
|
1621
|
+
type: "object",
|
|
1622
|
+
properties: {
|
|
1623
|
+
currency: {
|
|
1624
|
+
type: "string",
|
|
1625
|
+
description: 'Currency symbol (use "ALL" to retrieve all currencies), e.g. BTC, ETH'
|
|
1626
|
+
}
|
|
1627
|
+
},
|
|
1628
|
+
required: ["currency"]
|
|
1629
|
+
},
|
|
1630
|
+
handler: async (rawArgs, context) => {
|
|
1631
|
+
const args = asRecord(rawArgs);
|
|
1632
|
+
const currency = requireString(args, "currency");
|
|
1633
|
+
const response = await context.client.publicGet(
|
|
1634
|
+
`/v2/fee/inout/${currency}`,
|
|
1635
|
+
{},
|
|
1636
|
+
publicRateLimit("market_get_fee_inout")
|
|
1637
|
+
);
|
|
1638
|
+
return normalizeResponse(response);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
];
|
|
1642
|
+
}
|
|
1643
|
+
function registerAccountTools() {
|
|
1644
|
+
return [
|
|
1645
|
+
// ── 1. account_get_balance ─────────────────────────────────────
|
|
1646
|
+
{
|
|
1647
|
+
name: "account_get_balance",
|
|
1648
|
+
module: "account",
|
|
1649
|
+
description: "\uC804\uCCB4 \uACC4\uC88C \uC794\uACE0\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get all account balances.",
|
|
1650
|
+
isWrite: false,
|
|
1651
|
+
inputSchema: {
|
|
1652
|
+
type: "object",
|
|
1653
|
+
properties: {},
|
|
1654
|
+
required: []
|
|
1655
|
+
},
|
|
1656
|
+
handler: async (_rawArgs, context) => {
|
|
1657
|
+
const response = await context.client.privateGet(
|
|
1658
|
+
"/v1/accounts",
|
|
1659
|
+
void 0,
|
|
1660
|
+
privateRateLimit("account_get_balance")
|
|
1661
|
+
);
|
|
1662
|
+
return normalizeResponse(response);
|
|
1663
|
+
}
|
|
1664
|
+
},
|
|
1665
|
+
// ── 2. account_get_order_chance ────────────────────────────────
|
|
1666
|
+
{
|
|
1667
|
+
name: "account_get_order_chance",
|
|
1668
|
+
module: "account",
|
|
1669
|
+
description: "\uC8FC\uBB38 \uAC00\uB2A5 \uC815\uBCF4\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get order chance info (available balance, fees, limits) for a market.",
|
|
1670
|
+
isWrite: false,
|
|
1671
|
+
inputSchema: {
|
|
1672
|
+
type: "object",
|
|
1673
|
+
properties: {
|
|
1674
|
+
market: {
|
|
1675
|
+
type: "string",
|
|
1676
|
+
description: "Market code, e.g. KRW-BTC"
|
|
1677
|
+
}
|
|
1678
|
+
},
|
|
1679
|
+
required: ["market"]
|
|
1680
|
+
},
|
|
1681
|
+
handler: async (rawArgs, context) => {
|
|
1682
|
+
const args = asRecord(rawArgs);
|
|
1683
|
+
const response = await context.client.privateGet(
|
|
1684
|
+
"/v1/orders/chance",
|
|
1685
|
+
compactObject({ market: requireString(args, "market") }),
|
|
1686
|
+
privateRateLimit("account_get_order_chance")
|
|
1687
|
+
);
|
|
1688
|
+
return normalizeResponse(response);
|
|
1689
|
+
}
|
|
1690
|
+
},
|
|
1691
|
+
// ── 3. account_get_wallet_status ───────────────────────────────
|
|
1692
|
+
{
|
|
1693
|
+
name: "account_get_wallet_status",
|
|
1694
|
+
module: "account",
|
|
1695
|
+
description: "\uC785\uCD9C\uAE08 \uD604\uD669\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get wallet status (block status, deposit/withdrawal availability).",
|
|
1696
|
+
isWrite: false,
|
|
1697
|
+
inputSchema: {
|
|
1698
|
+
type: "object",
|
|
1699
|
+
properties: {},
|
|
1700
|
+
required: []
|
|
1701
|
+
},
|
|
1702
|
+
handler: async (_rawArgs, context) => {
|
|
1703
|
+
const response = await context.client.privateGet(
|
|
1704
|
+
"/v1/status/wallet",
|
|
1705
|
+
{},
|
|
1706
|
+
privateRateLimit("account_get_wallet_status")
|
|
1707
|
+
);
|
|
1708
|
+
return normalizeResponse(response);
|
|
1709
|
+
}
|
|
1710
|
+
},
|
|
1711
|
+
// ── 4. account_get_api_keys ────────────────────────────────────
|
|
1712
|
+
{
|
|
1713
|
+
name: "account_get_api_keys",
|
|
1714
|
+
module: "account",
|
|
1715
|
+
description: "API \uD0A4 \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get list of API keys and expiration dates.",
|
|
1716
|
+
isWrite: false,
|
|
1717
|
+
inputSchema: {
|
|
1718
|
+
type: "object",
|
|
1719
|
+
properties: {},
|
|
1720
|
+
required: []
|
|
1721
|
+
},
|
|
1722
|
+
handler: async (_rawArgs, context) => {
|
|
1723
|
+
const response = await context.client.privateGet(
|
|
1724
|
+
"/v1/api_keys",
|
|
1725
|
+
{},
|
|
1726
|
+
privateRateLimit("account_get_api_keys")
|
|
1727
|
+
);
|
|
1728
|
+
return normalizeResponse(response);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
];
|
|
1732
|
+
}
|
|
1733
|
+
function registerTradeTools() {
|
|
1734
|
+
return [
|
|
1735
|
+
// ── 1. trade_get_order ─────────────────────────────────────────
|
|
1736
|
+
{
|
|
1737
|
+
name: "trade_get_order",
|
|
1738
|
+
module: "trade",
|
|
1739
|
+
description: "\uAC1C\uBCC4 \uC8FC\uBB38\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get a specific order by uuid or client_order_id.",
|
|
1740
|
+
isWrite: false,
|
|
1741
|
+
inputSchema: {
|
|
1742
|
+
type: "object",
|
|
1743
|
+
properties: {
|
|
1744
|
+
uuid: {
|
|
1745
|
+
type: "string",
|
|
1746
|
+
description: "Order UUID"
|
|
1747
|
+
},
|
|
1748
|
+
client_order_id: {
|
|
1749
|
+
type: "string",
|
|
1750
|
+
description: "Client-assigned order ID"
|
|
1751
|
+
}
|
|
1752
|
+
},
|
|
1753
|
+
required: []
|
|
1754
|
+
},
|
|
1755
|
+
handler: async (rawArgs, context) => {
|
|
1756
|
+
const args = asRecord(rawArgs);
|
|
1757
|
+
const response = await context.client.privateGet(
|
|
1758
|
+
"/v1/order",
|
|
1759
|
+
compactObject({
|
|
1760
|
+
uuid: readString(args, "uuid"),
|
|
1761
|
+
client_order_id: readString(args, "client_order_id")
|
|
1762
|
+
}),
|
|
1763
|
+
privateRateLimit("trade_get_order")
|
|
1764
|
+
);
|
|
1765
|
+
return normalizeResponse(response);
|
|
1766
|
+
}
|
|
1767
|
+
},
|
|
1768
|
+
// ── 2. trade_get_orders ────────────────────────────────────────
|
|
1769
|
+
{
|
|
1770
|
+
name: "trade_get_orders",
|
|
1771
|
+
module: "trade",
|
|
1772
|
+
description: "\uC8FC\uBB38 \uB9AC\uC2A4\uD2B8\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get list of orders with filters.",
|
|
1773
|
+
isWrite: false,
|
|
1774
|
+
inputSchema: {
|
|
1775
|
+
type: "object",
|
|
1776
|
+
properties: {
|
|
1777
|
+
market: {
|
|
1778
|
+
type: "string",
|
|
1779
|
+
description: "Market code, e.g. KRW-BTC"
|
|
1780
|
+
},
|
|
1781
|
+
state: {
|
|
1782
|
+
type: "string",
|
|
1783
|
+
enum: ["wait", "watch", "done", "cancel"],
|
|
1784
|
+
description: "Order state filter"
|
|
1785
|
+
},
|
|
1786
|
+
states: {
|
|
1787
|
+
type: "array",
|
|
1788
|
+
items: { type: "string" },
|
|
1789
|
+
description: "Multiple order state filters"
|
|
1790
|
+
},
|
|
1791
|
+
uuids: {
|
|
1792
|
+
type: "array",
|
|
1793
|
+
items: { type: "string" },
|
|
1794
|
+
description: "Filter by order UUIDs"
|
|
1795
|
+
},
|
|
1796
|
+
client_order_ids: {
|
|
1797
|
+
type: "array",
|
|
1798
|
+
items: { type: "string" },
|
|
1799
|
+
description: "Filter by client order IDs"
|
|
1800
|
+
},
|
|
1801
|
+
page: {
|
|
1802
|
+
type: "number",
|
|
1803
|
+
description: "Page number"
|
|
1804
|
+
},
|
|
1805
|
+
limit: {
|
|
1806
|
+
type: "number",
|
|
1807
|
+
description: "Number of results per page"
|
|
1808
|
+
},
|
|
1809
|
+
order_by: {
|
|
1810
|
+
type: "string",
|
|
1811
|
+
description: "Sort order"
|
|
1812
|
+
}
|
|
1813
|
+
},
|
|
1814
|
+
required: []
|
|
1815
|
+
},
|
|
1816
|
+
handler: async (rawArgs, context) => {
|
|
1817
|
+
const args = asRecord(rawArgs);
|
|
1818
|
+
const response = await context.client.privateGet(
|
|
1819
|
+
"/v1/orders",
|
|
1820
|
+
compactObject({
|
|
1821
|
+
market: readString(args, "market"),
|
|
1822
|
+
state: readString(args, "state"),
|
|
1823
|
+
states: readStringArray(args, "states"),
|
|
1824
|
+
uuids: readStringArray(args, "uuids"),
|
|
1825
|
+
client_order_ids: readStringArray(args, "client_order_ids"),
|
|
1826
|
+
page: readNumber(args, "page"),
|
|
1827
|
+
limit: readNumber(args, "limit"),
|
|
1828
|
+
order_by: readString(args, "order_by")
|
|
1829
|
+
}),
|
|
1830
|
+
privateRateLimit("trade_get_orders")
|
|
1831
|
+
);
|
|
1832
|
+
return normalizeResponse(response);
|
|
1833
|
+
}
|
|
1834
|
+
},
|
|
1835
|
+
// ── 3. trade_place_order ───────────────────────────────────────
|
|
1836
|
+
{
|
|
1837
|
+
name: "trade_place_order",
|
|
1838
|
+
module: "trade",
|
|
1839
|
+
description: '\uC8FC\uBB38\uC744 \uC0DD\uC131\uD569\uB2C8\uB2E4. Place a new spot order on Bithumb. Use when: user asks to buy/sell a single market at a specific price or quantity. Do NOT use: for multiple orders at once (use trade_batch_place); for time-weighted execution (use twap_place). Field: order_type \u2208 {limit, price, market}. limit: price+volume required. price(\uC2DC\uC7A5\uAC00 \uB9E4\uC218): only price (total KRW). market(\uC2DC\uC7A5\uAC00 \uB9E4\uB3C4): only volume (coin qty). CLI flag: --order-type (alias: --ord-type, deprecated). Response field `ord_type` is preserved as Bithumb upstream. Example call: {"market":"KRW-BTC","side":"bid","order_type":"limit","price":"50000000","volume":"0.0001"}',
|
|
1840
|
+
isWrite: true,
|
|
1841
|
+
inputSchema: {
|
|
1842
|
+
type: "object",
|
|
1843
|
+
properties: {
|
|
1844
|
+
market: {
|
|
1845
|
+
type: "string",
|
|
1846
|
+
description: "Market code, e.g. KRW-BTC"
|
|
1847
|
+
},
|
|
1848
|
+
side: {
|
|
1849
|
+
type: "string",
|
|
1850
|
+
enum: ["bid", "ask"],
|
|
1851
|
+
description: "Order side: bid (buy) or ask (sell)"
|
|
1852
|
+
},
|
|
1853
|
+
order_type: {
|
|
1854
|
+
type: "string",
|
|
1855
|
+
enum: ["limit", "price", "market"],
|
|
1856
|
+
description: "Order type: limit, price (market buy), market (market sell). CLI flag: --order-type (alias: --ord-type, deprecated). Response field is `ord_type` (upstream Bithumb), preserved as-is."
|
|
1857
|
+
},
|
|
1858
|
+
price: {
|
|
1859
|
+
type: "string",
|
|
1860
|
+
description: "Order price (required for limit and price orders)"
|
|
1861
|
+
},
|
|
1862
|
+
volume: {
|
|
1863
|
+
type: "string",
|
|
1864
|
+
description: "Order volume (required for limit and market orders)"
|
|
1865
|
+
},
|
|
1866
|
+
client_order_id: {
|
|
1867
|
+
type: "string",
|
|
1868
|
+
description: "Client-assigned order ID for idempotency"
|
|
1869
|
+
}
|
|
1870
|
+
},
|
|
1871
|
+
required: ["market", "side", "order_type"]
|
|
1872
|
+
},
|
|
1873
|
+
handler: async (rawArgs, context) => {
|
|
1874
|
+
const args = asRecord(rawArgs);
|
|
1875
|
+
const response = await context.client.privatePost(
|
|
1876
|
+
"/v2/orders",
|
|
1877
|
+
compactObject({
|
|
1878
|
+
market: requireString(args, "market"),
|
|
1879
|
+
side: requireString(args, "side"),
|
|
1880
|
+
order_type: requireString(args, "order_type"),
|
|
1881
|
+
price: readString(args, "price"),
|
|
1882
|
+
volume: readString(args, "volume"),
|
|
1883
|
+
client_order_id: readString(args, "client_order_id")
|
|
1884
|
+
}),
|
|
1885
|
+
orderRateLimit("trade_place_order")
|
|
1886
|
+
);
|
|
1887
|
+
return normalizeResponse(response);
|
|
1888
|
+
}
|
|
1889
|
+
},
|
|
1890
|
+
// ── 4. trade_cancel_order ──────────────────────────────────────
|
|
1891
|
+
{
|
|
1892
|
+
name: "trade_cancel_order",
|
|
1893
|
+
module: "trade",
|
|
1894
|
+
description: "\uC8FC\uBB38\uC744 \uCDE8\uC18C\uD569\uB2C8\uB2E4. Cancel an order by order_id or client_order_id.",
|
|
1895
|
+
isWrite: true,
|
|
1896
|
+
inputSchema: {
|
|
1897
|
+
type: "object",
|
|
1898
|
+
properties: {
|
|
1899
|
+
order_id: {
|
|
1900
|
+
type: "string",
|
|
1901
|
+
description: "Order UUID to cancel"
|
|
1902
|
+
},
|
|
1903
|
+
client_order_id: {
|
|
1904
|
+
type: "string",
|
|
1905
|
+
description: "Client-assigned order ID to cancel"
|
|
1906
|
+
}
|
|
1907
|
+
},
|
|
1908
|
+
required: []
|
|
1909
|
+
},
|
|
1910
|
+
handler: async (rawArgs, context) => {
|
|
1911
|
+
const args = asRecord(rawArgs);
|
|
1912
|
+
const response = await context.client.privateDelete(
|
|
1913
|
+
"/v2/order",
|
|
1914
|
+
compactObject({
|
|
1915
|
+
order_id: readString(args, "order_id"),
|
|
1916
|
+
client_order_id: readString(args, "client_order_id")
|
|
1917
|
+
}),
|
|
1918
|
+
orderRateLimit("trade_cancel_order")
|
|
1919
|
+
);
|
|
1920
|
+
return normalizeResponse(response);
|
|
1921
|
+
}
|
|
1922
|
+
},
|
|
1923
|
+
// ── 5. trade_batch_place ───────────────────────────────────────
|
|
1924
|
+
{
|
|
1925
|
+
name: "trade_batch_place",
|
|
1926
|
+
module: "trade",
|
|
1927
|
+
description: '\uB2E4\uAC74 \uC8FC\uBB38\uC744 \uC694\uCCAD\uD569\uB2C8\uB2E4. Place multiple orders in a single batch (max 20). Use when: user asks for multiple orders at once (e.g., grid orders, simultaneous BTC+ETH limit buys). Do NOT use: for a single order (use trade_place_order); for time-sliced execution (use twap_place). Each order item field: `order_type` (canonical). Legacy `ord_type` is auto-normalized to `order_type` by the CLI; MCP callers should always send `order_type`. Partial-failure semantics: each item may succeed or fail independently. Do NOT auto-retry failed items; surface them to the user. Example call: {"batch_orders":[{"market":"KRW-BTC","side":"bid","order_type":"limit","price":"50000000","volume":"0.0001"},{"market":"KRW-ETH","side":"bid","order_type":"limit","price":"4500000","volume":"0.001"}]}',
|
|
1928
|
+
isWrite: true,
|
|
1929
|
+
inputSchema: {
|
|
1930
|
+
type: "object",
|
|
1931
|
+
properties: {
|
|
1932
|
+
batch_orders: {
|
|
1933
|
+
type: "array",
|
|
1934
|
+
items: {
|
|
1935
|
+
type: "object",
|
|
1936
|
+
properties: {
|
|
1937
|
+
market: { type: "string", description: "Market code, e.g. KRW-BTC" },
|
|
1938
|
+
side: { type: "string", enum: ["bid", "ask"], description: "Order side" },
|
|
1939
|
+
order_type: { type: "string", enum: ["limit", "price", "market"], description: "Order type" },
|
|
1940
|
+
price: { type: "string", description: "Order price" },
|
|
1941
|
+
volume: { type: "string", description: "Order volume" },
|
|
1942
|
+
client_order_id: { type: "string", description: "Client-assigned order ID" }
|
|
1943
|
+
},
|
|
1944
|
+
required: ["market", "side", "order_type"]
|
|
1945
|
+
},
|
|
1946
|
+
description: "Array of order objects (max 20)"
|
|
1947
|
+
}
|
|
1948
|
+
},
|
|
1949
|
+
required: ["batch_orders"]
|
|
1950
|
+
},
|
|
1951
|
+
handler: async (rawArgs, context) => {
|
|
1952
|
+
const args = asRecord(rawArgs);
|
|
1953
|
+
const batchOrders = args.batch_orders;
|
|
1954
|
+
if (!Array.isArray(batchOrders) || batchOrders.length === 0) {
|
|
1955
|
+
throw new ValidationError(
|
|
1956
|
+
'Missing required parameter "batch_orders".',
|
|
1957
|
+
"Provide an array of order objects."
|
|
1958
|
+
);
|
|
1959
|
+
}
|
|
1960
|
+
const response = await context.client.privatePost(
|
|
1961
|
+
"/v2/orders/batch",
|
|
1962
|
+
{ batch_orders: batchOrders },
|
|
1963
|
+
orderRateLimit("trade_batch_place")
|
|
1964
|
+
);
|
|
1965
|
+
return normalizeResponse(response);
|
|
1966
|
+
}
|
|
1967
|
+
},
|
|
1968
|
+
// ── 6. trade_batch_cancel ──────────────────────────────────────
|
|
1969
|
+
{
|
|
1970
|
+
name: "trade_batch_cancel",
|
|
1971
|
+
module: "trade",
|
|
1972
|
+
description: "\uB2E4\uAC74 \uC8FC\uBB38\uC744 \uCDE8\uC18C\uD569\uB2C8\uB2E4. Cancel multiple orders (max 30). Provide order_ids or client_order_ids.",
|
|
1973
|
+
isWrite: true,
|
|
1974
|
+
inputSchema: {
|
|
1975
|
+
type: "object",
|
|
1976
|
+
properties: {
|
|
1977
|
+
order_ids: {
|
|
1978
|
+
type: "array",
|
|
1979
|
+
items: { type: "string" },
|
|
1980
|
+
description: "List of order UUIDs to cancel (max 30)"
|
|
1981
|
+
},
|
|
1982
|
+
client_order_ids: {
|
|
1983
|
+
type: "array",
|
|
1984
|
+
items: { type: "string" },
|
|
1985
|
+
description: "List of client-assigned order IDs to cancel (max 30)"
|
|
1986
|
+
}
|
|
1987
|
+
},
|
|
1988
|
+
required: []
|
|
1989
|
+
},
|
|
1990
|
+
handler: async (rawArgs, context) => {
|
|
1991
|
+
const args = asRecord(rawArgs);
|
|
1992
|
+
const response = await context.client.privatePost(
|
|
1993
|
+
"/v2/orders/cancel",
|
|
1994
|
+
compactObject({
|
|
1995
|
+
order_ids: readStringArray(args, "order_ids"),
|
|
1996
|
+
client_order_ids: readStringArray(args, "client_order_ids")
|
|
1997
|
+
}),
|
|
1998
|
+
orderRateLimit("trade_batch_cancel")
|
|
1999
|
+
);
|
|
2000
|
+
return normalizeResponse(response);
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
];
|
|
2004
|
+
}
|
|
2005
|
+
var DEFAULT_LOG_DIR = path.join(os.homedir(), ".bithumb", "logs");
|
|
2006
|
+
function getLogPaths(logDir, days = 7) {
|
|
2007
|
+
const paths = [];
|
|
2008
|
+
const now = /* @__PURE__ */ new Date();
|
|
2009
|
+
for (let i = 0; i < days; i++) {
|
|
2010
|
+
const d = new Date(now);
|
|
2011
|
+
d.setDate(d.getDate() - i);
|
|
2012
|
+
const yyyy = d.getFullYear();
|
|
2013
|
+
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
2014
|
+
const dd = String(d.getDate()).padStart(2, "0");
|
|
2015
|
+
paths.push(path.join(logDir, `trade-${yyyy}-${mm}-${dd}.log`));
|
|
2016
|
+
}
|
|
2017
|
+
return paths;
|
|
2018
|
+
}
|
|
2019
|
+
function readEntries(logDir) {
|
|
2020
|
+
const filePaths = getLogPaths(logDir);
|
|
2021
|
+
const entries = [];
|
|
2022
|
+
for (const filePath of filePaths) {
|
|
2023
|
+
if (!fs.existsSync(filePath)) continue;
|
|
2024
|
+
let content;
|
|
2025
|
+
try {
|
|
2026
|
+
content = fs.readFileSync(filePath, "utf8");
|
|
2027
|
+
} catch {
|
|
2028
|
+
continue;
|
|
2029
|
+
}
|
|
2030
|
+
for (const line of content.split("\n")) {
|
|
2031
|
+
const trimmed = line.trim();
|
|
2032
|
+
if (!trimmed) continue;
|
|
2033
|
+
try {
|
|
2034
|
+
const entry = JSON.parse(trimmed);
|
|
2035
|
+
entries.push(entry);
|
|
2036
|
+
} catch {
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
return entries;
|
|
2041
|
+
}
|
|
2042
|
+
function extractTool(entry) {
|
|
2043
|
+
if (entry.tool) return entry.tool;
|
|
2044
|
+
if (typeof entry.message === "string" && entry.message.startsWith("tool:")) {
|
|
2045
|
+
return entry.message.slice("tool:".length);
|
|
2046
|
+
}
|
|
2047
|
+
return void 0;
|
|
2048
|
+
}
|
|
2049
|
+
function registerAuditTools(logDir) {
|
|
2050
|
+
const resolvedLogDir = logDir ?? DEFAULT_LOG_DIR;
|
|
2051
|
+
return [
|
|
2052
|
+
// ── trade_get_history ──────────────────────────────────────────
|
|
2053
|
+
{
|
|
2054
|
+
name: "trade_get_history",
|
|
2055
|
+
module: "account",
|
|
2056
|
+
description: "\uB85C\uCEEC \uB85C\uADF8\uC5D0\uC11C \uAC70\uB798 \uC774\uB825\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Read local audit logs filtered by tool, level, and time.",
|
|
2057
|
+
isWrite: false,
|
|
2058
|
+
inputSchema: {
|
|
2059
|
+
type: "object",
|
|
2060
|
+
properties: {
|
|
2061
|
+
limit: {
|
|
2062
|
+
type: "number",
|
|
2063
|
+
description: "Maximum number of entries to return (default 20)."
|
|
2064
|
+
},
|
|
2065
|
+
tool: {
|
|
2066
|
+
type: "string",
|
|
2067
|
+
description: "Filter by tool name."
|
|
2068
|
+
},
|
|
2069
|
+
level: {
|
|
2070
|
+
type: "string",
|
|
2071
|
+
enum: ["INFO", "WARN", "ERROR", "DEBUG"],
|
|
2072
|
+
description: "Filter by log level (case-insensitive)."
|
|
2073
|
+
},
|
|
2074
|
+
since: {
|
|
2075
|
+
type: "string",
|
|
2076
|
+
description: "ISO 8601 timestamp; return entries at or after this time."
|
|
2077
|
+
}
|
|
2078
|
+
},
|
|
2079
|
+
required: []
|
|
2080
|
+
},
|
|
2081
|
+
handler: async (rawArgs, _context) => {
|
|
2082
|
+
const args = asRecord(rawArgs);
|
|
2083
|
+
const limit = readNumber(args, "limit") ?? 20;
|
|
2084
|
+
const toolFilter = readString(args, "tool");
|
|
2085
|
+
const levelFilter = readString(args, "level")?.toLowerCase();
|
|
2086
|
+
const sinceStr = readString(args, "since");
|
|
2087
|
+
const sinceMs = sinceStr ? new Date(sinceStr).getTime() : void 0;
|
|
2088
|
+
const entries = readEntries(resolvedLogDir);
|
|
2089
|
+
const filtered = entries.filter((entry) => {
|
|
2090
|
+
if (toolFilter) {
|
|
2091
|
+
const t = extractTool(entry);
|
|
2092
|
+
if (!t || !t.includes(toolFilter)) return false;
|
|
2093
|
+
}
|
|
2094
|
+
if (levelFilter && entry.level !== levelFilter) return false;
|
|
2095
|
+
if (sinceMs !== void 0) {
|
|
2096
|
+
const entryMs = new Date(entry.ts).getTime();
|
|
2097
|
+
if (Number.isNaN(entryMs) || entryMs < sinceMs) return false;
|
|
2098
|
+
}
|
|
2099
|
+
return true;
|
|
2100
|
+
});
|
|
2101
|
+
filtered.sort((a, b) => {
|
|
2102
|
+
const ta = new Date(a.ts).getTime();
|
|
2103
|
+
const tb = new Date(b.ts).getTime();
|
|
2104
|
+
return tb - ta;
|
|
2105
|
+
});
|
|
2106
|
+
return {
|
|
2107
|
+
endpoint: "local:audit-log",
|
|
2108
|
+
requestTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2109
|
+
data: filtered.slice(0, limit)
|
|
2110
|
+
};
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
];
|
|
2114
|
+
}
|
|
2115
|
+
var BITHUMB_API_BASE_URL = "https://api.bithumb.com";
|
|
2116
|
+
var MODULES = ["market", "account", "trade", "twap", "withdraw", "deposit"];
|
|
2117
|
+
var DEFAULT_MODULES = ["market", "account", "trade", "twap", "withdraw", "deposit"];
|
|
2118
|
+
async function checkApiReachability(baseUrl) {
|
|
2119
|
+
try {
|
|
2120
|
+
const controller = new AbortController();
|
|
2121
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
2122
|
+
const res = await fetch(`${baseUrl}/v1/market/all`, {
|
|
2123
|
+
signal: controller.signal
|
|
2124
|
+
});
|
|
2125
|
+
clearTimeout(timeout);
|
|
2126
|
+
return {
|
|
2127
|
+
name: "API Reachability",
|
|
2128
|
+
status: res.ok ? "pass" : "fail",
|
|
2129
|
+
message: res.ok ? `${baseUrl} reachable (HTTP ${res.status})` : `${baseUrl} returned HTTP ${res.status}`
|
|
2130
|
+
};
|
|
2131
|
+
} catch (err) {
|
|
2132
|
+
return {
|
|
2133
|
+
name: "API Reachability",
|
|
2134
|
+
status: "fail",
|
|
2135
|
+
message: `Cannot reach ${baseUrl}: ${err instanceof Error ? err.message : String(err)}`
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
function checkAuthentication() {
|
|
2140
|
+
const accessKey = process.env.BITHUMB_ACCESS_KEY?.trim();
|
|
2141
|
+
const secretKey = process.env.BITHUMB_SECRET_KEY?.trim();
|
|
2142
|
+
if (accessKey && secretKey) {
|
|
2143
|
+
return { name: "Authentication", status: "pass", message: "API keys configured via environment variables" };
|
|
2144
|
+
}
|
|
2145
|
+
const tomlPath = join(homedir(), ".bithumb", "config.toml");
|
|
2146
|
+
if (existsSync(tomlPath)) {
|
|
2147
|
+
return { name: "Authentication", status: "pass", message: "config.toml found (credentials may be in profile)" };
|
|
2148
|
+
}
|
|
2149
|
+
if (accessKey || secretKey) {
|
|
2150
|
+
return { name: "Authentication", status: "fail", message: "Partial credentials: set both BITHUMB_ACCESS_KEY and BITHUMB_SECRET_KEY" };
|
|
2151
|
+
}
|
|
2152
|
+
return { name: "Authentication", status: "fail", message: "No credentials found (set env vars or create ~/.bithumb/config.toml)" };
|
|
2153
|
+
}
|
|
2154
|
+
function checkTomlConfig() {
|
|
2155
|
+
const tomlPath = join(homedir(), ".bithumb", "config.toml");
|
|
2156
|
+
if (existsSync(tomlPath)) {
|
|
2157
|
+
return { name: "TOML Config", status: "pass", message: `Found ${tomlPath}` };
|
|
2158
|
+
}
|
|
2159
|
+
return { name: "TOML Config", status: "fail", message: `Not found: ${tomlPath} (optional \u2014 use 'setup' command to create)` };
|
|
2160
|
+
}
|
|
2161
|
+
function checkModules(enabledModules) {
|
|
2162
|
+
return {
|
|
2163
|
+
name: "Enabled Modules",
|
|
2164
|
+
status: "pass",
|
|
2165
|
+
message: `Active: ${enabledModules.join(", ")}`
|
|
2166
|
+
};
|
|
2167
|
+
}
|
|
2168
|
+
var ALL_MODULES = ["market", "account", "trade", "twap", "withdraw", "deposit"];
|
|
2169
|
+
function registerDiagnoseTools() {
|
|
2170
|
+
return [
|
|
2171
|
+
{
|
|
2172
|
+
name: "system_get_capabilities",
|
|
2173
|
+
module: "account",
|
|
2174
|
+
description: "Return server capabilities and module availability for agent planning.",
|
|
2175
|
+
isWrite: false,
|
|
2176
|
+
inputSchema: {
|
|
2177
|
+
type: "object",
|
|
2178
|
+
properties: {},
|
|
2179
|
+
additionalProperties: false
|
|
2180
|
+
},
|
|
2181
|
+
handler: async (_args, context) => {
|
|
2182
|
+
const enabledModules = new Set(context.config.modules);
|
|
2183
|
+
const moduleAvailability = {};
|
|
2184
|
+
for (const mod of ALL_MODULES) {
|
|
2185
|
+
if (!enabledModules.has(mod)) {
|
|
2186
|
+
moduleAvailability[mod] = { status: "disabled", reasonCode: "MODULE_FILTERED" };
|
|
2187
|
+
} else if (mod !== "market" && !context.config.hasAuth) {
|
|
2188
|
+
moduleAvailability[mod] = { status: "requires_auth", reasonCode: "AUTH_MISSING" };
|
|
2189
|
+
} else {
|
|
2190
|
+
moduleAvailability[mod] = { status: "enabled" };
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
return {
|
|
2194
|
+
endpoint: "local:capabilities",
|
|
2195
|
+
requestTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2196
|
+
data: {
|
|
2197
|
+
readOnly: context.config.readOnly,
|
|
2198
|
+
hasAuth: context.config.hasAuth,
|
|
2199
|
+
moduleAvailability
|
|
2200
|
+
}
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2203
|
+
},
|
|
2204
|
+
{
|
|
2205
|
+
name: "system_diagnose",
|
|
2206
|
+
module: "account",
|
|
2207
|
+
description: "Run diagnostic checks on the Bithumb Trade Kit configuration. Checks API reachability, authentication, TOML config, and module status.",
|
|
2208
|
+
isWrite: false,
|
|
2209
|
+
inputSchema: {
|
|
2210
|
+
type: "object",
|
|
2211
|
+
properties: {}
|
|
2212
|
+
},
|
|
2213
|
+
handler: async (_args, context) => {
|
|
2214
|
+
const baseUrl = context?.config?.baseUrl ?? BITHUMB_API_BASE_URL;
|
|
2215
|
+
const modules = context?.config?.modules ?? [];
|
|
2216
|
+
const checks = [];
|
|
2217
|
+
checks.push(await checkApiReachability(baseUrl));
|
|
2218
|
+
checks.push(checkAuthentication());
|
|
2219
|
+
checks.push(checkTomlConfig());
|
|
2220
|
+
checks.push(checkModules(modules));
|
|
2221
|
+
const passed = checks.filter((c) => c.status === "pass").length;
|
|
2222
|
+
const total = checks.length;
|
|
2223
|
+
return {
|
|
2224
|
+
endpoint: "local:diagnose",
|
|
2225
|
+
requestTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2226
|
+
data: {
|
|
2227
|
+
summary: `${passed}/${total} checks passed`,
|
|
2228
|
+
checks
|
|
2229
|
+
}
|
|
2230
|
+
};
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
];
|
|
2234
|
+
}
|
|
2235
|
+
function registerTwapTools() {
|
|
2236
|
+
return [
|
|
2237
|
+
// ── 6. twap_place_order ────────────────────────────────────────
|
|
2238
|
+
{
|
|
2239
|
+
name: "twap_place_order",
|
|
2240
|
+
module: "twap",
|
|
2241
|
+
description: "TWAP \uC8FC\uBB38\uC744 \uC694\uCCAD\uD569\uB2C8\uB2E4. Place a TWAP (Time-Weighted Average Price) order.",
|
|
2242
|
+
isWrite: true,
|
|
2243
|
+
inputSchema: {
|
|
2244
|
+
type: "object",
|
|
2245
|
+
properties: {
|
|
2246
|
+
market: {
|
|
2247
|
+
type: "string",
|
|
2248
|
+
description: "\uAC70\uB798 \uB300\uC0C1 \uD398\uC5B4\uC758 \uACE0\uC720 \uC2EC\uBCFC (\uC608\uC2DC: KRW-BTC)"
|
|
2249
|
+
},
|
|
2250
|
+
side: {
|
|
2251
|
+
type: "string",
|
|
2252
|
+
enum: ["bid", "ask"],
|
|
2253
|
+
description: "\uC8FC\uBB38 \uC885\uB958: bid (\uB9E4\uC218), ask (\uB9E4\uB3C4)"
|
|
2254
|
+
},
|
|
2255
|
+
duration: {
|
|
2256
|
+
type: "string",
|
|
2257
|
+
description: "\uC8FC\uBB38 \uC2DC\uAC04 - TWAP \uC8FC\uBB38\uC774 \uC9C4\uD589\uB418\uB294 \uC2DC\uAC04(\uCD08). min 300, max 43200"
|
|
2258
|
+
},
|
|
2259
|
+
frequency: {
|
|
2260
|
+
type: "string",
|
|
2261
|
+
enum: ["5", "15", "20", "30", "60", "120"],
|
|
2262
|
+
description: "\uC8FC\uBB38 \uAC04\uACA9(\uCD08)"
|
|
2263
|
+
},
|
|
2264
|
+
volume: {
|
|
2265
|
+
type: "string",
|
|
2266
|
+
description: "\uC8FC\uBB38 \uC218\uB7C9 (\uB9E4\uB3C4 \uC2DC \uD544\uC218)"
|
|
2267
|
+
},
|
|
2268
|
+
price: {
|
|
2269
|
+
type: "string",
|
|
2270
|
+
description: "\uC8FC\uBB38 \uAC00\uACA9 (\uB9E4\uC218 \uC2DC \uD544\uC218)"
|
|
2271
|
+
}
|
|
2272
|
+
},
|
|
2273
|
+
required: ["market", "side", "duration", "frequency"]
|
|
2274
|
+
},
|
|
2275
|
+
handler: async (rawArgs, context) => {
|
|
2276
|
+
const args = asRecord(rawArgs);
|
|
2277
|
+
const response = await context.client.privatePost(
|
|
2278
|
+
"/v1/twap",
|
|
2279
|
+
compactObject({
|
|
2280
|
+
market: requireString(args, "market"),
|
|
2281
|
+
side: requireString(args, "side"),
|
|
2282
|
+
duration: requireString(args, "duration"),
|
|
2283
|
+
frequency: requireString(args, "frequency"),
|
|
2284
|
+
volume: readString(args, "volume"),
|
|
2285
|
+
price: readString(args, "price")
|
|
2286
|
+
}),
|
|
2287
|
+
orderRateLimit("twap_place_order")
|
|
2288
|
+
);
|
|
2289
|
+
return normalizeResponse(response);
|
|
2290
|
+
}
|
|
2291
|
+
},
|
|
2292
|
+
// ── 7. twap_get_orders ─────────────────────────────────────────
|
|
2293
|
+
{
|
|
2294
|
+
name: "twap_get_orders",
|
|
2295
|
+
module: "twap",
|
|
2296
|
+
description: "TWAP \uC8FC\uBB38 \uB0B4\uC5ED\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get TWAP order history.",
|
|
2297
|
+
isWrite: false,
|
|
2298
|
+
inputSchema: {
|
|
2299
|
+
type: "object",
|
|
2300
|
+
properties: {
|
|
2301
|
+
market: {
|
|
2302
|
+
type: "string",
|
|
2303
|
+
description: "\uAC70\uB798 \uB300\uC0C1 \uD398\uC5B4\uC758 \uACE0\uC720 \uC2EC\uBCFC (\uC608\uC2DC: KRW-BTC)"
|
|
2304
|
+
},
|
|
2305
|
+
uuids: {
|
|
2306
|
+
type: "array",
|
|
2307
|
+
items: { type: "string" },
|
|
2308
|
+
description: "TWAP \uC8FC\uBB38 ID \uBAA9\uB85D"
|
|
2309
|
+
},
|
|
2310
|
+
state: {
|
|
2311
|
+
type: "string",
|
|
2312
|
+
enum: ["progress", "done", "cancel"],
|
|
2313
|
+
description: "\uC8FC\uBB38 \uC0C1\uD0DC: progress (\uC9C4\uD589\uC911, default), done (\uC644\uB8CC), cancel (\uCDE8\uC18C)"
|
|
2314
|
+
},
|
|
2315
|
+
next_key: {
|
|
2316
|
+
type: "string",
|
|
2317
|
+
description: "\uB2E4\uC74C \uD398\uC774\uC9C0 \uC870\uD68C\uB97C \uC704\uD55C \uCEE4\uC11C \uAC12"
|
|
2318
|
+
},
|
|
2319
|
+
limit: {
|
|
2320
|
+
type: "number",
|
|
2321
|
+
description: "\uAC1C\uC218 \uC81C\uD55C (max 100)"
|
|
2322
|
+
},
|
|
2323
|
+
order_by: {
|
|
2324
|
+
type: "string",
|
|
2325
|
+
enum: ["asc", "desc"],
|
|
2326
|
+
description: "\uC870\uD68C \uACB0\uACFC \uC815\uB82C \uBC29\uC2DD: asc (\uC624\uB984\uCC28\uC21C), desc (\uB0B4\uB9BC\uCC28\uC21C, default)"
|
|
2327
|
+
}
|
|
2328
|
+
},
|
|
2329
|
+
required: []
|
|
2330
|
+
},
|
|
2331
|
+
handler: async (rawArgs, context) => {
|
|
2332
|
+
const args = asRecord(rawArgs);
|
|
2333
|
+
const response = await context.client.privateGet(
|
|
2334
|
+
"/v1/twap",
|
|
2335
|
+
compactObject({
|
|
2336
|
+
market: readString(args, "market"),
|
|
2337
|
+
uuids: readStringArray(args, "uuids"),
|
|
2338
|
+
state: readString(args, "state"),
|
|
2339
|
+
next_key: readString(args, "next_key"),
|
|
2340
|
+
limit: readNumber(args, "limit"),
|
|
2341
|
+
order_by: readString(args, "order_by")
|
|
2342
|
+
}),
|
|
2343
|
+
privateRateLimit("twap_get_orders")
|
|
2344
|
+
);
|
|
2345
|
+
return normalizeResponse(response);
|
|
2346
|
+
}
|
|
2347
|
+
},
|
|
2348
|
+
// ── 8. twap_cancel_order ───────────────────────────────────────
|
|
2349
|
+
{
|
|
2350
|
+
name: "twap_cancel_order",
|
|
2351
|
+
module: "twap",
|
|
2352
|
+
description: "TWAP \uC8FC\uBB38\uC744 \uCDE8\uC18C\uD569\uB2C8\uB2E4. Cancel a TWAP order.",
|
|
2353
|
+
isWrite: true,
|
|
2354
|
+
inputSchema: {
|
|
2355
|
+
type: "object",
|
|
2356
|
+
properties: {
|
|
2357
|
+
algo_order_id: {
|
|
2358
|
+
type: "string",
|
|
2359
|
+
description: "\uCDE8\uC18C\uD560 TWAP \uC8FC\uBB38 ID"
|
|
2360
|
+
}
|
|
2361
|
+
},
|
|
2362
|
+
required: ["algo_order_id"]
|
|
2363
|
+
},
|
|
2364
|
+
handler: async (rawArgs, context) => {
|
|
2365
|
+
const args = asRecord(rawArgs);
|
|
2366
|
+
const algo_order_id = requireString(args, "algo_order_id");
|
|
2367
|
+
const response = await context.client.privateDelete(
|
|
2368
|
+
"/v1/twap",
|
|
2369
|
+
{ algo_order_id },
|
|
2370
|
+
orderRateLimit("twap_cancel_order")
|
|
2371
|
+
);
|
|
2372
|
+
return normalizeResponse(response);
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
];
|
|
2376
|
+
}
|
|
2377
|
+
function registerWithdrawTools() {
|
|
2378
|
+
return [
|
|
2379
|
+
// ── 9. withdraw_get_chance ───────────────────────────────────────
|
|
2380
|
+
{
|
|
2381
|
+
name: "withdraw_get_chance",
|
|
2382
|
+
module: "withdraw",
|
|
2383
|
+
description: "\uCD9C\uAE08 \uAC00\uB2A5 \uC815\uBCF4\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get withdrawal chance info (available balance, fees).",
|
|
2384
|
+
isWrite: false,
|
|
2385
|
+
inputSchema: {
|
|
2386
|
+
type: "object",
|
|
2387
|
+
properties: {
|
|
2388
|
+
currency: {
|
|
2389
|
+
type: "string",
|
|
2390
|
+
description: "Currency symbol, e.g. BTC"
|
|
2391
|
+
},
|
|
2392
|
+
net_type: {
|
|
2393
|
+
type: "string",
|
|
2394
|
+
description: "Withdrawal network, e.g. BTC, DASH"
|
|
2395
|
+
}
|
|
2396
|
+
},
|
|
2397
|
+
required: ["currency", "net_type"]
|
|
2398
|
+
},
|
|
2399
|
+
handler: async (rawArgs, context) => {
|
|
2400
|
+
const args = asRecord(rawArgs);
|
|
2401
|
+
const response = await context.client.privateGet(
|
|
2402
|
+
"/v1/withdraws/chance",
|
|
2403
|
+
{
|
|
2404
|
+
currency: requireString(args, "currency"),
|
|
2405
|
+
net_type: requireString(args, "net_type")
|
|
2406
|
+
},
|
|
2407
|
+
privateRateLimit("withdraw_get_chance")
|
|
2408
|
+
);
|
|
2409
|
+
return normalizeResponse(response);
|
|
2410
|
+
}
|
|
2411
|
+
},
|
|
2412
|
+
// ── 10. withdraw_get ─────────────────────────────────────────────
|
|
2413
|
+
{
|
|
2414
|
+
name: "withdraw_get",
|
|
2415
|
+
module: "withdraw",
|
|
2416
|
+
description: "\uAC1C\uBCC4 \uCD9C\uAE08\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get a specific withdrawal by currency.",
|
|
2417
|
+
isWrite: false,
|
|
2418
|
+
inputSchema: {
|
|
2419
|
+
type: "object",
|
|
2420
|
+
properties: {
|
|
2421
|
+
currency: {
|
|
2422
|
+
type: "string",
|
|
2423
|
+
description: "Currency symbol, e.g. BTC"
|
|
2424
|
+
},
|
|
2425
|
+
uuid: {
|
|
2426
|
+
type: "string",
|
|
2427
|
+
description: "Withdrawal unique ID"
|
|
2428
|
+
},
|
|
2429
|
+
txid: {
|
|
2430
|
+
type: "string",
|
|
2431
|
+
description: "Withdrawal transaction ID"
|
|
2432
|
+
}
|
|
2433
|
+
},
|
|
2434
|
+
required: ["currency"]
|
|
2435
|
+
},
|
|
2436
|
+
handler: async (rawArgs, context) => {
|
|
2437
|
+
const args = asRecord(rawArgs);
|
|
2438
|
+
const response = await context.client.privateGet(
|
|
2439
|
+
"/v1/withdraw",
|
|
2440
|
+
compactObject({
|
|
2441
|
+
currency: requireString(args, "currency"),
|
|
2442
|
+
uuid: readString(args, "uuid"),
|
|
2443
|
+
txid: readString(args, "txid")
|
|
2444
|
+
}),
|
|
2445
|
+
privateRateLimit("withdraw_get")
|
|
2446
|
+
);
|
|
2447
|
+
return normalizeResponse(response);
|
|
2448
|
+
}
|
|
2449
|
+
},
|
|
2450
|
+
// ── 11. withdraw_get_list ────────────────────────────────────────
|
|
2451
|
+
{
|
|
2452
|
+
name: "withdraw_get_list",
|
|
2453
|
+
module: "withdraw",
|
|
2454
|
+
description: "\uCD9C\uAE08 \uB9AC\uC2A4\uD2B8\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get list of coin withdrawals.",
|
|
2455
|
+
isWrite: false,
|
|
2456
|
+
inputSchema: {
|
|
2457
|
+
type: "object",
|
|
2458
|
+
properties: {
|
|
2459
|
+
currency: {
|
|
2460
|
+
type: "string",
|
|
2461
|
+
description: "Currency symbol, e.g. BTC"
|
|
2462
|
+
},
|
|
2463
|
+
state: {
|
|
2464
|
+
type: "string",
|
|
2465
|
+
enum: ["PROCESSING", "DONE", "CANCELED"],
|
|
2466
|
+
description: "Withdrawal state filter"
|
|
2467
|
+
},
|
|
2468
|
+
uuids: {
|
|
2469
|
+
type: "array",
|
|
2470
|
+
items: { type: "string" },
|
|
2471
|
+
description: "Filter by withdrawal UUIDs"
|
|
2472
|
+
},
|
|
2473
|
+
txids: {
|
|
2474
|
+
type: "array",
|
|
2475
|
+
items: { type: "string" },
|
|
2476
|
+
description: "Filter by transaction IDs"
|
|
2477
|
+
},
|
|
2478
|
+
limit: {
|
|
2479
|
+
type: "number",
|
|
2480
|
+
description: "Number of results per page (max 100)"
|
|
2481
|
+
},
|
|
2482
|
+
page: {
|
|
2483
|
+
type: "number",
|
|
2484
|
+
description: "Page number"
|
|
2485
|
+
},
|
|
2486
|
+
order_by: {
|
|
2487
|
+
type: "string",
|
|
2488
|
+
enum: ["asc", "desc"],
|
|
2489
|
+
description: "Sort order (default: desc)"
|
|
2490
|
+
}
|
|
2491
|
+
},
|
|
2492
|
+
required: []
|
|
2493
|
+
},
|
|
2494
|
+
handler: async (rawArgs, context) => {
|
|
2495
|
+
const args = asRecord(rawArgs);
|
|
2496
|
+
const response = await context.client.privateGet(
|
|
2497
|
+
"/v1/withdraws",
|
|
2498
|
+
compactObject({
|
|
2499
|
+
currency: readString(args, "currency"),
|
|
2500
|
+
state: readString(args, "state"),
|
|
2501
|
+
uuids: readStringArray(args, "uuids"),
|
|
2502
|
+
txids: readStringArray(args, "txids"),
|
|
2503
|
+
limit: readNumber(args, "limit"),
|
|
2504
|
+
page: readNumber(args, "page"),
|
|
2505
|
+
order_by: readString(args, "order_by")
|
|
2506
|
+
}),
|
|
2507
|
+
privateRateLimit("withdraw_get_list")
|
|
2508
|
+
);
|
|
2509
|
+
return normalizeResponse(response);
|
|
2510
|
+
}
|
|
2511
|
+
},
|
|
2512
|
+
// ── 12. withdraw_get_list_krw ────────────────────────────────────
|
|
2513
|
+
{
|
|
2514
|
+
name: "withdraw_get_list_krw",
|
|
2515
|
+
module: "withdraw",
|
|
2516
|
+
description: "\uC6D0\uD654 \uCD9C\uAE08 \uB9AC\uC2A4\uD2B8\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get list of KRW withdrawals.",
|
|
2517
|
+
isWrite: false,
|
|
2518
|
+
inputSchema: {
|
|
2519
|
+
type: "object",
|
|
2520
|
+
properties: {
|
|
2521
|
+
state: {
|
|
2522
|
+
type: "string",
|
|
2523
|
+
enum: ["PROCESSING", "DONE", "CANCELED"],
|
|
2524
|
+
description: "Withdrawal state filter"
|
|
2525
|
+
},
|
|
2526
|
+
uuids: {
|
|
2527
|
+
type: "array",
|
|
2528
|
+
items: { type: "string" },
|
|
2529
|
+
description: "Filter by withdrawal UUIDs"
|
|
2530
|
+
},
|
|
2531
|
+
txids: {
|
|
2532
|
+
type: "array",
|
|
2533
|
+
items: { type: "string" },
|
|
2534
|
+
description: "Filter by transaction IDs"
|
|
2535
|
+
},
|
|
2536
|
+
limit: {
|
|
2537
|
+
type: "number",
|
|
2538
|
+
description: "Number of results per page (max 100)"
|
|
2539
|
+
},
|
|
2540
|
+
page: {
|
|
2541
|
+
type: "number",
|
|
2542
|
+
description: "Page number"
|
|
2543
|
+
},
|
|
2544
|
+
order_by: {
|
|
2545
|
+
type: "string",
|
|
2546
|
+
enum: ["asc", "desc"],
|
|
2547
|
+
description: "Sort order (default: desc)"
|
|
2548
|
+
}
|
|
2549
|
+
},
|
|
2550
|
+
required: []
|
|
2551
|
+
},
|
|
2552
|
+
handler: async (rawArgs, context) => {
|
|
2553
|
+
const args = asRecord(rawArgs);
|
|
2554
|
+
const response = await context.client.privateGet(
|
|
2555
|
+
"/v1/withdraws/krw",
|
|
2556
|
+
compactObject({
|
|
2557
|
+
state: readString(args, "state"),
|
|
2558
|
+
uuids: readStringArray(args, "uuids"),
|
|
2559
|
+
txids: readStringArray(args, "txids"),
|
|
2560
|
+
limit: readNumber(args, "limit"),
|
|
2561
|
+
page: readNumber(args, "page"),
|
|
2562
|
+
order_by: readString(args, "order_by")
|
|
2563
|
+
}),
|
|
2564
|
+
privateRateLimit("withdraw_get_list_krw")
|
|
2565
|
+
);
|
|
2566
|
+
return normalizeResponse(response);
|
|
2567
|
+
}
|
|
2568
|
+
},
|
|
2569
|
+
// ── 13. withdraw_coin ────────────────────────────────────────────
|
|
2570
|
+
{
|
|
2571
|
+
name: "withdraw_coin",
|
|
2572
|
+
module: "withdraw",
|
|
2573
|
+
description: "\uC2E4\uC81C \uAC00\uC0C1 \uC790\uC0B0\uC744 \uCD9C\uAE08\uD569\uB2C8\uB2E4. \uC774 \uC791\uC5C5\uC740 \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. Withdraw cryptocurrency. This action is IRREVERSIBLE. Use when: user has explicitly approved a withdrawal AND the full pre-flight checklist has run (account_get_balance, account_get_wallet_status, withdraw_get_chance, withdraw_get_addresses, market_get_fee_inout). Do NOT use: without prior `withdraw_get_chance` to confirm net_type/min/fee; without `withdraw_get_addresses` to confirm destination is in the allow-list; without explicit user confirmation of the full destination string. Multi-network coins (USDT/USDC/XRP): always run `withdraw_get_chance` first to discover supported `net_type`. Wrong net_type = permanent loss. secondary_address (memo/tag): mandatory for XRP/EOS/ATOM-class coins. Missing it = lost funds.",
|
|
2574
|
+
isWrite: true,
|
|
2575
|
+
inputSchema: {
|
|
2576
|
+
type: "object",
|
|
2577
|
+
properties: {
|
|
2578
|
+
currency: {
|
|
2579
|
+
type: "string",
|
|
2580
|
+
description: "Currency symbol, e.g. BTC"
|
|
2581
|
+
},
|
|
2582
|
+
net_type: {
|
|
2583
|
+
type: "string",
|
|
2584
|
+
description: "Withdrawal network, e.g. BTC, DASH"
|
|
2585
|
+
},
|
|
2586
|
+
amount: {
|
|
2587
|
+
type: "string",
|
|
2588
|
+
description: "Withdrawal amount"
|
|
2589
|
+
},
|
|
2590
|
+
address: {
|
|
2591
|
+
type: "string",
|
|
2592
|
+
description: "Registered withdrawal address"
|
|
2593
|
+
},
|
|
2594
|
+
secondary_address: {
|
|
2595
|
+
type: "string",
|
|
2596
|
+
description: "Secondary address (for certain assets)"
|
|
2597
|
+
},
|
|
2598
|
+
exchange_name: {
|
|
2599
|
+
type: "string",
|
|
2600
|
+
description: "Exchange name (English)"
|
|
2601
|
+
},
|
|
2602
|
+
receiver_type: {
|
|
2603
|
+
type: "string",
|
|
2604
|
+
enum: ["personal", "corporation"],
|
|
2605
|
+
description: "Receiver type: personal or corporation"
|
|
2606
|
+
},
|
|
2607
|
+
receiver_ko_name: {
|
|
2608
|
+
type: "string",
|
|
2609
|
+
description: "Receiver Korean name"
|
|
2610
|
+
},
|
|
2611
|
+
receiver_en_name: {
|
|
2612
|
+
type: "string",
|
|
2613
|
+
description: "Receiver English name"
|
|
2614
|
+
},
|
|
2615
|
+
receiver_corp_ko_name: {
|
|
2616
|
+
type: "string",
|
|
2617
|
+
description: "Corporation Korean name (required if corporation)"
|
|
2618
|
+
},
|
|
2619
|
+
receiver_corp_en_name: {
|
|
2620
|
+
type: "string",
|
|
2621
|
+
description: "Corporation English name (required if corporation)"
|
|
2622
|
+
}
|
|
2623
|
+
},
|
|
2624
|
+
required: ["currency", "net_type", "amount", "address"]
|
|
2625
|
+
},
|
|
2626
|
+
handler: async (rawArgs, context) => {
|
|
2627
|
+
const args = asRecord(rawArgs);
|
|
2628
|
+
const response = await context.client.privatePost(
|
|
2629
|
+
"/v1/withdraws/coin",
|
|
2630
|
+
compactObject({
|
|
2631
|
+
currency: requireString(args, "currency"),
|
|
2632
|
+
net_type: requireString(args, "net_type"),
|
|
2633
|
+
amount: requireString(args, "amount"),
|
|
2634
|
+
address: requireString(args, "address"),
|
|
2635
|
+
secondary_address: readString(args, "secondary_address"),
|
|
2636
|
+
exchange_name: readString(args, "exchange_name"),
|
|
2637
|
+
receiver_type: readString(args, "receiver_type"),
|
|
2638
|
+
receiver_ko_name: readString(args, "receiver_ko_name"),
|
|
2639
|
+
receiver_en_name: readString(args, "receiver_en_name"),
|
|
2640
|
+
receiver_corp_ko_name: readString(args, "receiver_corp_ko_name"),
|
|
2641
|
+
receiver_corp_en_name: readString(args, "receiver_corp_en_name")
|
|
2642
|
+
}),
|
|
2643
|
+
orderRateLimit("withdraw_coin")
|
|
2644
|
+
);
|
|
2645
|
+
return normalizeResponse(response);
|
|
2646
|
+
}
|
|
2647
|
+
},
|
|
2648
|
+
// ── 14. withdraw_krw ─────────────────────────────────────────────
|
|
2649
|
+
{
|
|
2650
|
+
name: "withdraw_krw",
|
|
2651
|
+
module: "withdraw",
|
|
2652
|
+
description: "\uB4F1\uB85D\uB41C \uACC4\uC88C\uB85C \uC6D0\uD654\uB97C \uCD9C\uAE08\uD569\uB2C8\uB2E4. 2\uCC28 \uC778\uC99D(\uCE74\uCE74\uC624)\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. Withdraw KRW to registered bank account. Requires 2FA (Kakao).",
|
|
2653
|
+
isWrite: true,
|
|
2654
|
+
inputSchema: {
|
|
2655
|
+
type: "object",
|
|
2656
|
+
properties: {
|
|
2657
|
+
amount: {
|
|
2658
|
+
type: "string",
|
|
2659
|
+
description: "Withdrawal amount in KRW"
|
|
2660
|
+
},
|
|
2661
|
+
two_factor_type: {
|
|
2662
|
+
type: "string",
|
|
2663
|
+
description: "2FA method (e.g. kakao)"
|
|
2664
|
+
}
|
|
2665
|
+
},
|
|
2666
|
+
required: ["amount", "two_factor_type"]
|
|
2667
|
+
},
|
|
2668
|
+
handler: async (rawArgs, context) => {
|
|
2669
|
+
const args = asRecord(rawArgs);
|
|
2670
|
+
const response = await context.client.privatePost(
|
|
2671
|
+
"/v1/withdraws/krw",
|
|
2672
|
+
{
|
|
2673
|
+
amount: requireString(args, "amount"),
|
|
2674
|
+
two_factor_type: requireString(args, "two_factor_type")
|
|
2675
|
+
},
|
|
2676
|
+
orderRateLimit("withdraw_krw")
|
|
2677
|
+
);
|
|
2678
|
+
return normalizeResponse(response);
|
|
2679
|
+
}
|
|
2680
|
+
},
|
|
2681
|
+
// ── 15. withdraw_cancel_coin ─────────────────────────────────────
|
|
2682
|
+
{
|
|
2683
|
+
name: "withdraw_cancel_coin",
|
|
2684
|
+
module: "withdraw",
|
|
2685
|
+
description: "\uAC00\uC0C1 \uC790\uC0B0 \uCD9C\uAE08\uC744 \uCDE8\uC18C\uD569\uB2C8\uB2E4. Cancel a cryptocurrency withdrawal.",
|
|
2686
|
+
isWrite: true,
|
|
2687
|
+
inputSchema: {
|
|
2688
|
+
type: "object",
|
|
2689
|
+
properties: {
|
|
2690
|
+
withdrawal_id: {
|
|
2691
|
+
type: "string",
|
|
2692
|
+
description: "Withdrawal unique ID to cancel"
|
|
2693
|
+
}
|
|
2694
|
+
},
|
|
2695
|
+
required: ["withdrawal_id"]
|
|
2696
|
+
},
|
|
2697
|
+
handler: async (rawArgs, context) => {
|
|
2698
|
+
const args = asRecord(rawArgs);
|
|
2699
|
+
const response = await context.client.privateDelete(
|
|
2700
|
+
"/v1/withdraws/coin",
|
|
2701
|
+
{
|
|
2702
|
+
withdrawal_id: requireString(args, "withdrawal_id")
|
|
2703
|
+
},
|
|
2704
|
+
orderRateLimit("withdraw_cancel_coin")
|
|
2705
|
+
);
|
|
2706
|
+
return normalizeResponse(response);
|
|
2707
|
+
}
|
|
2708
|
+
},
|
|
2709
|
+
// ── 16. withdraw_get_addresses ───────────────────────────────────
|
|
2710
|
+
{
|
|
2711
|
+
name: "withdraw_get_addresses",
|
|
2712
|
+
module: "withdraw",
|
|
2713
|
+
description: "\uCD9C\uAE08 \uD5C8\uC6A9 \uC8FC\uC18C \uB9AC\uC2A4\uD2B8\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get list of allowed withdrawal addresses.",
|
|
2714
|
+
isWrite: false,
|
|
2715
|
+
inputSchema: {
|
|
2716
|
+
type: "object",
|
|
2717
|
+
properties: {},
|
|
2718
|
+
required: []
|
|
2719
|
+
},
|
|
2720
|
+
handler: async (_rawArgs, context) => {
|
|
2721
|
+
const response = await context.client.privateGet(
|
|
2722
|
+
"/v1/withdraws/coin_addresses",
|
|
2723
|
+
{},
|
|
2724
|
+
privateRateLimit("withdraw_get_addresses")
|
|
2725
|
+
);
|
|
2726
|
+
return normalizeResponse(response);
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
];
|
|
2730
|
+
}
|
|
2731
|
+
function registerDepositTools() {
|
|
2732
|
+
return [
|
|
2733
|
+
// ── 17. deposit_get ───────────────────────────────────────────
|
|
2734
|
+
{
|
|
2735
|
+
name: "deposit_get",
|
|
2736
|
+
module: "deposit",
|
|
2737
|
+
description: '\uAC1C\uBCC4 \uC785\uAE08\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. Get a single deposit by currency + uuid (or single txid). Use when: user has a specific deposit UUID and wants the full record. Do NOT use: to search by transaction id list \u2014 use `deposit_get_list` with `txids` (array). The CLI blocks `deposit get --txids` and redirects to `deposit list --txids`. Example call: {"currency":"BTC","uuid":"12345678-...."}',
|
|
2738
|
+
isWrite: false,
|
|
2739
|
+
inputSchema: {
|
|
2740
|
+
type: "object",
|
|
2741
|
+
properties: {
|
|
2742
|
+
currency: {
|
|
2743
|
+
type: "string",
|
|
2744
|
+
description: "Currency symbol, e.g. BTC"
|
|
2745
|
+
},
|
|
2746
|
+
uuid: {
|
|
2747
|
+
type: "string",
|
|
2748
|
+
description: "Deposit UUID"
|
|
2749
|
+
},
|
|
2750
|
+
txid: {
|
|
2751
|
+
type: "string",
|
|
2752
|
+
description: "Deposit TXID"
|
|
2753
|
+
}
|
|
2754
|
+
},
|
|
2755
|
+
required: ["currency"]
|
|
2756
|
+
},
|
|
2757
|
+
handler: async (rawArgs, context) => {
|
|
2758
|
+
const args = asRecord(rawArgs);
|
|
2759
|
+
const response = await context.client.privateGet(
|
|
2760
|
+
"/v1/deposit",
|
|
2761
|
+
compactObject({
|
|
2762
|
+
currency: requireString(args, "currency"),
|
|
2763
|
+
uuid: readString(args, "uuid"),
|
|
2764
|
+
txid: readString(args, "txid")
|
|
2765
|
+
}),
|
|
2766
|
+
privateRateLimit("deposit_get")
|
|
2767
|
+
);
|
|
2768
|
+
return normalizeResponse(response);
|
|
2769
|
+
}
|
|
2770
|
+
},
|
|
2771
|
+
// ── 18. deposit_get_list ──────────────────────────────────────
|
|
2772
|
+
{
|
|
2773
|
+
name: "deposit_get_list",
|
|
2774
|
+
module: "deposit",
|
|
2775
|
+
description: `\uC785\uAE08 \uB9AC\uC2A4\uD2B8\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get list of coin deposits with optional filters. Use when: user asks 'find this txid' / 'recent deposits' / 'list deposits in PROCESSING state'. Do NOT use: when you already have a deposit UUID and want a single record (use \`deposit_get\`). Example call (txid lookup): {"txids":["0xabc123..."]}`,
|
|
2776
|
+
isWrite: false,
|
|
2777
|
+
inputSchema: {
|
|
2778
|
+
type: "object",
|
|
2779
|
+
properties: {
|
|
2780
|
+
currency: {
|
|
2781
|
+
type: "string",
|
|
2782
|
+
description: "Currency symbol, e.g. BTC"
|
|
2783
|
+
},
|
|
2784
|
+
state: {
|
|
2785
|
+
type: "string",
|
|
2786
|
+
description: "Deposit state filter"
|
|
2787
|
+
},
|
|
2788
|
+
uuids: {
|
|
2789
|
+
type: "array",
|
|
2790
|
+
items: { type: "string" },
|
|
2791
|
+
description: "Filter by deposit UUIDs"
|
|
2792
|
+
},
|
|
2793
|
+
txids: {
|
|
2794
|
+
type: "array",
|
|
2795
|
+
items: { type: "string" },
|
|
2796
|
+
description: "Filter by deposit TXIDs"
|
|
2797
|
+
},
|
|
2798
|
+
limit: {
|
|
2799
|
+
type: "number",
|
|
2800
|
+
description: "Number of results (max 100)"
|
|
2801
|
+
},
|
|
2802
|
+
page: {
|
|
2803
|
+
type: "number",
|
|
2804
|
+
description: "Page number (default 1)"
|
|
2805
|
+
},
|
|
2806
|
+
order_by: {
|
|
2807
|
+
type: "string",
|
|
2808
|
+
description: "Sort order: asc or desc (default desc)"
|
|
2809
|
+
}
|
|
2810
|
+
},
|
|
2811
|
+
required: []
|
|
2812
|
+
},
|
|
2813
|
+
handler: async (rawArgs, context) => {
|
|
2814
|
+
const args = asRecord(rawArgs);
|
|
2815
|
+
const response = await context.client.privateGet(
|
|
2816
|
+
"/v1/deposits",
|
|
2817
|
+
compactObject({
|
|
2818
|
+
currency: readString(args, "currency"),
|
|
2819
|
+
state: readString(args, "state"),
|
|
2820
|
+
uuids: readStringArray(args, "uuids"),
|
|
2821
|
+
txids: readStringArray(args, "txids"),
|
|
2822
|
+
limit: readNumber(args, "limit"),
|
|
2823
|
+
page: readNumber(args, "page"),
|
|
2824
|
+
order_by: readString(args, "order_by")
|
|
2825
|
+
}),
|
|
2826
|
+
privateRateLimit("deposit_get_list")
|
|
2827
|
+
);
|
|
2828
|
+
return normalizeResponse(response);
|
|
2829
|
+
}
|
|
2830
|
+
},
|
|
2831
|
+
// ── 19. deposit_get_list_krw ──────────────────────────────────
|
|
2832
|
+
{
|
|
2833
|
+
name: "deposit_get_list_krw",
|
|
2834
|
+
module: "deposit",
|
|
2835
|
+
description: "\uC6D0\uD654 \uC785\uAE08 \uB9AC\uC2A4\uD2B8\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get list of KRW deposits.",
|
|
2836
|
+
isWrite: false,
|
|
2837
|
+
inputSchema: {
|
|
2838
|
+
type: "object",
|
|
2839
|
+
properties: {
|
|
2840
|
+
state: {
|
|
2841
|
+
type: "string",
|
|
2842
|
+
description: "Deposit state: PROCESSING, ACCEPTED, CANCELED"
|
|
2843
|
+
},
|
|
2844
|
+
uuids: {
|
|
2845
|
+
type: "array",
|
|
2846
|
+
items: { type: "string" },
|
|
2847
|
+
description: "Filter by deposit UUIDs"
|
|
2848
|
+
},
|
|
2849
|
+
txids: {
|
|
2850
|
+
type: "array",
|
|
2851
|
+
items: { type: "string" },
|
|
2852
|
+
description: "Filter by deposit TXIDs"
|
|
2853
|
+
},
|
|
2854
|
+
limit: {
|
|
2855
|
+
type: "number",
|
|
2856
|
+
description: "Number of results (max 100)"
|
|
2857
|
+
},
|
|
2858
|
+
page: {
|
|
2859
|
+
type: "number",
|
|
2860
|
+
description: "Page number (default 1)"
|
|
2861
|
+
},
|
|
2862
|
+
order_by: {
|
|
2863
|
+
type: "string",
|
|
2864
|
+
description: "Sort order: asc or desc (default desc)"
|
|
2865
|
+
}
|
|
2866
|
+
},
|
|
2867
|
+
required: []
|
|
2868
|
+
},
|
|
2869
|
+
handler: async (rawArgs, context) => {
|
|
2870
|
+
const args = asRecord(rawArgs);
|
|
2871
|
+
const response = await context.client.privateGet(
|
|
2872
|
+
"/v1/deposits/krw",
|
|
2873
|
+
compactObject({
|
|
2874
|
+
state: readString(args, "state"),
|
|
2875
|
+
uuids: readStringArray(args, "uuids"),
|
|
2876
|
+
txids: readStringArray(args, "txids"),
|
|
2877
|
+
limit: readNumber(args, "limit"),
|
|
2878
|
+
page: readNumber(args, "page"),
|
|
2879
|
+
order_by: readString(args, "order_by")
|
|
2880
|
+
}),
|
|
2881
|
+
privateRateLimit("deposit_get_list_krw")
|
|
2882
|
+
);
|
|
2883
|
+
return normalizeResponse(response);
|
|
2884
|
+
}
|
|
2885
|
+
},
|
|
2886
|
+
// ── 20. deposit_krw ──────────────────────────────────────────
|
|
2887
|
+
{
|
|
2888
|
+
name: "deposit_krw",
|
|
2889
|
+
module: "deposit",
|
|
2890
|
+
description: "\uC6D0\uD654 \uC785\uAE08\uC744 \uC694\uCCAD\uD569\uB2C8\uB2E4. 2\uCC28 \uC778\uC99D(\uCE74\uCE74\uC624)\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. Request KRW deposit. Requires 2FA (Kakao).",
|
|
2891
|
+
isWrite: true,
|
|
2892
|
+
inputSchema: {
|
|
2893
|
+
type: "object",
|
|
2894
|
+
properties: {
|
|
2895
|
+
amount: {
|
|
2896
|
+
type: "string",
|
|
2897
|
+
description: "Deposit amount in KRW"
|
|
2898
|
+
},
|
|
2899
|
+
two_factor_type: {
|
|
2900
|
+
type: "string",
|
|
2901
|
+
description: "2FA method, e.g. kakao"
|
|
2902
|
+
}
|
|
2903
|
+
},
|
|
2904
|
+
required: ["amount", "two_factor_type"]
|
|
2905
|
+
},
|
|
2906
|
+
handler: async (rawArgs, context) => {
|
|
2907
|
+
const args = asRecord(rawArgs);
|
|
2908
|
+
const response = await context.client.privatePost(
|
|
2909
|
+
"/v1/deposits/krw",
|
|
2910
|
+
{
|
|
2911
|
+
amount: requireString(args, "amount"),
|
|
2912
|
+
two_factor_type: requireString(args, "two_factor_type")
|
|
2913
|
+
},
|
|
2914
|
+
privateRateLimit("deposit_krw")
|
|
2915
|
+
);
|
|
2916
|
+
return normalizeResponse(response);
|
|
2917
|
+
}
|
|
2918
|
+
},
|
|
2919
|
+
// ── 21. deposit_generate_address ─────────────────────────────
|
|
2920
|
+
{
|
|
2921
|
+
name: "deposit_generate_address",
|
|
2922
|
+
module: "deposit",
|
|
2923
|
+
description: "\uC785\uAE08 \uC8FC\uC18C\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4. Generate a new deposit address.",
|
|
2924
|
+
isWrite: true,
|
|
2925
|
+
inputSchema: {
|
|
2926
|
+
type: "object",
|
|
2927
|
+
properties: {
|
|
2928
|
+
currency: {
|
|
2929
|
+
type: "string",
|
|
2930
|
+
description: "Currency symbol, e.g. BTC"
|
|
2931
|
+
},
|
|
2932
|
+
net_type: {
|
|
2933
|
+
type: "string",
|
|
2934
|
+
description: "Network type, e.g. BTC, ETH"
|
|
2935
|
+
}
|
|
2936
|
+
},
|
|
2937
|
+
required: ["currency", "net_type"]
|
|
2938
|
+
},
|
|
2939
|
+
handler: async (rawArgs, context) => {
|
|
2940
|
+
const args = asRecord(rawArgs);
|
|
2941
|
+
const response = await context.client.privatePost(
|
|
2942
|
+
"/v1/deposits/generate_coin_address",
|
|
2943
|
+
{
|
|
2944
|
+
currency: requireString(args, "currency"),
|
|
2945
|
+
net_type: requireString(args, "net_type")
|
|
2946
|
+
},
|
|
2947
|
+
privateRateLimit("deposit_generate_address")
|
|
2948
|
+
);
|
|
2949
|
+
return normalizeResponse(response);
|
|
2950
|
+
}
|
|
2951
|
+
},
|
|
2952
|
+
// ── 22. deposit_get_addresses ────────────────────────────────
|
|
2953
|
+
{
|
|
2954
|
+
name: "deposit_get_addresses",
|
|
2955
|
+
module: "deposit",
|
|
2956
|
+
description: "\uC804\uCCB4 \uC785\uAE08 \uC8FC\uC18C\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get all deposit addresses.",
|
|
2957
|
+
isWrite: false,
|
|
2958
|
+
inputSchema: {
|
|
2959
|
+
type: "object",
|
|
2960
|
+
properties: {},
|
|
2961
|
+
required: []
|
|
2962
|
+
},
|
|
2963
|
+
handler: async (_rawArgs, context) => {
|
|
2964
|
+
const response = await context.client.privateGet(
|
|
2965
|
+
"/v1/deposits/coin_addresses",
|
|
2966
|
+
{},
|
|
2967
|
+
privateRateLimit("deposit_get_addresses")
|
|
2968
|
+
);
|
|
2969
|
+
return normalizeResponse(response);
|
|
2970
|
+
}
|
|
2971
|
+
},
|
|
2972
|
+
// ── 23. deposit_get_address ──────────────────────────────────
|
|
2973
|
+
{
|
|
2974
|
+
name: "deposit_get_address",
|
|
2975
|
+
module: "deposit",
|
|
2976
|
+
description: "\uAC1C\uBCC4 \uC785\uAE08 \uC8FC\uC18C\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. Get deposit address for a specific currency and network.",
|
|
2977
|
+
isWrite: false,
|
|
2978
|
+
inputSchema: {
|
|
2979
|
+
type: "object",
|
|
2980
|
+
properties: {
|
|
2981
|
+
currency: {
|
|
2982
|
+
type: "string",
|
|
2983
|
+
description: "Currency symbol, e.g. BTC"
|
|
2984
|
+
},
|
|
2985
|
+
net_type: {
|
|
2986
|
+
type: "string",
|
|
2987
|
+
description: "Network type, e.g. BTC, ETH"
|
|
2988
|
+
}
|
|
2989
|
+
},
|
|
2990
|
+
required: ["currency", "net_type"]
|
|
2991
|
+
},
|
|
2992
|
+
handler: async (rawArgs, context) => {
|
|
2993
|
+
const args = asRecord(rawArgs);
|
|
2994
|
+
const response = await context.client.privateGet(
|
|
2995
|
+
"/v1/deposits/coin_address",
|
|
2996
|
+
{
|
|
2997
|
+
currency: requireString(args, "currency"),
|
|
2998
|
+
net_type: requireString(args, "net_type")
|
|
2999
|
+
},
|
|
3000
|
+
privateRateLimit("deposit_get_address")
|
|
3001
|
+
);
|
|
3002
|
+
return normalizeResponse(response);
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
];
|
|
3006
|
+
}
|
|
3007
|
+
function allToolSpecs() {
|
|
3008
|
+
return [
|
|
3009
|
+
...registerMarketTools(),
|
|
3010
|
+
...registerAccountTools(),
|
|
3011
|
+
...registerTradeTools(),
|
|
3012
|
+
...registerAuditTools(),
|
|
3013
|
+
...registerDiagnoseTools(),
|
|
3014
|
+
...registerTwapTools(),
|
|
3015
|
+
...registerWithdrawTools(),
|
|
3016
|
+
...registerDepositTools()
|
|
3017
|
+
];
|
|
3018
|
+
}
|
|
3019
|
+
function createToolRunner(client, config) {
|
|
3020
|
+
const tools = allToolSpecs();
|
|
3021
|
+
const toolMap = new Map(
|
|
3022
|
+
tools.map((t) => [t.name, t])
|
|
3023
|
+
);
|
|
3024
|
+
return async (toolName, args) => {
|
|
3025
|
+
const tool = toolMap.get(toolName);
|
|
3026
|
+
if (!tool) throw new Error(`Unknown tool: ${toolName}`);
|
|
3027
|
+
return await tool.handler(args, { config, client });
|
|
3028
|
+
};
|
|
3029
|
+
}
|
|
3030
|
+
function configFilePath() {
|
|
3031
|
+
return join2(homedir2(), ".bithumb", "config.toml");
|
|
3032
|
+
}
|
|
3033
|
+
function readFullConfig() {
|
|
3034
|
+
const path3 = configFilePath();
|
|
3035
|
+
if (!existsSync2(path3)) return { profiles: {} };
|
|
3036
|
+
const raw = readFileSync(path3, "utf-8");
|
|
3037
|
+
try {
|
|
3038
|
+
return parse(raw);
|
|
3039
|
+
} catch (err) {
|
|
3040
|
+
throw new ConfigError(
|
|
3041
|
+
`Failed to parse ${path3}: ${err instanceof Error ? err.message : String(err)}`,
|
|
3042
|
+
"Check TOML syntax in your config file, or delete and re-create it."
|
|
3043
|
+
);
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
function readTomlProfile(profileName) {
|
|
3047
|
+
const config = readFullConfig();
|
|
3048
|
+
const name = profileName ?? config.default_profile ?? "default";
|
|
3049
|
+
return config.profiles?.[name] ?? {};
|
|
3050
|
+
}
|
|
3051
|
+
var CONFIG_HEADER = "# Bithumb Trade Kit Configuration\n# Wrap values containing special chars in quotes\n\n";
|
|
3052
|
+
function writeFullConfig(config) {
|
|
3053
|
+
const path3 = configFilePath();
|
|
3054
|
+
const dir = dirname(path3);
|
|
3055
|
+
if (!existsSync2(dir)) {
|
|
3056
|
+
mkdirSync(dir, { recursive: true });
|
|
3057
|
+
}
|
|
3058
|
+
writeFileSync(path3, CONFIG_HEADER + stringify(config), "utf-8");
|
|
3059
|
+
}
|
|
3060
|
+
function loadConfig(options) {
|
|
3061
|
+
const toml = readTomlProfile(options?.profile);
|
|
3062
|
+
const accessKey = process.env.BITHUMB_ACCESS_KEY?.trim() ?? toml.access_key;
|
|
3063
|
+
const secretKey = process.env.BITHUMB_SECRET_KEY?.trim() ?? toml.secret_key;
|
|
3064
|
+
const hasAuth = Boolean(accessKey && secretKey);
|
|
3065
|
+
const partialAuth = Boolean(accessKey) || Boolean(secretKey);
|
|
3066
|
+
if (partialAuth && !hasAuth) {
|
|
3067
|
+
throw new ConfigError(
|
|
3068
|
+
"Partial API credentials.",
|
|
3069
|
+
"Set both BITHUMB_ACCESS_KEY and BITHUMB_SECRET_KEY (env vars or config.toml profile)."
|
|
3070
|
+
);
|
|
3071
|
+
}
|
|
3072
|
+
const baseUrl = (process.env.BITHUMB_API_BASE_URL?.trim() ?? toml.base_url ?? BITHUMB_API_BASE_URL).replace(/\/+$/, "");
|
|
3073
|
+
const rawTimeout = process.env.BITHUMB_TIMEOUT_MS ? Number(process.env.BITHUMB_TIMEOUT_MS) : toml.timeout_ms ?? 15e3;
|
|
3074
|
+
if (!Number.isFinite(rawTimeout) || rawTimeout <= 0) {
|
|
3075
|
+
throw new ConfigError(
|
|
3076
|
+
"Invalid timeout.",
|
|
3077
|
+
"BITHUMB_TIMEOUT_MS must be a positive integer."
|
|
3078
|
+
);
|
|
3079
|
+
}
|
|
3080
|
+
let modules = [...DEFAULT_MODULES];
|
|
3081
|
+
if (options?.modules) {
|
|
3082
|
+
const requested = options.modules.split(",").map((s) => s.trim()).filter(Boolean);
|
|
3083
|
+
if (requested.length > 0) {
|
|
3084
|
+
let isAll = false;
|
|
3085
|
+
for (const m of requested) {
|
|
3086
|
+
if (m === "all") {
|
|
3087
|
+
modules = [...MODULES];
|
|
3088
|
+
isAll = true;
|
|
3089
|
+
break;
|
|
3090
|
+
}
|
|
3091
|
+
if (!MODULES.includes(m)) {
|
|
3092
|
+
throw new ConfigError(
|
|
3093
|
+
`Unknown module "${m}".`,
|
|
3094
|
+
`Use: ${MODULES.join(", ")} or "all".`
|
|
3095
|
+
);
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
if (!isAll) modules = requested;
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
return {
|
|
3102
|
+
accessKey,
|
|
3103
|
+
secretKey,
|
|
3104
|
+
hasAuth,
|
|
3105
|
+
baseUrl,
|
|
3106
|
+
timeoutMs: Math.floor(rawTimeout),
|
|
3107
|
+
modules,
|
|
3108
|
+
readOnly: options?.readOnly ?? false,
|
|
3109
|
+
verbose: options?.verbose ?? false
|
|
3110
|
+
};
|
|
3111
|
+
}
|
|
3112
|
+
var LEVEL_ORDER = {
|
|
3113
|
+
debug: 0,
|
|
3114
|
+
info: 1,
|
|
3115
|
+
warn: 2,
|
|
3116
|
+
error: 3
|
|
3117
|
+
};
|
|
3118
|
+
var SENSITIVE_KEY_PATTERN = /accessKey|secretKey|password|secret|token|jwt/i;
|
|
3119
|
+
function redactSensitive(obj) {
|
|
3120
|
+
const result = {};
|
|
3121
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
3122
|
+
if (SENSITIVE_KEY_PATTERN.test(key)) {
|
|
3123
|
+
result[key] = "***REDACTED***";
|
|
3124
|
+
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
3125
|
+
result[key] = redactSensitive(value);
|
|
3126
|
+
} else {
|
|
3127
|
+
result[key] = value;
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
return result;
|
|
3131
|
+
}
|
|
3132
|
+
function todayDateString() {
|
|
3133
|
+
const d = /* @__PURE__ */ new Date();
|
|
3134
|
+
const yyyy = d.getFullYear();
|
|
3135
|
+
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
3136
|
+
const dd = String(d.getDate()).padStart(2, "0");
|
|
3137
|
+
return `${yyyy}-${mm}-${dd}`;
|
|
3138
|
+
}
|
|
3139
|
+
var TradeLogger = class {
|
|
3140
|
+
logDir;
|
|
3141
|
+
minLevel;
|
|
3142
|
+
verbose;
|
|
3143
|
+
constructor(minLevelOrOptions) {
|
|
3144
|
+
if (typeof minLevelOrOptions === "string") {
|
|
3145
|
+
this.logDir = join3(homedir3(), ".bithumb", "logs");
|
|
3146
|
+
this.minLevel = minLevelOrOptions;
|
|
3147
|
+
this.verbose = false;
|
|
3148
|
+
} else {
|
|
3149
|
+
this.logDir = minLevelOrOptions?.logDir ?? join3(homedir3(), ".bithumb", "logs");
|
|
3150
|
+
this.minLevel = minLevelOrOptions?.minLevel ?? "info";
|
|
3151
|
+
this.verbose = minLevelOrOptions?.verbose ?? false;
|
|
3152
|
+
}
|
|
3153
|
+
try {
|
|
3154
|
+
mkdirSync2(this.logDir, { recursive: true });
|
|
3155
|
+
} catch {
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
debug(message, meta) {
|
|
3159
|
+
this.log("debug", message, meta);
|
|
3160
|
+
}
|
|
3161
|
+
info(message, meta) {
|
|
3162
|
+
this.log("info", message, meta);
|
|
3163
|
+
}
|
|
3164
|
+
warn(message, meta) {
|
|
3165
|
+
this.log("warn", message, meta);
|
|
3166
|
+
}
|
|
3167
|
+
error(message, meta) {
|
|
3168
|
+
this.log("error", message, meta);
|
|
3169
|
+
}
|
|
3170
|
+
/** Log a tool invocation result (used by MCP server). */
|
|
3171
|
+
logTool(level, toolName, args, result, elapsedMs) {
|
|
3172
|
+
this.log(level, `tool:${toolName}`, {
|
|
3173
|
+
args,
|
|
3174
|
+
result,
|
|
3175
|
+
elapsedMs
|
|
3176
|
+
});
|
|
3177
|
+
}
|
|
3178
|
+
log(level, message, meta) {
|
|
3179
|
+
if (LEVEL_ORDER[level] < LEVEL_ORDER[this.minLevel]) return;
|
|
3180
|
+
const entry = {
|
|
3181
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3182
|
+
level,
|
|
3183
|
+
message,
|
|
3184
|
+
...meta ? redactSensitive(meta) : {}
|
|
3185
|
+
};
|
|
3186
|
+
const line = JSON.stringify(entry);
|
|
3187
|
+
if (this.verbose || level === "error") {
|
|
3188
|
+
process.stderr.write(`[${level}] ${message}
|
|
3189
|
+
`);
|
|
3190
|
+
}
|
|
3191
|
+
try {
|
|
3192
|
+
const filePath = join3(this.logDir, `trade-${todayDateString()}.log`);
|
|
3193
|
+
appendFileSync(filePath, line + "\n", "utf8");
|
|
3194
|
+
} catch {
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
};
|
|
3198
|
+
var CACHE_DIR = join4(homedir4(), ".bithumb");
|
|
3199
|
+
var CACHE_FILE = join4(CACHE_DIR, "update-check.json");
|
|
3200
|
+
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
3201
|
+
function isNewerVersion(current, latest) {
|
|
3202
|
+
const parse2 = (v) => v.replace(/^v/, "").split(".").map((n) => parseInt(n, 10) || 0);
|
|
3203
|
+
const cur = parse2(current);
|
|
3204
|
+
const lat = parse2(latest);
|
|
3205
|
+
for (let i = 0; i < Math.max(cur.length, lat.length); i++) {
|
|
3206
|
+
const c = cur[i] ?? 0;
|
|
3207
|
+
const l = lat[i] ?? 0;
|
|
3208
|
+
if (l > c) return true;
|
|
3209
|
+
if (l < c) return false;
|
|
3210
|
+
}
|
|
3211
|
+
return false;
|
|
3212
|
+
}
|
|
3213
|
+
async function fetchDistTags(packageName) {
|
|
3214
|
+
try {
|
|
3215
|
+
const url = `https://registry.npmjs.org/-/package/${encodeURIComponent(packageName)}/dist-tags`;
|
|
3216
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(5e3) });
|
|
3217
|
+
if (!res.ok) return null;
|
|
3218
|
+
return await res.json();
|
|
3219
|
+
} catch {
|
|
3220
|
+
return null;
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
async function fetchLatestVersion(packageName) {
|
|
3224
|
+
const tags = await fetchDistTags(packageName);
|
|
3225
|
+
return tags?.latest ?? null;
|
|
3226
|
+
}
|
|
3227
|
+
function readCache() {
|
|
3228
|
+
try {
|
|
3229
|
+
if (!existsSync3(CACHE_FILE)) return null;
|
|
3230
|
+
const raw = readFileSync2(CACHE_FILE, "utf8");
|
|
3231
|
+
return JSON.parse(raw);
|
|
3232
|
+
} catch {
|
|
3233
|
+
return null;
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
function writeCache(data) {
|
|
3237
|
+
try {
|
|
3238
|
+
if (!existsSync3(CACHE_DIR)) {
|
|
3239
|
+
mkdirSync3(CACHE_DIR, { recursive: true });
|
|
3240
|
+
}
|
|
3241
|
+
writeFileSync2(CACHE_FILE, JSON.stringify(data), "utf8");
|
|
3242
|
+
} catch {
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
function isCacheStale(cache) {
|
|
3246
|
+
return Date.now() - cache.checkedAt > CACHE_TTL_MS;
|
|
3247
|
+
}
|
|
3248
|
+
function checkForUpdates(packageName, currentVersion) {
|
|
3249
|
+
try {
|
|
3250
|
+
const cache = readCache();
|
|
3251
|
+
if (cache && !isCacheStale(cache)) {
|
|
3252
|
+
if (isNewerVersion(currentVersion, cache.latestVersion)) {
|
|
3253
|
+
process.stderr.write(
|
|
3254
|
+
`
|
|
3255
|
+
Update available: ${currentVersion} \u2192 ${cache.latestVersion}
|
|
3256
|
+
Run: npm install -g ${packageName}
|
|
3257
|
+
|
|
3258
|
+
`
|
|
3259
|
+
);
|
|
3260
|
+
}
|
|
3261
|
+
return;
|
|
3262
|
+
}
|
|
3263
|
+
setImmediate(() => {
|
|
3264
|
+
fetchLatestVersion(packageName).then((latest) => {
|
|
3265
|
+
if (latest) {
|
|
3266
|
+
writeCache({ latestVersion: latest, checkedAt: Date.now() });
|
|
3267
|
+
if (isNewerVersion(currentVersion, latest)) {
|
|
3268
|
+
process.stderr.write(
|
|
3269
|
+
`
|
|
3270
|
+
Update available: ${currentVersion} \u2192 ${latest}
|
|
3271
|
+
Run: npm install -g ${packageName}
|
|
3272
|
+
|
|
3273
|
+
`
|
|
3274
|
+
);
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
}).catch(() => {
|
|
3278
|
+
});
|
|
3279
|
+
});
|
|
3280
|
+
} catch {
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
var CLIENT_NAMES = {
|
|
3284
|
+
"claude-desktop": "Claude Desktop",
|
|
3285
|
+
cursor: "Cursor",
|
|
3286
|
+
windsurf: "Windsurf",
|
|
3287
|
+
vscode: "VS Code",
|
|
3288
|
+
"claude-code": "Claude Code CLI"
|
|
3289
|
+
};
|
|
3290
|
+
var SUPPORTED_CLIENTS = Object.keys(CLIENT_NAMES);
|
|
3291
|
+
function appData() {
|
|
3292
|
+
return process.env.APPDATA ?? path2.join(os2.homedir(), "AppData", "Roaming");
|
|
3293
|
+
}
|
|
3294
|
+
var CLAUDE_CONFIG_FILE = "claude_desktop_config.json";
|
|
3295
|
+
function findMsStoreClaudePath() {
|
|
3296
|
+
const localAppData = process.env.LOCALAPPDATA ?? path2.join(os2.homedir(), "AppData", "Local");
|
|
3297
|
+
const packagesDir = path2.join(localAppData, "Packages");
|
|
3298
|
+
try {
|
|
3299
|
+
const entries = fs2.readdirSync(packagesDir);
|
|
3300
|
+
const claudePkg = entries.find((e) => e.startsWith("Claude_"));
|
|
3301
|
+
if (claudePkg) {
|
|
3302
|
+
const configPath = path2.join(
|
|
3303
|
+
packagesDir,
|
|
3304
|
+
claudePkg,
|
|
3305
|
+
"LocalCache",
|
|
3306
|
+
"Roaming",
|
|
3307
|
+
"Claude",
|
|
3308
|
+
CLAUDE_CONFIG_FILE
|
|
3309
|
+
);
|
|
3310
|
+
if (fs2.existsSync(configPath) || fs2.existsSync(path2.dirname(configPath))) {
|
|
3311
|
+
return configPath;
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
} catch {
|
|
3315
|
+
}
|
|
3316
|
+
return null;
|
|
3317
|
+
}
|
|
3318
|
+
function getConfigPath(client) {
|
|
3319
|
+
const home = os2.homedir();
|
|
3320
|
+
const platform = process.platform;
|
|
3321
|
+
switch (client) {
|
|
3322
|
+
case "claude-desktop":
|
|
3323
|
+
if (platform === "win32") {
|
|
3324
|
+
return findMsStoreClaudePath() ?? path2.join(appData(), "Claude", CLAUDE_CONFIG_FILE);
|
|
3325
|
+
}
|
|
3326
|
+
if (platform === "darwin") {
|
|
3327
|
+
return path2.join(home, "Library", "Application Support", "Claude", CLAUDE_CONFIG_FILE);
|
|
3328
|
+
}
|
|
3329
|
+
return path2.join(process.env.XDG_CONFIG_HOME ?? path2.join(home, ".config"), "Claude", CLAUDE_CONFIG_FILE);
|
|
3330
|
+
case "cursor":
|
|
3331
|
+
return path2.join(home, ".cursor", "mcp.json");
|
|
3332
|
+
case "windsurf":
|
|
3333
|
+
return path2.join(home, ".codeium", "windsurf", "mcp_config.json");
|
|
3334
|
+
case "vscode":
|
|
3335
|
+
return path2.join(process.cwd(), ".mcp.json");
|
|
3336
|
+
case "claude-code":
|
|
3337
|
+
return null;
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
var NPX_PACKAGE = "@bithumb-tradekit/mcp";
|
|
3341
|
+
function buildEntry(client, args) {
|
|
3342
|
+
if (client === "vscode") {
|
|
3343
|
+
return { type: "stdio", command: "bithumb-trade-mcp", args };
|
|
3344
|
+
}
|
|
3345
|
+
return { command: "npx", args: ["-y", NPX_PACKAGE, ...args] };
|
|
3346
|
+
}
|
|
3347
|
+
function buildArgs(options) {
|
|
3348
|
+
const args = [];
|
|
3349
|
+
if (options.profile) args.push("--profile", options.profile);
|
|
3350
|
+
args.push("--modules", options.modules ?? "all");
|
|
3351
|
+
return args;
|
|
3352
|
+
}
|
|
3353
|
+
function mergeJsonConfig(configPath, serverName, entry) {
|
|
3354
|
+
const dir = path2.dirname(configPath);
|
|
3355
|
+
if (!fs2.existsSync(dir)) fs2.mkdirSync(dir, { recursive: true });
|
|
3356
|
+
let data = {};
|
|
3357
|
+
if (fs2.existsSync(configPath)) {
|
|
3358
|
+
const raw = fs2.readFileSync(configPath, "utf-8");
|
|
3359
|
+
try {
|
|
3360
|
+
data = JSON.parse(raw);
|
|
3361
|
+
} catch {
|
|
3362
|
+
throw new Error(`Failed to parse existing config at ${configPath}`);
|
|
3363
|
+
}
|
|
3364
|
+
const backupPath = configPath + ".bak";
|
|
3365
|
+
fs2.copyFileSync(configPath, backupPath);
|
|
3366
|
+
process.stdout.write(` Backup \u2192 ${backupPath}
|
|
3367
|
+
`);
|
|
3368
|
+
}
|
|
3369
|
+
if (typeof data.mcpServers !== "object" || data.mcpServers === null) {
|
|
3370
|
+
data.mcpServers = {};
|
|
3371
|
+
}
|
|
3372
|
+
data.mcpServers[serverName] = entry;
|
|
3373
|
+
fs2.writeFileSync(configPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
3374
|
+
}
|
|
3375
|
+
function printSetupUsage() {
|
|
3376
|
+
process.stdout.write(
|
|
3377
|
+
`Usage: bithumb-trade-mcp setup --client <client> [--profile <name>] [--modules <list>]
|
|
3378
|
+
|
|
3379
|
+
Clients:
|
|
3380
|
+
` + SUPPORTED_CLIENTS.map((id) => ` ${id.padEnd(16)} ${CLIENT_NAMES[id]}`).join("\n") + `
|
|
3381
|
+
|
|
3382
|
+
Options:
|
|
3383
|
+
--profile <name> Profile from ${configFilePath()} (default: uses default_profile)
|
|
3384
|
+
--modules <list> Comma-separated modules or "all" (default: all)
|
|
3385
|
+
`
|
|
3386
|
+
);
|
|
3387
|
+
}
|
|
3388
|
+
function runSetup(options) {
|
|
3389
|
+
const { client } = options;
|
|
3390
|
+
const name = CLIENT_NAMES[client];
|
|
3391
|
+
const args = buildArgs(options);
|
|
3392
|
+
const serverName = options.profile ? `bithumb-trade-mcp-${options.profile}` : "bithumb-trade-mcp";
|
|
3393
|
+
if (client === "claude-code") {
|
|
3394
|
+
const claudeArgs = [
|
|
3395
|
+
"mcp",
|
|
3396
|
+
"add",
|
|
3397
|
+
"--transport",
|
|
3398
|
+
"stdio",
|
|
3399
|
+
serverName,
|
|
3400
|
+
"--",
|
|
3401
|
+
"bithumb-trade-mcp",
|
|
3402
|
+
...args
|
|
3403
|
+
];
|
|
3404
|
+
process.stdout.write(`Running: claude ${claudeArgs.join(" ")}
|
|
3405
|
+
`);
|
|
3406
|
+
execFileSync("claude", claudeArgs, { stdio: "inherit" });
|
|
3407
|
+
process.stdout.write(`\u2713 Configured ${name}
|
|
3408
|
+
`);
|
|
3409
|
+
return;
|
|
3410
|
+
}
|
|
3411
|
+
const configPath = getConfigPath(client);
|
|
3412
|
+
if (!configPath) {
|
|
3413
|
+
throw new Error(`${name} is not supported on this platform`);
|
|
3414
|
+
}
|
|
3415
|
+
const entry = buildEntry(client, args);
|
|
3416
|
+
mergeJsonConfig(configPath, serverName, entry);
|
|
3417
|
+
process.stdout.write(
|
|
3418
|
+
`\u2713 Configured ${name}
|
|
3419
|
+
${configPath}
|
|
3420
|
+
Server args: ${args.join(" ")}
|
|
3421
|
+
`
|
|
3422
|
+
);
|
|
3423
|
+
if (client !== "vscode") {
|
|
3424
|
+
process.stdout.write(` Restart ${name} to apply changes.
|
|
3425
|
+
`);
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3429
|
+
export {
|
|
3430
|
+
BithumbApiError,
|
|
3431
|
+
BithumbRestClient,
|
|
3432
|
+
createToolRunner,
|
|
3433
|
+
configFilePath,
|
|
3434
|
+
readFullConfig,
|
|
3435
|
+
writeFullConfig,
|
|
3436
|
+
loadConfig,
|
|
3437
|
+
TradeLogger,
|
|
3438
|
+
checkForUpdates,
|
|
3439
|
+
SUPPORTED_CLIENTS,
|
|
3440
|
+
printSetupUsage,
|
|
3441
|
+
runSetup
|
|
3442
|
+
};
|
|
3443
|
+
/*! Bundled license information:
|
|
3444
|
+
|
|
3445
|
+
smol-toml/dist/error.js:
|
|
3446
|
+
smol-toml/dist/util.js:
|
|
3447
|
+
smol-toml/dist/date.js:
|
|
3448
|
+
smol-toml/dist/primitive.js:
|
|
3449
|
+
smol-toml/dist/extract.js:
|
|
3450
|
+
smol-toml/dist/struct.js:
|
|
3451
|
+
smol-toml/dist/parse.js:
|
|
3452
|
+
smol-toml/dist/stringify.js:
|
|
3453
|
+
smol-toml/dist/index.js:
|
|
3454
|
+
(*!
|
|
3455
|
+
* Copyright (c) Squirrel Chat et al., All rights reserved.
|
|
3456
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
3457
|
+
*
|
|
3458
|
+
* Redistribution and use in source and binary forms, with or without
|
|
3459
|
+
* modification, are permitted provided that the following conditions are met:
|
|
3460
|
+
*
|
|
3461
|
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
3462
|
+
* list of conditions and the following disclaimer.
|
|
3463
|
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
3464
|
+
* this list of conditions and the following disclaimer in the
|
|
3465
|
+
* documentation and/or other materials provided with the distribution.
|
|
3466
|
+
* 3. Neither the name of the copyright holder nor the names of its contributors
|
|
3467
|
+
* may be used to endorse or promote products derived from this software without
|
|
3468
|
+
* specific prior written permission.
|
|
3469
|
+
*
|
|
3470
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
3471
|
+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
3472
|
+
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
3473
|
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
3474
|
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
3475
|
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
3476
|
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
3477
|
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
3478
|
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
3479
|
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
3480
|
+
*)
|
|
3481
|
+
*/
|
|
3482
|
+
//# sourceMappingURL=chunk-6NIRYFQU.js.map
|