gate-wallet-cli 1.0.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.
@@ -0,0 +1,1152 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import ora from "ora";
4
+ import { getMcpClient, getMcpClientSync, getServerUrl, } from "../core/mcp-client.js";
5
+ import { openBrowser } from "../core/oauth.js";
6
+ import { saveAuth, loadAuth, clearAuth, getAuthFilePath, } from "../core/token-store.js";
7
+ export function registerAuthCommands(program) {
8
+ program
9
+ .command("login")
10
+ .description("Login (opens browser)")
11
+ .option("--google", "Use Google OAuth instead of Gate")
12
+ .action(async function (opts) {
13
+ const serverUrl = getServerUrl();
14
+ const stored = loadAuth();
15
+ if (stored) {
16
+ const connectSpinner = ora("Restoring previous session...").start();
17
+ try {
18
+ const mcp = await getMcpClient({ serverUrl });
19
+ mcp.setMcpToken(stored.mcp_token);
20
+ connectSpinner.succeed("Already logged in (session restored from disk)");
21
+ if (stored.user_id)
22
+ console.log(chalk.green(` User ID: ${stored.user_id}`));
23
+ console.log(chalk.green(` Provider: ${stored.provider}`));
24
+ console.log(chalk.gray(` Run ${chalk.white("logout")} to switch accounts.`));
25
+ return;
26
+ }
27
+ catch {
28
+ connectSpinner.warn("Stored session invalid, starting fresh login...");
29
+ clearAuth();
30
+ }
31
+ }
32
+ try {
33
+ const connectSpinner = ora("Connecting to MCP Server...").start();
34
+ const mcp = await getMcpClient({ serverUrl });
35
+ connectSpinner.succeed("MCP Server connected");
36
+ if (opts.google) {
37
+ await loginGoogleViaRest(mcp, serverUrl);
38
+ return;
39
+ }
40
+ await loginGateViaRest(mcp, serverUrl);
41
+ }
42
+ catch (err) {
43
+ console.error(chalk.red(`Error: ${err.message}`));
44
+ }
45
+ });
46
+ program
47
+ .command("status")
48
+ .description("Show connection and auth status")
49
+ .action(async function () {
50
+ const mcp = getMcpClientSync();
51
+ const stored = loadAuth();
52
+ if (mcp?.isAuthenticated()) {
53
+ console.log(chalk.green("MCP: connected & authenticated"));
54
+ return;
55
+ }
56
+ if (stored) {
57
+ console.log(chalk.green("Auth: token found on disk"));
58
+ console.log(` Provider: ${stored.provider}`);
59
+ if (stored.user_id)
60
+ console.log(` User ID: ${stored.user_id}`);
61
+ if (stored.expires_at) {
62
+ const remaining = Math.max(0, stored.expires_at - Date.now());
63
+ const days = Math.floor(remaining / 86_400_000);
64
+ console.log(` Expires in: ${days} days`);
65
+ }
66
+ console.log(chalk.gray(` File: ${getAuthFilePath()}`));
67
+ }
68
+ else {
69
+ console.log(chalk.yellow("Not logged in."));
70
+ console.log(chalk.gray(` Run ${chalk.white("login")} or ${chalk.white("login --google")} to get started.`));
71
+ }
72
+ });
73
+ program
74
+ .command("logout")
75
+ .description("Logout and clear token")
76
+ .action(async () => {
77
+ const mcp = getMcpClientSync();
78
+ if (mcp?.isAuthenticated()) {
79
+ try {
80
+ await mcp.authLogout();
81
+ }
82
+ catch {
83
+ // best-effort server-side logout
84
+ }
85
+ }
86
+ clearAuth();
87
+ console.log(chalk.gray("Logged out. Token cleared."));
88
+ });
89
+ program
90
+ .command("tools")
91
+ .description("List available MCP tools")
92
+ .action(async function () {
93
+ const mcp = await getMcpClient();
94
+ const result = await mcp.listTools();
95
+ console.log(chalk.bold(`MCP Tools (${result.tools.length}):\n`));
96
+ for (const tool of result.tools) {
97
+ console.log(` ${chalk.cyan(tool.name.padEnd(40))} ${chalk.gray(tool.description ?? "")}`);
98
+ }
99
+ });
100
+ program
101
+ .command("call <tool> [json]")
102
+ .description("Call an MCP tool directly (for testing)")
103
+ .action(async function (tool, json) {
104
+ try {
105
+ const mcp = await getMcpClient();
106
+ const args = json ? JSON.parse(json) : {};
107
+ const result = await mcp.callTool(tool, args);
108
+ if ("content" in result && Array.isArray(result.content)) {
109
+ for (const item of result.content) {
110
+ if (item.type === "text") {
111
+ try {
112
+ const parsed = JSON.parse(item.text);
113
+ console.log(JSON.stringify(parsed, null, 2));
114
+ }
115
+ catch {
116
+ console.log(item.text);
117
+ }
118
+ }
119
+ }
120
+ }
121
+ else {
122
+ console.log(JSON.stringify(result, null, 2));
123
+ }
124
+ }
125
+ catch (err) {
126
+ console.error(chalk.red(err.message));
127
+ }
128
+ });
129
+ }
130
+ /** 确保 MCP 已连接且已认证,返回 client */
131
+ async function ensureAuthedMcp() {
132
+ const mcp = await getMcpClient();
133
+ if (!mcp.isAuthenticated()) {
134
+ const stored = loadAuth();
135
+ if (stored) {
136
+ mcp.setMcpToken(stored.mcp_token);
137
+ }
138
+ else {
139
+ throw new Error("Not logged in. Run: login");
140
+ }
141
+ }
142
+ return mcp;
143
+ }
144
+ /** 从 callTool 返回的 MCP content 中提取 JSON 对象 */
145
+ function extractToolJson(result) {
146
+ if ("content" in result && Array.isArray(result.content)) {
147
+ for (const item of result.content) {
148
+ if (item.type === "text") {
149
+ try {
150
+ return JSON.parse(item.text);
151
+ }
152
+ catch {
153
+ // not JSON
154
+ }
155
+ }
156
+ }
157
+ }
158
+ return result;
159
+ }
160
+ /** 格式化打印 MCP tool 返回结果 */
161
+ function printToolResult(result) {
162
+ if ("content" in result && Array.isArray(result.content)) {
163
+ for (const item of result.content) {
164
+ if (item.type === "text") {
165
+ try {
166
+ const parsed = JSON.parse(item.text);
167
+ console.log(JSON.stringify(parsed, null, 2));
168
+ }
169
+ catch {
170
+ console.log(item.text);
171
+ }
172
+ }
173
+ }
174
+ }
175
+ else {
176
+ console.log(JSON.stringify(result, null, 2));
177
+ }
178
+ }
179
+ /** 注册顶级快捷命令 — 覆盖所有 MCP tools */
180
+ export function registerShortcutCommands(program) {
181
+ /** 注册一个简单快捷命令的工厂函数 */
182
+ function shortcut(name, desc, toolName, buildArgs, options, positionalDef) {
183
+ const cmdDef = positionalDef ? `${name} ${positionalDef}` : name;
184
+ const cmd = program.command(cmdDef).description(desc);
185
+ if (options) {
186
+ for (const [f, d, dv] of options) {
187
+ if (dv !== undefined)
188
+ cmd.option(f, d, dv);
189
+ else
190
+ cmd.option(f, d);
191
+ }
192
+ }
193
+ cmd.action(async function (...actionArgs) {
194
+ try {
195
+ const mcp = await ensureAuthedMcp();
196
+ let args = {};
197
+ if (buildArgs) {
198
+ const positional = [];
199
+ let opts = {};
200
+ for (const a of actionArgs) {
201
+ if (typeof a === "string")
202
+ positional.push(a);
203
+ else if (a && typeof a === "object" && !(a instanceof Command))
204
+ opts = a;
205
+ }
206
+ args = await buildArgs(opts, positional, mcp);
207
+ }
208
+ const result = await mcp.callTool(toolName, args);
209
+ printToolResult(result);
210
+ }
211
+ catch (err) {
212
+ console.error(chalk.red(err.message));
213
+ }
214
+ });
215
+ }
216
+ // ─── Wallet ──────────────────────────────────────────────
217
+ shortcut("balance", "查询总资产余额", "wallet.get_total_asset");
218
+ shortcut("address", "查询钱包地址", "wallet.get_addresses");
219
+ shortcut("tokens", "查询 token 列表和余额", "wallet.get_token_list");
220
+ shortcut("sign-msg", "签名消息 (必须为 32 位 hex 字符串,如 aabbccddeeff00112233445566778899)", "wallet.sign_message", (opts, pos) => {
221
+ const msg = pos[0] ?? "";
222
+ if (!/^[0-9a-fA-F]{32}$/.test(msg)) {
223
+ throw new Error("message 必须为 32 位十六进制字符串 (16 bytes),例如: aabbccddeeff00112233445566778899");
224
+ }
225
+ return {
226
+ message: msg,
227
+ chain: (opts.chain ?? "EVM").toUpperCase(),
228
+ };
229
+ }, [["--chain <chain>", "链类型: EVM | SOL", "EVM"]], "<message>");
230
+ shortcut("sign-tx", "签名原始交易", "wallet.sign_transaction", (_opts, pos) => ({ raw_tx: pos[0] }), undefined, "<raw_tx>");
231
+ // ─── Transaction ─────────────────────────────────────────
232
+ shortcut("gas", "查询 Gas 费用 (默认 ETH,SOL 自动构建模拟交易)", "tx.gas", async (opts, pos, mcp) => {
233
+ const GAS_CHAIN_ALIAS = {
234
+ ARB: "ARBITRUM",
235
+ OP: "OPTIMISM",
236
+ AVAX: "AVALANCHE",
237
+ MATIC: "POLYGON",
238
+ };
239
+ const raw = (pos[0] ?? opts.chain ?? "ETH").toUpperCase();
240
+ const chain = GAS_CHAIN_ALIAS[raw] ?? raw;
241
+ const args = { chain };
242
+ if (chain === "SOL") {
243
+ const addrRaw = await mcp.callTool("wallet.get_addresses", {});
244
+ const addrData = extractToolJson(addrRaw);
245
+ const from = opts.from ?? addrData.addresses?.["SOL"] ?? "";
246
+ const to = opts.to ?? "So11111111111111111111111111111111111111112";
247
+ args.from = from;
248
+ args.to = to;
249
+ if (opts.data) {
250
+ args.data = opts.data;
251
+ }
252
+ else {
253
+ const unsignedRaw = await mcp.callTool("tx.get_sol_unsigned", {
254
+ from,
255
+ to,
256
+ amount: opts.amount ?? "0.000001",
257
+ });
258
+ const unsignedData = extractToolJson(unsignedRaw);
259
+ if (unsignedData.unsigned_tx_hex) {
260
+ args.data = b58ToB64(unsignedData.unsigned_tx_hex);
261
+ }
262
+ }
263
+ if (opts.value)
264
+ args.value = opts.value;
265
+ }
266
+ else {
267
+ if (opts.from)
268
+ args.from = opts.from;
269
+ if (opts.to)
270
+ args.to = opts.to;
271
+ }
272
+ return args;
273
+ }, [
274
+ ["--chain <chain>", "链名 (ETH/SOL/BSC...)"],
275
+ ["--from <address>", "发送方地址 (SOL 默认自动获取)"],
276
+ ["--to <address>", "接收方地址 (SOL 默认同 from)"],
277
+ ["--amount <amount>", "模拟转账金额 (SOL 用,默认 0.000001)"],
278
+ ["--value <lamports>", "金额 lamports (SOL 可选)"],
279
+ ["--data <base64>", "完整序列化交易 base64 (SOL 可选,默认自动构建)"],
280
+ ], "[chain]");
281
+ shortcut("transfer", "转账预览", "tx.transfer_preview", (opts) => {
282
+ const args = {};
283
+ if (opts.chain)
284
+ args.chain = opts.chain.toUpperCase();
285
+ if (opts.from)
286
+ args.from = opts.from;
287
+ if (opts.to)
288
+ args.to = opts.to;
289
+ if (opts.amount)
290
+ args.amount = opts.amount;
291
+ if (opts.token) {
292
+ if (opts.chain && opts.chain.toUpperCase() === "SOL") {
293
+ args.token_mint = opts.token;
294
+ if (opts.tokenDecimals)
295
+ args.token_decimals = Number(opts.tokenDecimals);
296
+ }
297
+ else {
298
+ args.token_contract = opts.token;
299
+ }
300
+ if (opts.tokenSymbol) {
301
+ args.token = opts.tokenSymbol;
302
+ }
303
+ }
304
+ else if (opts.chain && opts.chain.toUpperCase() === "SOL") {
305
+ args.token = "SOL";
306
+ }
307
+ else {
308
+ args.token = "ETH";
309
+ }
310
+ return args;
311
+ }, [
312
+ ["--chain <chain>", "链名 (ETH/BSC/SOL...)"],
313
+ ["--to <address>", "收款地址"],
314
+ ["--amount <amount>", "金额"],
315
+ ["--from <address>", "付款地址 (默认自动获取)"],
316
+ ["--token <contract>", "Token 合约/Mint 地址 (原生币可不填)"],
317
+ ["--token-decimals <decimals>", "Token 精度 (SOL SPL 代币需要)"],
318
+ ["--token-symbol <symbol>", "Token 符号 (用于显示,如 TRUMP/USDC)"],
319
+ ]);
320
+ // ─── 一键转账 (Preview → Sign → Broadcast) ────────────
321
+ program
322
+ .command("send")
323
+ .description("一键转账 (Preview→Sign→Broadcast)")
324
+ .option("--chain <chain>", "链名 (ETH/SOL/BSC...)")
325
+ .option("--to <address>", "收款地址")
326
+ .option("--amount <amount>", "金额")
327
+ .option("--from <address>", "付款地址 (默认自动获取)")
328
+ .option("--token <contract>", "Token 合约/Mint 地址 (原生币可不填)")
329
+ .option("--token-decimals <decimals>", "Token 精度 (SPL 代币必填或自动查询)")
330
+ .option("--token-symbol <symbol>", "Token 符号 (用于显示,如 TRUMP/USDC)")
331
+ .action(async function (opts) {
332
+ try {
333
+ const mcp = await ensureAuthedMcp();
334
+ const chain = (opts.chain ?? "ETH").toUpperCase();
335
+ const addrRes = extractToolJson((await mcp.callTool("wallet.get_addresses", {})));
336
+ const accountId = addrRes.account_id ?? "";
337
+ const from = opts.from ??
338
+ (chain === "SOL"
339
+ ? addrRes.addresses?.["SOL"]
340
+ : addrRes.addresses?.["EVM"]) ??
341
+ "";
342
+ if (!opts.to || !opts.amount) {
343
+ console.error(chalk.red("--to 和 --amount 是必填项"));
344
+ return;
345
+ }
346
+ // Auto-resolve token_decimals and token symbol for SPL/ERC20 transfers
347
+ let tokenDecimals;
348
+ let tokenSymbol = opts.tokenSymbol;
349
+ if (opts.tokenDecimals) {
350
+ tokenDecimals = Number(opts.tokenDecimals);
351
+ }
352
+ if (opts.token && chain === "SOL" && (!tokenDecimals || !tokenSymbol)) {
353
+ try {
354
+ const tokenListRes = extractToolJson((await mcp.callTool("token_list_swap_tokens", {
355
+ chain_name: "solana",
356
+ search: opts.token,
357
+ })));
358
+ const matched = tokenListRes.tokens?.find((t) => t.address?.toLowerCase() === opts.token.toLowerCase());
359
+ if (matched?.decimal != null && !tokenDecimals) {
360
+ tokenDecimals = matched.decimal;
361
+ }
362
+ if (matched?.symbol && !tokenSymbol) {
363
+ tokenSymbol = matched.symbol;
364
+ }
365
+ }
366
+ catch {
367
+ // ignore lookup failure; will fail at preview if decimals truly required
368
+ }
369
+ }
370
+ else if (opts.token && chain !== "SOL" && !tokenSymbol) {
371
+ try {
372
+ const chainName = chain === "ETH" ? "ethereum" : chain.toLowerCase();
373
+ const tokenListRes = extractToolJson((await mcp.callTool("token_list_swap_tokens", {
374
+ chain_name: chainName,
375
+ search: opts.token,
376
+ })));
377
+ const matched = tokenListRes.tokens?.find((t) => t.address?.toLowerCase() === opts.token.toLowerCase());
378
+ if (matched?.symbol) {
379
+ tokenSymbol = matched.symbol;
380
+ }
381
+ }
382
+ catch {
383
+ // ignore
384
+ }
385
+ }
386
+ // Step 1: Preview
387
+ const previewSpinner = ora("转账预览...").start();
388
+ const previewArgs = {
389
+ chain,
390
+ from,
391
+ to: opts.to,
392
+ amount: opts.amount,
393
+ };
394
+ if (opts.token) {
395
+ if (chain === "SOL") {
396
+ previewArgs.token_mint = opts.token;
397
+ if (tokenDecimals != null) {
398
+ previewArgs.token_decimals = tokenDecimals;
399
+ }
400
+ }
401
+ else {
402
+ previewArgs.token_contract = opts.token;
403
+ }
404
+ if (tokenSymbol) {
405
+ previewArgs.token = tokenSymbol;
406
+ }
407
+ }
408
+ else if (chain === "SOL") {
409
+ previewArgs.token = "SOL";
410
+ }
411
+ else {
412
+ previewArgs.token = "ETH";
413
+ }
414
+ let previewResult = extractToolJson((await mcp.callTool("tx.transfer_preview", previewArgs)));
415
+ const unsignedTx = previewResult.unsigned_tx_hex ??
416
+ previewResult.key_info
417
+ ?.unsigned_tx_hex;
418
+ if (!unsignedTx) {
419
+ previewSpinner.fail("预览失败:未获得 unsigned_tx_hex");
420
+ console.log(JSON.stringify(previewResult, null, 2));
421
+ return;
422
+ }
423
+ const keyInfo = previewResult.key_info ?? {};
424
+ const token = keyInfo.token ?? chain;
425
+ previewSpinner.succeed(`预览成功:${keyInfo.summary ?? `${opts.amount} ${token} → ${opts.to}`}`);
426
+ // SOL native: refresh blockhash via get_sol_unsigned (SPL tokens skip — get_sol_unsigned only supports native SOL)
427
+ let txToSign = unsignedTx;
428
+ if (chain === "SOL" && !opts.token) {
429
+ const freshSpinner = ora("获取最新 blockhash...").start();
430
+ const solArgs = {
431
+ from,
432
+ to: opts.to,
433
+ amount: opts.amount,
434
+ };
435
+ const freshResult = extractToolJson((await mcp.callTool("tx.get_sol_unsigned", solArgs)));
436
+ if (freshResult.unsigned_tx_hex) {
437
+ txToSign = freshResult.unsigned_tx_hex;
438
+ freshSpinner.succeed("已获取最新 unsigned_tx");
439
+ }
440
+ else {
441
+ freshSpinner.warn("未能刷新 blockhash,使用预览的 unsigned_tx");
442
+ }
443
+ }
444
+ // Step 2: Sign
445
+ const signSpinner = ora("签名交易...").start();
446
+ const signChain = chain === "SOL" ? "SOL" : "EVM";
447
+ const signResult = extractToolJson((await mcp.callTool("wallet.sign_transaction", {
448
+ chain: signChain,
449
+ raw_tx: txToSign,
450
+ })));
451
+ const signedTx = signResult.signedTransaction;
452
+ if (!signedTx) {
453
+ signSpinner.fail("签名失败");
454
+ console.log(JSON.stringify(signResult, null, 2));
455
+ return;
456
+ }
457
+ signSpinner.succeed("签名成功");
458
+ // Step 3: Broadcast
459
+ const broadcastSpinner = ora("广播交易...").start();
460
+ const sendResult = extractToolJson((await mcp.callTool("tx.send_raw_transaction", {
461
+ chain,
462
+ signed_tx: signedTx,
463
+ account_id: accountId,
464
+ address: from,
465
+ trans_oppo_address: opts.to,
466
+ token_short_name: token,
467
+ trans_balance: opts.amount,
468
+ trans_type: "transfer",
469
+ })));
470
+ if (sendResult.hash) {
471
+ broadcastSpinner.succeed("交易已广播");
472
+ console.log(chalk.green(` Hash: ${sendResult.hash}`));
473
+ if (sendResult.explorer_url) {
474
+ console.log(chalk.gray(` Explorer: ${sendResult.explorer_url}`));
475
+ }
476
+ }
477
+ else {
478
+ broadcastSpinner.fail("广播失败");
479
+ console.log(JSON.stringify(sendResult, null, 2));
480
+ }
481
+ }
482
+ catch (err) {
483
+ console.error(chalk.red(err.message));
484
+ }
485
+ });
486
+ shortcut("quote", "获取兑换报价 (ETH→USDT: --from-chain 1 --to-chain 1 --from - --to 0xdAC1...ec7 --native-in 1 --native-out 0)", "tx.quote", async (opts, _pos, mcp) => {
487
+ const args = {};
488
+ if (opts.fromChain)
489
+ args.chain_id_in = Number(opts.fromChain);
490
+ if (opts.toChain)
491
+ args.chain_id_out = Number(opts.toChain);
492
+ if (opts.from)
493
+ args.token_in = opts.from;
494
+ if (opts.to)
495
+ args.token_out = opts.to;
496
+ if (opts.amount)
497
+ args.amount = opts.amount;
498
+ if (opts.slippage) {
499
+ const raw = Number(opts.slippage);
500
+ args.slippage = raw >= 1 ? raw / 100 : raw;
501
+ }
502
+ if (opts.nativeIn)
503
+ args.native_in = Number(opts.nativeIn);
504
+ if (opts.nativeOut)
505
+ args.native_out = Number(opts.nativeOut);
506
+ if (opts.wallet) {
507
+ args.user_wallet = opts.wallet;
508
+ }
509
+ else {
510
+ const addrRes = (await mcp.callTool("wallet.get_addresses", {}));
511
+ const addresses = addrRes.addresses;
512
+ const chainId = Number(opts.fromChain ?? 1);
513
+ args.user_wallet =
514
+ chainId === 501 ? addresses?.["SOL"] : addresses?.["EVM"];
515
+ }
516
+ return args;
517
+ }, [
518
+ ["--from-chain <id>", "源链 ID (ETH=1, BSC=56, SOL=501...)", "1"],
519
+ ["--to-chain <id>", "目标链 ID (同链 swap 则和 from-chain 相同)", "1"],
520
+ ["--from <token>", "源 token 地址, 原生币用 -"],
521
+ ["--to <token>", "目标 token 合约地址"],
522
+ ["--amount <amount>", "数量"],
523
+ ["--slippage <pct>", "滑点 (0.03=3%)", "0.03"],
524
+ ["--native-in <0|1>", "源 token 是否原生币 (1=是, 0=否)"],
525
+ ["--native-out <0|1>", "目标 token 是否原生币 (1=是, 0=否)"],
526
+ ["--wallet <address>", "钱包地址 (默认自动获取)"],
527
+ ]);
528
+ shortcut("swap", "一键兑换 (Quote→Build→Sign→Submit)", "tx.swap", async (opts, _pos, mcp) => {
529
+ const addrRes = (await mcp.callTool("wallet.get_addresses", {}));
530
+ const accountId = addrRes.account_id;
531
+ const addresses = addrRes.addresses;
532
+ const chainIdIn = Number(opts.fromChain ?? 1);
533
+ const wallet = opts.wallet ??
534
+ (chainIdIn === 501 ? addresses?.["SOL"] : addresses?.["EVM"]) ??
535
+ "";
536
+ const rawSlippage = Number(opts.slippage ?? "0.03");
537
+ const slippage = rawSlippage >= 1 ? rawSlippage / 100 : rawSlippage;
538
+ const args = {
539
+ chain_id_in: chainIdIn,
540
+ chain_id_out: Number(opts.toChain ?? opts.fromChain ?? 1),
541
+ token_in: opts.from,
542
+ token_out: opts.to,
543
+ amount: opts.amount,
544
+ slippage,
545
+ user_wallet: wallet,
546
+ native_in: Number(opts.nativeIn ?? "0"),
547
+ native_out: Number(opts.nativeOut ?? "0"),
548
+ account_id: accountId,
549
+ };
550
+ if (opts.toWallet)
551
+ args.to_wallet = opts.toWallet;
552
+ return args;
553
+ }, [
554
+ ["--from-chain <id>", "源链 ID (ETH=1, BSC=56, SOL=501...)", "1"],
555
+ ["--to-chain <id>", "目标链 ID", "1"],
556
+ ["--from <token>", "源 token 地址, 原生币用 -"],
557
+ ["--to <token>", "目标 token 合约地址"],
558
+ ["--amount <amount>", "数量"],
559
+ ["--slippage <pct>", "滑点 (0.03=3%)", "0.03"],
560
+ ["--native-in <0|1>", "源 token 是否原生币 (1=是, 0=否)", "0"],
561
+ ["--native-out <0|1>", "目标 token 是否原生币 (1=是, 0=否)", "0"],
562
+ ["--wallet <address>", "源链钱包地址 (默认自动获取)"],
563
+ ["--to-wallet <address>", "目标链钱包地址 (跨链时需要)"],
564
+ ]);
565
+ shortcut("swap-detail", "查询兑换交易详情", "tx.swap_detail", (_opts, pos) => ({ tx_order_id: pos[0] }), undefined, "<order_id>");
566
+ shortcut("send-tx", "广播已签名交易(自动获取 account_id 和 address)", "tx.send_raw_transaction", async (opts, _pos, mcp) => {
567
+ const addrRes = (await mcp.callTool("wallet.get_addresses", {}));
568
+ const accountId = addrRes.account_id;
569
+ const chain = (opts.chain ?? "ETH").toUpperCase();
570
+ const addresses = addrRes.addresses;
571
+ const fromAddr = opts.address ?? addresses?.[chain === "ETH" ? "EVM" : chain] ?? "";
572
+ const args = {
573
+ chain,
574
+ signed_tx: opts.hex,
575
+ account_id: accountId,
576
+ address: fromAddr,
577
+ trans_oppo_address: opts.to,
578
+ token_short_name: opts.token ?? chain,
579
+ trans_balance: opts.amount ?? "0",
580
+ trans_type: opts.type ?? "transfer",
581
+ };
582
+ return args;
583
+ }, [
584
+ ["--chain <chain>", "链名,如 ETH", "ETH"],
585
+ ["--hex <signed_tx>", "签名后的交易 hex"],
586
+ ["--to <to>", "接收方地址"],
587
+ ["--token <symbol>", "代币名称,如 ETH / USDT"],
588
+ ["--amount <amount>", "转账金额"],
589
+ ["--address <from>", "发送方地址(默认自动获取)"],
590
+ ["--type <type>", "交易类型", "transfer"],
591
+ ]);
592
+ shortcut("tx-detail", "查询交易详情 (by hash)", "tx.detail", (_opts, pos) => ({ hash_id: pos[0] }), undefined, "<tx_hash>");
593
+ shortcut("tx-history", "查询交易历史", "tx.list", (opts) => {
594
+ const args = {};
595
+ if (opts.page)
596
+ args.page_num = opts.page;
597
+ if (opts.limit)
598
+ args.page_size = opts.limit;
599
+ return args;
600
+ }, [
601
+ ["--page <n>", "页码", "1"],
602
+ ["--limit <n>", "每页条数", "20"],
603
+ ]);
604
+ shortcut("swap-history", "查询 Swap/Bridge 交易历史", "tx.history_list", (opts) => {
605
+ const args = {};
606
+ if (opts.page)
607
+ args.page_num = Number(opts.page);
608
+ if (opts.limit)
609
+ args.page_size = Number(opts.limit);
610
+ return args;
611
+ }, [
612
+ ["--page <n>", "页码", "1"],
613
+ ["--limit <n>", "每页条数", "20"],
614
+ ]);
615
+ shortcut("sol-tx", "构建 Solana 未签名转账交易", "tx.get_sol_unsigned", (opts) => {
616
+ const args = {};
617
+ if (opts.to)
618
+ args.to_address = opts.to;
619
+ if (opts.amount)
620
+ args.amount = opts.amount;
621
+ if (opts.mint)
622
+ args.token_mint = opts.mint;
623
+ return args;
624
+ }, [
625
+ ["--to <address>", "收款地址"],
626
+ ["--amount <amount>", "金额"],
627
+ ["--mint <address>", "SPL Token Mint (原生 SOL 可不填)"],
628
+ ]);
629
+ // ─── Market ──────────────────────────────────────────────
630
+ shortcut("kline", "查询 K 线数据", "market_get_kline", (opts) => {
631
+ const args = {};
632
+ if (opts.chain)
633
+ args.chain = opts.chain;
634
+ if (opts.address)
635
+ args.token_address = opts.address;
636
+ if (opts.period)
637
+ args.period = opts.period;
638
+ return args;
639
+ }, [
640
+ ["--chain <chain>", "链名 (eth/bsc/solana...)"],
641
+ ["--address <addr>", "Token 合约地址"],
642
+ ["--period <period>", "时间周期 (1m/5m/1h/4h/1d)", "1h"],
643
+ ]);
644
+ shortcut("liquidity", "查询流动性池事件", "market_get_pair_liquidity", (opts) => {
645
+ const args = {};
646
+ if (opts.chain)
647
+ args.chain = opts.chain;
648
+ if (opts.address)
649
+ args.token_address = opts.address;
650
+ return args;
651
+ }, [
652
+ ["--chain <chain>", "链名"],
653
+ ["--address <addr>", "Token 合约地址"],
654
+ ]);
655
+ shortcut("tx-stats", "查询交易量统计 (5m/1h/4h/24h)", "market_get_tx_stats", (opts) => {
656
+ const args = {};
657
+ if (opts.chain)
658
+ args.chain = opts.chain;
659
+ if (opts.address)
660
+ args.token_address = opts.address;
661
+ return args;
662
+ }, [
663
+ ["--chain <chain>", "链名"],
664
+ ["--address <addr>", "Token 合约地址"],
665
+ ]);
666
+ shortcut("swap-tokens", "查询链上可兑换 Token 列表", "token_list_swap_tokens", (opts) => {
667
+ const args = {};
668
+ if (opts.chain)
669
+ args.chain = opts.chain;
670
+ if (opts.search)
671
+ args.search = opts.search;
672
+ return args;
673
+ }, [
674
+ ["--chain <chain>", "链名"],
675
+ ["--search <keyword>", "搜索关键词 (symbol/address)"],
676
+ ]);
677
+ shortcut("bridge-tokens", "查询跨链桥目标 Token", "token_list_cross_chain_bridge_tokens", (opts) => {
678
+ const args = {};
679
+ if (opts.srcChain)
680
+ args.source_chain = opts.srcChain;
681
+ if (opts.destChain)
682
+ args.chain = opts.destChain;
683
+ if (opts.token)
684
+ args.source_address = opts.token;
685
+ return args;
686
+ }, [
687
+ ["--src-chain <chain>", "源链"],
688
+ ["--dest-chain <chain>", "目标链"],
689
+ ["--token <address>", "源 Token 地址"],
690
+ ]);
691
+ // ─── Token ───────────────────────────────────────────────
692
+ shortcut("token-info", "查询 Token 详情 (价格/市值/持仓分布)", "token_get_coin_info", (opts) => {
693
+ const args = {};
694
+ if (opts.chain)
695
+ args.chain = opts.chain;
696
+ if (opts.address)
697
+ args.address = opts.address;
698
+ return args;
699
+ }, [
700
+ ["--chain <chain>", "链名"],
701
+ ["--address <addr>", "Token 合约地址"],
702
+ ]);
703
+ shortcut("token-risk", "查询 Token 安全审计信息", "token_get_risk_info", (opts) => {
704
+ const args = {};
705
+ if (opts.chain)
706
+ args.chain = opts.chain;
707
+ if (opts.address)
708
+ args.address = opts.address;
709
+ return args;
710
+ }, [
711
+ ["--chain <chain>", "链名"],
712
+ ["--address <addr>", "Token 合约地址"],
713
+ ]);
714
+ shortcut("token-rank", "Token 涨跌幅排行榜 (24h)", "token_ranking", (opts) => {
715
+ const args = {};
716
+ if (opts.chain)
717
+ args.chain = opts.chain;
718
+ if (opts.limit)
719
+ args.limit = Number(opts.limit);
720
+ if (opts.direction)
721
+ args.direction = opts.direction;
722
+ return args;
723
+ }, [
724
+ ["--chain <chain>", "链名"],
725
+ ["--limit <n>", "Top N", "10"],
726
+ ["--direction <dir>", "desc (涨幅) | asc (跌幅)", "desc"],
727
+ ]);
728
+ shortcut("new-tokens", "按创建时间筛选新 Token", "token_get_coins_range_by_created_at", (opts) => {
729
+ const args = {};
730
+ if (opts.chain)
731
+ args.chain = opts.chain;
732
+ if (opts.start)
733
+ args.start = opts.start;
734
+ args.end = opts.end ?? new Date().toISOString();
735
+ return args;
736
+ }, [
737
+ ["--chain <chain>", "链名"],
738
+ ["--start <time>", "开始时间 (RFC3339, 如 2026-03-08T00:00:00Z)"],
739
+ ["--end <time>", "结束时间 (RFC3339)"],
740
+ ]);
741
+ // ─── Chain / RPC ─────────────────────────────────────────
742
+ shortcut("chain-config", "查询链配置 (networkKey, endpoint, chainID)", "chain.config", (_opts, pos) => (pos[0] ? { chain: pos[0].toUpperCase() } : {}), undefined, "[chain]");
743
+ shortcut("rpc", "执行 JSON-RPC 调用 (eth_blockNumber, eth_getBalance...)", "rpc.call", (opts) => {
744
+ const args = {};
745
+ if (opts.chain)
746
+ args.chain = opts.chain.toUpperCase();
747
+ if (opts.method)
748
+ args.method = opts.method;
749
+ if (opts.params) {
750
+ try {
751
+ args.params = JSON.parse(opts.params);
752
+ }
753
+ catch {
754
+ args.params = opts.params;
755
+ }
756
+ }
757
+ return args;
758
+ }, [
759
+ ["--chain <chain>", "链名"],
760
+ ["--method <method>", "RPC 方法 (eth_blockNumber...)"],
761
+ ["--params <json>", "参数 JSON 数组"],
762
+ ]);
763
+ }
764
+ // ─── MCP Device Flow 登录 ────────────────────────────────
765
+ async function loginWithDeviceFlow(mcp, serverUrl, isGoogle, provider) {
766
+ const loginSpinner = ora(`Starting ${provider} OAuth login...`).start();
767
+ let startResult;
768
+ try {
769
+ startResult = isGoogle
770
+ ? await mcp.authGoogleLoginStart()
771
+ : await mcp.authGateLoginStart();
772
+ }
773
+ catch (err) {
774
+ loginSpinner.fail(`Failed to start device flow: ${err.message}`);
775
+ return;
776
+ }
777
+ const parsed = parseToolResult(startResult);
778
+ if (!parsed?.verification_url || !parsed?.flow_id) {
779
+ loginSpinner.fail("Failed to start login flow (invalid response)");
780
+ return;
781
+ }
782
+ loginSpinner.succeed("Login flow started");
783
+ if (parsed.user_code) {
784
+ console.log(chalk.gray(` Code: ${parsed.user_code}`));
785
+ }
786
+ const opened = await openBrowser(parsed.verification_url);
787
+ if (opened) {
788
+ console.log(chalk.green(" ✔ Browser opened — please authorize there."));
789
+ }
790
+ const pollSpinner = ora("Waiting for authorization...").start();
791
+ const intervalMs = (parsed.interval ?? 5) * 1000;
792
+ const deadline = Date.now() + (parsed.expires_in ?? 1800) * 1000;
793
+ let cancelled = false;
794
+ const onSigint = () => {
795
+ cancelled = true;
796
+ };
797
+ process.once("SIGINT", onSigint);
798
+ while (Date.now() < deadline && !cancelled) {
799
+ await sleep(intervalMs);
800
+ try {
801
+ const pollResult = isGoogle
802
+ ? await mcp.authGoogleLoginPoll(parsed.flow_id)
803
+ : await mcp.authGateLoginPoll(parsed.flow_id);
804
+ const poll = parseToolResult(pollResult);
805
+ if (!poll)
806
+ continue;
807
+ if (poll.status === "ok") {
808
+ const token = poll.access_token ?? poll.mcp_token;
809
+ if (token) {
810
+ mcp.setMcpToken(token);
811
+ process.removeListener("SIGINT", onSigint);
812
+ pollSpinner.succeed("Login successful!");
813
+ saveAuth({
814
+ mcp_token: token,
815
+ provider: isGoogle ? "google" : "gate",
816
+ user_id: poll.user_id,
817
+ expires_at: poll.expires_in
818
+ ? Date.now() + poll.expires_in * 1000
819
+ : Date.now() + 30 * 86_400_000,
820
+ env: "default",
821
+ server_url: serverUrl,
822
+ });
823
+ console.log();
824
+ if (poll.user_id)
825
+ console.log(chalk.green(` User ID: ${poll.user_id}`));
826
+ console.log(chalk.green(` Wallet: custodial (${provider})`));
827
+ console.log(chalk.gray(` Token saved to ${getAuthFilePath()}`));
828
+ await reportWalletAddresses(mcp);
829
+ return;
830
+ }
831
+ }
832
+ if (poll.status === "error") {
833
+ process.removeListener("SIGINT", onSigint);
834
+ pollSpinner.fail(`Login failed: ${poll.error ?? "Unknown error"}`);
835
+ return;
836
+ }
837
+ }
838
+ catch {
839
+ // poll 请求失败,继续轮询
840
+ }
841
+ }
842
+ process.removeListener("SIGINT", onSigint);
843
+ pollSpinner.fail(cancelled ? "Login cancelled" : "Login timed out");
844
+ }
845
+ // ─── Google OAuth 登录(REST API + 服务端回调)─────────────
846
+ const GOOGLE_AUTH_ENDPOINT = "https://accounts.google.com/o/oauth2/v2/auth";
847
+ const GOOGLE_CLIENT_ID = "663295861438-ehhqhr8j2cn3hailtjmedtbcd806vca6.apps.googleusercontent.com";
848
+ const GOOGLE_SCOPE = "openid email profile";
849
+ async function loginGoogleViaRest(mcp, serverUrl) {
850
+ const baseUrl = mcp.getServerBaseUrl();
851
+ const callbackUrl = `${baseUrl}/oauth/google/device/callback`;
852
+ const loginSpinner = ora("Starting Google OAuth login...").start();
853
+ // 1. 通过 REST API 启动 Google OAuth device flow
854
+ let flowData;
855
+ try {
856
+ const res = await fetch(`${baseUrl}/oauth/google/device/start`, {
857
+ method: "POST",
858
+ headers: { "Content-Type": "application/json" },
859
+ body: JSON.stringify({}),
860
+ });
861
+ if (!res.ok) {
862
+ const text = await res.text();
863
+ throw new Error(`${res.status} ${text}`);
864
+ }
865
+ flowData = (await res.json());
866
+ }
867
+ catch (err) {
868
+ loginSpinner.fail(`Failed to start Google login: ${err.message}`);
869
+ return;
870
+ }
871
+ if (flowData.error) {
872
+ loginSpinner.fail(`Google login error: ${flowData.error}`);
873
+ return;
874
+ }
875
+ // 2. 如果服务端返回了 verification_url,直接用;否则手动构建
876
+ let authUrl;
877
+ if (flowData.verification_url) {
878
+ authUrl = flowData.verification_url;
879
+ }
880
+ else {
881
+ const state = flowData.state ?? flowData.flow_id ?? "";
882
+ const url = new URL(GOOGLE_AUTH_ENDPOINT);
883
+ url.searchParams.set("client_id", GOOGLE_CLIENT_ID);
884
+ url.searchParams.set("redirect_uri", callbackUrl);
885
+ url.searchParams.set("response_type", "code");
886
+ url.searchParams.set("scope", GOOGLE_SCOPE);
887
+ url.searchParams.set("access_type", "offline");
888
+ url.searchParams.set("prompt", "consent");
889
+ if (state)
890
+ url.searchParams.set("state", state);
891
+ authUrl = url.toString();
892
+ }
893
+ const flowId = flowData.flow_id ?? flowData.device_code ?? "";
894
+ if (!flowId) {
895
+ loginSpinner.fail("Failed to start Google login: no flow_id returned");
896
+ return;
897
+ }
898
+ loginSpinner.succeed("Google OAuth flow started");
899
+ if (flowData.user_code) {
900
+ console.log(chalk.gray(` Code: ${flowData.user_code}`));
901
+ }
902
+ const opened = await openBrowser(authUrl);
903
+ if (opened) {
904
+ console.log(chalk.green(" ✔ Browser opened — please authorize there."));
905
+ }
906
+ // 4. 轮询等待结果
907
+ const pollSpinner = ora("Waiting for Google authorization...").start();
908
+ const intervalMs = (flowData.interval ?? 5) * 1000;
909
+ const deadline = Date.now() + (flowData.expires_in ?? 1800) * 1000;
910
+ let cancelled = false;
911
+ const onSigint = () => {
912
+ cancelled = true;
913
+ };
914
+ process.once("SIGINT", onSigint);
915
+ while (Date.now() < deadline && !cancelled) {
916
+ await sleep(intervalMs);
917
+ try {
918
+ const res = await fetch(`${baseUrl}/oauth/google/device/poll`, {
919
+ method: "POST",
920
+ headers: { "Content-Type": "application/json" },
921
+ body: JSON.stringify({ flow_id: flowId }),
922
+ });
923
+ if (!res.ok)
924
+ continue;
925
+ const poll = (await res.json());
926
+ if (poll.status === "ok") {
927
+ const token = poll.access_token ?? poll.mcp_token;
928
+ if (token) {
929
+ mcp.setMcpToken(token);
930
+ process.removeListener("SIGINT", onSigint);
931
+ pollSpinner.succeed("Google login successful!");
932
+ saveAuth({
933
+ mcp_token: token,
934
+ provider: "google",
935
+ user_id: poll.user_id,
936
+ expires_at: poll.expires_in
937
+ ? Date.now() + poll.expires_in * 1000
938
+ : Date.now() + 30 * 86_400_000,
939
+ env: "default",
940
+ server_url: serverUrl,
941
+ });
942
+ console.log();
943
+ if (poll.user_id)
944
+ console.log(chalk.green(` User ID: ${poll.user_id}`));
945
+ if (poll.wallet_address)
946
+ console.log(chalk.green(` Wallet: ${poll.wallet_address}`));
947
+ console.log(chalk.green(` Provider: Google`));
948
+ console.log(chalk.gray(` Token saved to ${getAuthFilePath()}`));
949
+ await reportWalletAddresses(mcp);
950
+ return;
951
+ }
952
+ }
953
+ if (poll.status === "error") {
954
+ process.removeListener("SIGINT", onSigint);
955
+ pollSpinner.fail(`Google login failed: ${poll.error ?? "Unknown error"}`);
956
+ return;
957
+ }
958
+ }
959
+ catch {
960
+ // poll 失败,继续轮询
961
+ }
962
+ }
963
+ process.removeListener("SIGINT", onSigint);
964
+ pollSpinner.fail(cancelled ? "Login cancelled" : "Login timed out");
965
+ }
966
+ // ─── Gate OAuth 登录(REST API + 服务端回调)──────────────
967
+ async function loginGateViaRest(mcp, serverUrl) {
968
+ const baseUrl = mcp.getServerBaseUrl();
969
+ const loginSpinner = ora("Starting Gate OAuth login...").start();
970
+ let flowData;
971
+ try {
972
+ const res = await fetch(`${baseUrl}/oauth/gate/device/start`, {
973
+ method: "POST",
974
+ headers: { "Content-Type": "application/json" },
975
+ body: JSON.stringify({}),
976
+ });
977
+ if (!res.ok) {
978
+ const text = await res.text();
979
+ throw new Error(`${res.status} ${text}`);
980
+ }
981
+ flowData = (await res.json());
982
+ }
983
+ catch (err) {
984
+ loginSpinner.fail(`Failed to start Gate login: ${err.message}`);
985
+ return;
986
+ }
987
+ if (flowData.error) {
988
+ loginSpinner.fail(`Gate login error: ${flowData.error}`);
989
+ return;
990
+ }
991
+ if (!flowData.verification_url || !flowData.flow_id) {
992
+ loginSpinner.fail("Failed to start Gate login: no verification_url returned");
993
+ return;
994
+ }
995
+ loginSpinner.succeed("Gate OAuth flow started");
996
+ if (flowData.user_code) {
997
+ console.log(chalk.gray(` Code: ${flowData.user_code}`));
998
+ }
999
+ const opened = await openBrowser(flowData.verification_url);
1000
+ if (opened) {
1001
+ console.log(chalk.green(" ✔ Browser opened — please authorize there."));
1002
+ }
1003
+ const pollSpinner = ora("Waiting for Gate authorization...").start();
1004
+ const intervalMs = (flowData.interval ?? 5) * 1000;
1005
+ const deadline = Date.now() + (flowData.expires_in ?? 1800) * 1000;
1006
+ let cancelled = false;
1007
+ const onSigint = () => {
1008
+ cancelled = true;
1009
+ };
1010
+ process.once("SIGINT", onSigint);
1011
+ while (Date.now() < deadline && !cancelled) {
1012
+ await sleep(intervalMs);
1013
+ try {
1014
+ const res = await fetch(`${baseUrl}/oauth/gate/device/poll`, {
1015
+ method: "POST",
1016
+ headers: { "Content-Type": "application/json" },
1017
+ body: JSON.stringify({ flow_id: flowData.flow_id }),
1018
+ });
1019
+ if (!res.ok)
1020
+ continue;
1021
+ const poll = (await res.json());
1022
+ if (poll.status === "ok") {
1023
+ const token = poll.access_token ?? poll.mcp_token;
1024
+ if (token) {
1025
+ mcp.setMcpToken(token);
1026
+ process.removeListener("SIGINT", onSigint);
1027
+ pollSpinner.succeed("Gate login successful!");
1028
+ saveAuth({
1029
+ mcp_token: token,
1030
+ provider: "gate",
1031
+ user_id: poll.user_id,
1032
+ expires_at: poll.expires_in
1033
+ ? Date.now() + poll.expires_in * 1000
1034
+ : Date.now() + 30 * 86_400_000,
1035
+ env: "default",
1036
+ server_url: serverUrl,
1037
+ });
1038
+ console.log();
1039
+ if (poll.user_id)
1040
+ console.log(chalk.green(` User ID: ${poll.user_id}`));
1041
+ if (poll.wallet_address)
1042
+ console.log(chalk.green(` Wallet: ${poll.wallet_address}`));
1043
+ console.log(chalk.green(` Provider: Gate`));
1044
+ console.log(chalk.gray(` Token saved to ${getAuthFilePath()}`));
1045
+ await reportWalletAddresses(mcp);
1046
+ return;
1047
+ }
1048
+ }
1049
+ if (poll.status === "error") {
1050
+ process.removeListener("SIGINT", onSigint);
1051
+ pollSpinner.fail(`Gate login failed: ${poll.error ?? "Unknown error"}`);
1052
+ return;
1053
+ }
1054
+ }
1055
+ catch {
1056
+ // poll 失败,继续轮询
1057
+ }
1058
+ }
1059
+ process.removeListener("SIGINT", onSigint);
1060
+ pollSpinner.fail(cancelled ? "Login cancelled" : "Login timed out");
1061
+ }
1062
+ // ─── 工具函数 ────────────────────────────────────────────
1063
+ function parseToolResult(result) {
1064
+ if ("content" in result && Array.isArray(result.content)) {
1065
+ const text = result.content.find((c) => c.type === "text" && typeof c.text === "string");
1066
+ if (text) {
1067
+ try {
1068
+ return JSON.parse(text.text);
1069
+ }
1070
+ catch {
1071
+ return null;
1072
+ }
1073
+ }
1074
+ }
1075
+ return null;
1076
+ }
1077
+ function sleep(ms) {
1078
+ return new Promise((r) => setTimeout(r, ms));
1079
+ }
1080
+ const B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
1081
+ function b58ToB64(b58) {
1082
+ let n = BigInt(0);
1083
+ for (const ch of b58) {
1084
+ n = n * 58n + BigInt(B58_ALPHABET.indexOf(ch));
1085
+ }
1086
+ const hex = n.toString(16).padStart(2, "0");
1087
+ const bytes = Buffer.from(hex.length % 2 ? "0" + hex : hex, "hex");
1088
+ let pad = 0;
1089
+ for (const ch of b58) {
1090
+ if (ch === "1")
1091
+ pad++;
1092
+ else
1093
+ break;
1094
+ }
1095
+ const result = Buffer.concat([Buffer.alloc(pad), bytes]);
1096
+ return result.toString("base64");
1097
+ }
1098
+ const CHAIN_ADDRESS_MAP = {
1099
+ EVM: {
1100
+ networkKey: "ETH",
1101
+ accountKey: "ETH",
1102
+ chains: "ETH,ARB,OP,BASE,LINEA,SCROLL,ZKSYNC",
1103
+ accountFormat: "",
1104
+ },
1105
+ SOL: {
1106
+ networkKey: "SOL",
1107
+ chains: "SOL",
1108
+ },
1109
+ };
1110
+ async function reportWalletAddresses(mcp) {
1111
+ const reportSpinner = ora("Reporting wallet addresses...").start();
1112
+ try {
1113
+ const addrResult = await mcp.callTool("wallet.get_addresses");
1114
+ const addrData = parseToolResult(addrResult);
1115
+ if (!addrData?.addresses || Object.keys(addrData.addresses).length === 0) {
1116
+ reportSpinner.warn("No wallet addresses to report");
1117
+ return;
1118
+ }
1119
+ const chainAddressList = Object.entries(addrData.addresses)
1120
+ .map(([chainType, address]) => {
1121
+ const meta = CHAIN_ADDRESS_MAP[chainType];
1122
+ if (!meta)
1123
+ return null;
1124
+ return { ...meta, chainAddress: address };
1125
+ })
1126
+ .filter((item) => item !== null);
1127
+ if (chainAddressList.length === 0) {
1128
+ reportSpinner.warn("No supported chains to report");
1129
+ return;
1130
+ }
1131
+ const wallets = [
1132
+ {
1133
+ accounts: [{ chainAddressList }],
1134
+ },
1135
+ ];
1136
+ const reportResult = await mcp.callTool("agentic.report", { wallets });
1137
+ const report = parseToolResult(reportResult);
1138
+ if (report?.wallets?.length) {
1139
+ reportSpinner.succeed(`Wallet addresses reported (${chainAddressList.length} chains)`);
1140
+ for (const w of report.wallets) {
1141
+ console.log(chalk.gray(` walletID: ${w.walletID}`));
1142
+ }
1143
+ }
1144
+ else {
1145
+ reportSpinner.warn("Wallet report returned empty result");
1146
+ }
1147
+ }
1148
+ catch (err) {
1149
+ reportSpinner.warn(`Wallet report failed: ${err.message}`);
1150
+ }
1151
+ }
1152
+ //# sourceMappingURL=auth.cmd.js.map