@moly-mcp/lido 1.0.6 → 1.0.8
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/alerts-ARQAPRIT.js +16 -0
- package/dist/bin.js +278 -15
- package/dist/chunk-6F64RPQQ.js +65 -0
- package/dist/chunk-6UIRFWG4.js +73 -0
- package/dist/chunk-CH4MXPWS.js +41 -0
- package/dist/chunk-CQ6ZSCMZ.js +97 -0
- package/dist/chunk-EKZFGIVK.js +289 -0
- package/dist/chunk-EQYEWCQO.js +233 -0
- package/dist/chunk-GL6TLHSF.js +215 -0
- package/dist/{chunk-PIFEXJ56.js → chunk-P6VFMSPM.js} +19 -2
- package/dist/chunk-PDX44BCA.js +11 -0
- package/dist/chunk-RR74UAKD.js +163 -0
- package/dist/daemon-GU45VVNU.js +214 -0
- package/dist/position-LKVHTEKX.js +10 -0
- package/dist/server/index.js +190 -12
- package/dist/{session-RFQTJ6WZ.js → session-37TYTCEU.js} +350 -36
- package/dist/store-5CEITPDY.js +14 -0
- package/dist/store-SKFUVSK4.js +19 -0
- package/dist/store-WRLUM7OW.js +16 -0
- package/package.json +6 -1
- package/dist/chunk-RE3UIDLV.js +0 -545
|
@@ -1,27 +1,57 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getSettings,
|
|
4
|
+
stakeEth,
|
|
5
|
+
updateSettings
|
|
6
|
+
} from "./chunk-CQ6ZSCMZ.js";
|
|
2
7
|
import {
|
|
3
8
|
castVote,
|
|
4
9
|
claimWithdrawals,
|
|
5
|
-
getBalance,
|
|
6
|
-
getConversionRate,
|
|
7
10
|
getProposal,
|
|
8
11
|
getProposals,
|
|
9
|
-
getRewards,
|
|
10
|
-
getSettings,
|
|
11
12
|
getWithdrawalRequests,
|
|
12
13
|
getWithdrawalStatus,
|
|
13
|
-
requestWithdrawal
|
|
14
|
-
|
|
14
|
+
requestWithdrawal
|
|
15
|
+
} from "./chunk-EQYEWCQO.js";
|
|
16
|
+
import {
|
|
17
|
+
configureAlertChannels,
|
|
18
|
+
listAlerts,
|
|
19
|
+
removeAlertById,
|
|
20
|
+
setAlert
|
|
21
|
+
} from "./chunk-6UIRFWG4.js";
|
|
22
|
+
import "./chunk-6F64RPQQ.js";
|
|
23
|
+
import {
|
|
24
|
+
bridgeToEthereum,
|
|
25
|
+
getBridgeQuote,
|
|
26
|
+
getBridgeStatus,
|
|
27
|
+
getL2Balance,
|
|
28
|
+
getTotalPosition
|
|
29
|
+
} from "./chunk-GL6TLHSF.js";
|
|
30
|
+
import {
|
|
31
|
+
getBalance,
|
|
32
|
+
getConversionRate,
|
|
33
|
+
getRewards,
|
|
15
34
|
unwrapWsteth,
|
|
16
|
-
updateSettings,
|
|
17
35
|
wrapSteth
|
|
18
|
-
} from "./chunk-
|
|
19
|
-
import "./chunk-
|
|
36
|
+
} from "./chunk-EKZFGIVK.js";
|
|
37
|
+
import "./chunk-P6VFMSPM.js";
|
|
38
|
+
import {
|
|
39
|
+
loadBounds,
|
|
40
|
+
saveBounds
|
|
41
|
+
} from "./chunk-CH4MXPWS.js";
|
|
42
|
+
import {
|
|
43
|
+
initLedger,
|
|
44
|
+
ledgerStats,
|
|
45
|
+
logEntry,
|
|
46
|
+
queryLedger
|
|
47
|
+
} from "./chunk-RR74UAKD.js";
|
|
48
|
+
import "./chunk-PDX44BCA.js";
|
|
20
49
|
|
|
21
50
|
// src/chat/session.ts
|
|
22
51
|
import * as readline from "readline";
|
|
23
52
|
import * as fs from "fs";
|
|
24
53
|
import * as path from "path";
|
|
54
|
+
import { fileURLToPath } from "url";
|
|
25
55
|
|
|
26
56
|
// src/chat/providers.ts
|
|
27
57
|
async function callAnthropic(apiKey, model, messages, tools) {
|
|
@@ -325,14 +355,158 @@ var TOOL_DEFS = [
|
|
|
325
355
|
},
|
|
326
356
|
{
|
|
327
357
|
name: "update_settings",
|
|
328
|
-
description: "Change mode, network, or
|
|
358
|
+
description: "Change mode, network, RPC, or chain scope.",
|
|
329
359
|
parameters: {
|
|
330
360
|
type: "object",
|
|
331
361
|
properties: {
|
|
332
362
|
network: { type: "string", enum: ["hoodi", "mainnet"] },
|
|
333
363
|
mode: { type: "string", enum: ["simulation", "live"] },
|
|
334
364
|
rpc: { type: "string", nullable: true },
|
|
335
|
-
model: { type: "string" }
|
|
365
|
+
model: { type: "string" },
|
|
366
|
+
chain_scope: { type: "string", enum: ["ethereum", "all"], description: "ethereum = L1 only, all = L1 + Base/Arbitrum bridging" }
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
name: "get_l2_balance",
|
|
372
|
+
description: "Get ETH and wstETH balances on Base or Arbitrum L2. Mainnet only.",
|
|
373
|
+
parameters: {
|
|
374
|
+
type: "object",
|
|
375
|
+
required: ["source_chain"],
|
|
376
|
+
properties: {
|
|
377
|
+
source_chain: { type: "string", enum: ["base", "arbitrum"], description: "L2 chain to query" },
|
|
378
|
+
address: { type: "string", description: "Address to check (optional, defaults to wallet)" }
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
name: "get_bridge_quote",
|
|
384
|
+
description: "Get a quote for bridging ETH or wstETH from an L2 to Ethereum L1 via LI.FI. Mainnet only.",
|
|
385
|
+
parameters: {
|
|
386
|
+
type: "object",
|
|
387
|
+
required: ["source_chain", "token", "amount"],
|
|
388
|
+
properties: {
|
|
389
|
+
source_chain: { type: "string", enum: ["base", "arbitrum"] },
|
|
390
|
+
token: { type: "string", enum: ["ETH", "wstETH"], description: "Token to bridge" },
|
|
391
|
+
amount: { type: "string", description: 'Amount to bridge (e.g. "0.1")' },
|
|
392
|
+
to_token: { type: "string", enum: ["ETH", "wstETH"], description: "Token to receive on L1 (default ETH)" }
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
name: "bridge_to_ethereum",
|
|
398
|
+
description: "Bridge ETH or wstETH from Base/Arbitrum to Ethereum L1 via LI.FI. Mainnet only.",
|
|
399
|
+
parameters: {
|
|
400
|
+
type: "object",
|
|
401
|
+
required: ["source_chain", "token", "amount"],
|
|
402
|
+
properties: {
|
|
403
|
+
source_chain: { type: "string", enum: ["base", "arbitrum"] },
|
|
404
|
+
token: { type: "string", enum: ["ETH", "wstETH"] },
|
|
405
|
+
amount: { type: "string", description: "Amount to bridge" },
|
|
406
|
+
to_token: { type: "string", enum: ["ETH", "wstETH"] },
|
|
407
|
+
dry_run: { type: "boolean", description: "Simulate without broadcasting" }
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
name: "get_bridge_status",
|
|
413
|
+
description: "Check the status of an in-progress bridge transaction. Mainnet only.",
|
|
414
|
+
parameters: {
|
|
415
|
+
type: "object",
|
|
416
|
+
required: ["tx_hash", "source_chain"],
|
|
417
|
+
properties: {
|
|
418
|
+
tx_hash: { type: "string", description: "Bridge transaction hash on the L2" },
|
|
419
|
+
source_chain: { type: "string", enum: ["base", "arbitrum"] }
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
name: "set_alert",
|
|
425
|
+
description: "Create a new alert. Conditions: balance_below, balance_above, reward_rate_below, reward_rate_above, withdrawal_ready, proposal_new, conversion_rate_above, conversion_rate_below.",
|
|
426
|
+
parameters: {
|
|
427
|
+
type: "object",
|
|
428
|
+
required: ["condition"],
|
|
429
|
+
properties: {
|
|
430
|
+
condition: { type: "string", description: "Alert condition type" },
|
|
431
|
+
threshold: { type: "number", description: "Numeric threshold (required for _above/_below conditions)" },
|
|
432
|
+
channel: { type: "string", enum: ["telegram", "webhook"], description: "Notification channel (default: telegram)" }
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
name: "list_alerts",
|
|
438
|
+
description: "List all configured alerts.",
|
|
439
|
+
parameters: { type: "object", properties: {} }
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
name: "remove_alert",
|
|
443
|
+
description: "Remove an alert by ID.",
|
|
444
|
+
parameters: {
|
|
445
|
+
type: "object",
|
|
446
|
+
required: ["id"],
|
|
447
|
+
properties: { id: { type: "string", description: "Alert ID to remove" } }
|
|
448
|
+
}
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
name: "configure_alert_channels",
|
|
452
|
+
description: "Configure Telegram and/or webhook notification channels for alerts.",
|
|
453
|
+
parameters: {
|
|
454
|
+
type: "object",
|
|
455
|
+
properties: {
|
|
456
|
+
telegram_token: { type: "string", description: "Telegram bot token" },
|
|
457
|
+
telegram_chat_id: { type: "string", description: "Telegram chat ID" },
|
|
458
|
+
webhook_url: { type: "string", description: "Webhook URL for HTTP POST notifications" }
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
name: "get_total_position",
|
|
464
|
+
description: "Aggregated cross-chain position: ETH, stETH, wstETH across Ethereum + Base + Arbitrum, all converted to ETH equivalent.",
|
|
465
|
+
parameters: {
|
|
466
|
+
type: "object",
|
|
467
|
+
properties: {
|
|
468
|
+
address: { type: "string", description: "Ethereum address (optional)" }
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
name: "get_bounds",
|
|
474
|
+
description: "Get current policy bounds (max stake per tx, daily limit, min ETH reserve, governance auto-vote).",
|
|
475
|
+
parameters: { type: "object", properties: {} }
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
name: "set_bounds",
|
|
479
|
+
description: "Update policy bounds that gate write operations.",
|
|
480
|
+
parameters: {
|
|
481
|
+
type: "object",
|
|
482
|
+
properties: {
|
|
483
|
+
maxStakePerTx: { type: "number", description: "Max ETH per single stake" },
|
|
484
|
+
maxDailyStake: { type: "number", description: "Max ETH staked per day" },
|
|
485
|
+
minEthReserve: { type: "number", description: "Min ETH to keep unstaked for gas" },
|
|
486
|
+
autoRestakeThreshold: { type: "number", description: "Auto-restake rewards threshold in ETH" },
|
|
487
|
+
governanceAutoVote: { type: "boolean", description: "Allow agent to auto-vote on proposals" }
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
name: "get_trade_history",
|
|
493
|
+
description: "Query the activity ledger with filters.",
|
|
494
|
+
parameters: {
|
|
495
|
+
type: "object",
|
|
496
|
+
properties: {
|
|
497
|
+
tool: { type: "string", description: "Filter by tool name (e.g. stake_eth)" },
|
|
498
|
+
since: { type: "string", description: "ISO date to filter from (e.g. 2026-01-01)" },
|
|
499
|
+
limit: { type: "number", description: "Max results (default 50)" }
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
name: "get_staking_summary",
|
|
505
|
+
description: "Aggregate stats from the activity ledger: total operations, staked ETH, errors.",
|
|
506
|
+
parameters: {
|
|
507
|
+
type: "object",
|
|
508
|
+
properties: {
|
|
509
|
+
since: { type: "string", description: "ISO date to filter from (optional)" }
|
|
336
510
|
}
|
|
337
511
|
}
|
|
338
512
|
}
|
|
@@ -386,6 +560,54 @@ async function executeTool(name, args) {
|
|
|
386
560
|
case "update_settings":
|
|
387
561
|
result = updateSettings(args);
|
|
388
562
|
break;
|
|
563
|
+
case "get_l2_balance":
|
|
564
|
+
result = await getL2Balance(args.source_chain, args.address);
|
|
565
|
+
break;
|
|
566
|
+
case "get_bridge_quote":
|
|
567
|
+
result = await getBridgeQuote(args.source_chain, args.token, args.amount, args.to_token);
|
|
568
|
+
break;
|
|
569
|
+
case "bridge_to_ethereum":
|
|
570
|
+
result = await bridgeToEthereum(args.source_chain, args.token, args.amount, args.to_token, args.dry_run);
|
|
571
|
+
break;
|
|
572
|
+
case "get_bridge_status":
|
|
573
|
+
result = await getBridgeStatus(args.tx_hash, args.source_chain);
|
|
574
|
+
break;
|
|
575
|
+
case "set_alert":
|
|
576
|
+
result = setAlert(args);
|
|
577
|
+
break;
|
|
578
|
+
case "list_alerts":
|
|
579
|
+
result = listAlerts();
|
|
580
|
+
break;
|
|
581
|
+
case "remove_alert":
|
|
582
|
+
result = removeAlertById(args.id);
|
|
583
|
+
break;
|
|
584
|
+
case "configure_alert_channels":
|
|
585
|
+
result = configureAlertChannels(args);
|
|
586
|
+
break;
|
|
587
|
+
case "get_total_position":
|
|
588
|
+
result = await getTotalPosition(args.address);
|
|
589
|
+
break;
|
|
590
|
+
case "get_bounds":
|
|
591
|
+
result = loadBounds();
|
|
592
|
+
break;
|
|
593
|
+
case "set_bounds": {
|
|
594
|
+
const current = loadBounds();
|
|
595
|
+
const patch = args;
|
|
596
|
+
if (patch.maxStakePerTx !== void 0) current.maxStakePerTx = patch.maxStakePerTx;
|
|
597
|
+
if (patch.maxDailyStake !== void 0) current.maxDailyStake = patch.maxDailyStake;
|
|
598
|
+
if (patch.minEthReserve !== void 0) current.minEthReserve = patch.minEthReserve;
|
|
599
|
+
if (patch.autoRestakeThreshold !== void 0) current.autoRestakeThreshold = patch.autoRestakeThreshold;
|
|
600
|
+
if (patch.governanceAutoVote !== void 0) current.governanceAutoVote = patch.governanceAutoVote;
|
|
601
|
+
saveBounds(current);
|
|
602
|
+
result = current;
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
case "get_trade_history":
|
|
606
|
+
result = queryLedger({ tool: args.tool, since: args.since, limit: args.limit });
|
|
607
|
+
break;
|
|
608
|
+
case "get_staking_summary":
|
|
609
|
+
result = ledgerStats(args.since);
|
|
610
|
+
break;
|
|
389
611
|
default:
|
|
390
612
|
return `Unknown tool: ${name}`;
|
|
391
613
|
}
|
|
@@ -395,6 +617,50 @@ async function executeTool(name, args) {
|
|
|
395
617
|
}
|
|
396
618
|
}
|
|
397
619
|
|
|
620
|
+
// src/bounds/enforce.ts
|
|
621
|
+
var STAKE_TOOLS = /* @__PURE__ */ new Set(["stake_eth", "bridge_to_ethereum"]);
|
|
622
|
+
async function checkBounds(toolName, args) {
|
|
623
|
+
const bounds = loadBounds();
|
|
624
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
625
|
+
if (bounds.lastResetDate !== today) {
|
|
626
|
+
bounds.dailyStaked = 0;
|
|
627
|
+
bounds.lastResetDate = today;
|
|
628
|
+
saveBounds(bounds);
|
|
629
|
+
}
|
|
630
|
+
if (toolName === "cast_vote" && !bounds.governanceAutoVote) {
|
|
631
|
+
return { allowed: false, reason: `Governance auto-vote is disabled. Set bounds: governanceAutoVote = true` };
|
|
632
|
+
}
|
|
633
|
+
if (STAKE_TOOLS.has(toolName)) {
|
|
634
|
+
const amount = parseFloat(args.amount_eth ?? args.amount ?? "0");
|
|
635
|
+
if (isNaN(amount) || amount <= 0) return { allowed: true };
|
|
636
|
+
if (amount > bounds.maxStakePerTx) {
|
|
637
|
+
return { allowed: false, reason: `Amount ${amount} ETH exceeds max per tx (${bounds.maxStakePerTx} ETH)` };
|
|
638
|
+
}
|
|
639
|
+
if (bounds.dailyStaked + amount > bounds.maxDailyStake) {
|
|
640
|
+
return { allowed: false, reason: `Would exceed daily limit: staked today ${bounds.dailyStaked.toFixed(4)} + ${amount} > ${bounds.maxDailyStake} ETH` };
|
|
641
|
+
}
|
|
642
|
+
try {
|
|
643
|
+
const bal = await getBalance();
|
|
644
|
+
const ethBal = parseFloat(bal.balances.eth);
|
|
645
|
+
if (ethBal - amount < bounds.minEthReserve) {
|
|
646
|
+
return { allowed: false, reason: `Would leave only ${(ethBal - amount).toFixed(4)} ETH, below reserve of ${bounds.minEthReserve} ETH` };
|
|
647
|
+
}
|
|
648
|
+
} catch {
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return { allowed: true };
|
|
652
|
+
}
|
|
653
|
+
function recordStake(amount) {
|
|
654
|
+
const bounds = loadBounds();
|
|
655
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
656
|
+
if (bounds.lastResetDate !== today) {
|
|
657
|
+
bounds.dailyStaked = 0;
|
|
658
|
+
bounds.lastResetDate = today;
|
|
659
|
+
}
|
|
660
|
+
bounds.dailyStaked += amount;
|
|
661
|
+
saveBounds(bounds);
|
|
662
|
+
}
|
|
663
|
+
|
|
398
664
|
// src/chat/session.ts
|
|
399
665
|
var R = "\x1B[0m";
|
|
400
666
|
var B = "\x1B[1m";
|
|
@@ -412,30 +678,39 @@ ${CY}${B} \u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2
|
|
|
412
678
|
${CY}${B} \u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2554\u255D ${R}
|
|
413
679
|
${CY}${B} \u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 ${R}
|
|
414
680
|
${CY}${B} \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D ${R}
|
|
415
|
-
${D} powered by Lido
|
|
681
|
+
${D} powered by Lido${R}
|
|
416
682
|
`;
|
|
417
683
|
function ln(text = "") {
|
|
418
684
|
process.stdout.write(text + "\n");
|
|
419
685
|
}
|
|
686
|
+
function printResponse(text) {
|
|
687
|
+
const lines = text.split("\n");
|
|
688
|
+
const prefix = `${B}${GR}moly${R} \u203A `;
|
|
689
|
+
const indent = " ";
|
|
690
|
+
ln();
|
|
691
|
+
lines.forEach((line, i) => {
|
|
692
|
+
process.stdout.write((i === 0 ? prefix : indent) + line + "\n");
|
|
693
|
+
});
|
|
694
|
+
ln();
|
|
695
|
+
}
|
|
420
696
|
function saveTrade(toolName, args, result) {
|
|
421
697
|
try {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
698
|
+
let txHash;
|
|
699
|
+
let amount;
|
|
700
|
+
try {
|
|
701
|
+
const parsed = JSON.parse(result);
|
|
702
|
+
txHash = parsed.txHash ?? parsed.tx_hash;
|
|
703
|
+
amount = args.amount_eth ?? args.amount_steth ?? args.amount;
|
|
704
|
+
} catch {
|
|
705
|
+
}
|
|
706
|
+
logEntry({
|
|
428
707
|
tool: toolName,
|
|
429
708
|
args,
|
|
430
|
-
result
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
return result;
|
|
435
|
-
}
|
|
436
|
-
})()
|
|
709
|
+
result,
|
|
710
|
+
tx_hash: txHash,
|
|
711
|
+
amount,
|
|
712
|
+
status: "ok"
|
|
437
713
|
});
|
|
438
|
-
fs.appendFileSync(file, record + "\n");
|
|
439
714
|
} catch {
|
|
440
715
|
}
|
|
441
716
|
}
|
|
@@ -453,18 +728,44 @@ var WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
|
453
728
|
"claim_withdrawals",
|
|
454
729
|
"wrap_steth",
|
|
455
730
|
"unwrap_wsteth",
|
|
456
|
-
"cast_vote"
|
|
731
|
+
"cast_vote",
|
|
732
|
+
"bridge_to_ethereum"
|
|
457
733
|
]);
|
|
458
734
|
async function startChatSession(cfg) {
|
|
459
735
|
if (!cfg.ai) {
|
|
460
736
|
ln(`${RE}No AI provider configured. Run: moly setup${R}`);
|
|
461
737
|
process.exit(1);
|
|
462
738
|
}
|
|
739
|
+
try {
|
|
740
|
+
initLedger();
|
|
741
|
+
} catch {
|
|
742
|
+
}
|
|
463
743
|
const { provider, apiKey, model } = cfg.ai;
|
|
744
|
+
let skillContext = "";
|
|
745
|
+
try {
|
|
746
|
+
const skillPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../lido.skill.md");
|
|
747
|
+
if (fs.existsSync(skillPath)) {
|
|
748
|
+
skillContext = fs.readFileSync(skillPath, "utf-8") + "\n\n";
|
|
749
|
+
}
|
|
750
|
+
} catch {
|
|
751
|
+
}
|
|
464
752
|
const messages = [
|
|
465
753
|
{
|
|
466
754
|
role: "user",
|
|
467
|
-
content: `You are Moly, a terminal assistant for Lido Finance on ${cfg.network}.
|
|
755
|
+
content: skillContext + `You are Moly, a terminal assistant for Lido Finance on ${cfg.network}.
|
|
756
|
+
Mode: ${cfg.mode} (${cfg.mode === "simulation" ? "dry-run, nothing broadcast" : "LIVE - real on-chain transactions"}).
|
|
757
|
+
Chain scope: ${cfg.chainScope ?? "ethereum"}.
|
|
758
|
+
|
|
759
|
+
You can only do what your tools support: staking ETH, withdrawals, wrap/unwrap stETH/wstETH, balances, rewards, Lido DAO governance${cfg.chainScope === "all" ? ", and L2 bridging from Base/Arbitrum to Ethereum via LI.FI" : ""}.
|
|
760
|
+
${cfg.chainScope === "all" ? "If the user wants to stake ETH from Base or Arbitrum, first check their L2 balance with get_l2_balance, then bridge to Ethereum with bridge_to_ethereum, then after bridging completes use stake_eth. Bridge takes 1-20 min, tell user to check with get_bridge_status.\n" : ""}If asked about anything outside those tools (e.g. Lido Vaults, validators, node operators, DeFi integrations), say clearly and briefly that it is not supported.
|
|
761
|
+
|
|
762
|
+
OUTPUT FORMAT RULES (terminal, no markdown):
|
|
763
|
+
- Never use **bold**, _italic_, # headers, or backtick code blocks.
|
|
764
|
+
- Use plain dashes (-) for lists, one item per line.
|
|
765
|
+
- Use blank lines to separate sections or groups of information.
|
|
766
|
+
- For key/value data (balances, settings, etc.) use " key: value" format, one per line.
|
|
767
|
+
- Keep prose concise. Lead with the answer, then details.
|
|
768
|
+
- For live transactions always confirm with the user first.`
|
|
468
769
|
},
|
|
469
770
|
{
|
|
470
771
|
role: "assistant",
|
|
@@ -473,8 +774,8 @@ async function startChatSession(cfg) {
|
|
|
473
774
|
];
|
|
474
775
|
printBanner(cfg);
|
|
475
776
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
476
|
-
const prompt = () => new Promise((
|
|
477
|
-
rl.question(`${B}${BL}you${R} \u203A `,
|
|
777
|
+
const prompt = () => new Promise((resolve2, reject) => {
|
|
778
|
+
rl.question(`${B}${BL}you${R} \u203A `, resolve2);
|
|
478
779
|
rl.once("close", () => reject(new Error("closed")));
|
|
479
780
|
});
|
|
480
781
|
while (true) {
|
|
@@ -500,25 +801,38 @@ async function startChatSession(cfg) {
|
|
|
500
801
|
const toolResults = [];
|
|
501
802
|
for (const tc of response.toolCalls) {
|
|
502
803
|
ln(`${D} \u21B3 ${MA}${tc.name}${R}${D} ${JSON.stringify(tc.args)}${R}`);
|
|
804
|
+
if (WRITE_TOOLS.has(tc.name)) {
|
|
805
|
+
try {
|
|
806
|
+
const check = await checkBounds(tc.name, tc.args);
|
|
807
|
+
if (!check.allowed) {
|
|
808
|
+
ln(`${RE} \u2715 BLOCKED: ${check.reason}${R}`);
|
|
809
|
+
toolResults.push(makeToolResultMessage(provider, tc.id, tc.name, JSON.stringify({ blocked: true, reason: check.reason })));
|
|
810
|
+
continue;
|
|
811
|
+
}
|
|
812
|
+
} catch {
|
|
813
|
+
}
|
|
814
|
+
}
|
|
503
815
|
const result = await executeTool(tc.name, tc.args);
|
|
504
816
|
ln(`${D} ${result.slice(0, 300)}${result.length > 300 ? "\u2026" : ""}${R}`);
|
|
505
817
|
if (WRITE_TOOLS.has(tc.name)) {
|
|
506
818
|
saveTrade(tc.name, tc.args, result);
|
|
819
|
+
if (tc.name === "stake_eth" && tc.args.amount_eth) {
|
|
820
|
+
try {
|
|
821
|
+
recordStake(parseFloat(tc.args.amount_eth));
|
|
822
|
+
} catch {
|
|
823
|
+
}
|
|
824
|
+
}
|
|
507
825
|
}
|
|
508
826
|
toolResults.push(makeToolResultMessage(provider, tc.id, tc.name, result));
|
|
509
827
|
}
|
|
510
828
|
messages.push(...toolResults);
|
|
511
829
|
if (response.text) {
|
|
512
|
-
|
|
513
|
-
ln(`${B}${GR}moly${R} \u203A ${response.text}`);
|
|
514
|
-
ln();
|
|
830
|
+
printResponse(response.text);
|
|
515
831
|
}
|
|
516
832
|
continue;
|
|
517
833
|
}
|
|
518
834
|
if (response.text) {
|
|
519
|
-
|
|
520
|
-
ln(`${B}${GR}moly${R} \u203A ${response.text}`);
|
|
521
|
-
ln();
|
|
835
|
+
printResponse(response.text);
|
|
522
836
|
}
|
|
523
837
|
break;
|
|
524
838
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
addAlert,
|
|
4
|
+
loadAlerts,
|
|
5
|
+
loadChannelConfig,
|
|
6
|
+
removeAlert,
|
|
7
|
+
saveAlerts,
|
|
8
|
+
saveChannelConfig
|
|
9
|
+
} from "./chunk-6F64RPQQ.js";
|
|
10
|
+
import "./chunk-P6VFMSPM.js";
|
|
11
|
+
import "./chunk-PDX44BCA.js";
|
|
12
|
+
export {
|
|
13
|
+
addAlert,
|
|
14
|
+
loadAlerts,
|
|
15
|
+
loadChannelConfig,
|
|
16
|
+
removeAlert,
|
|
17
|
+
saveAlerts,
|
|
18
|
+
saveChannelConfig
|
|
19
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
exportLedger,
|
|
4
|
+
initLedger,
|
|
5
|
+
ledgerStats,
|
|
6
|
+
logEntry,
|
|
7
|
+
queryLedger
|
|
8
|
+
} from "./chunk-RR74UAKD.js";
|
|
9
|
+
import "./chunk-PDX44BCA.js";
|
|
10
|
+
export {
|
|
11
|
+
exportLedger,
|
|
12
|
+
initLedger,
|
|
13
|
+
ledgerStats,
|
|
14
|
+
logEntry,
|
|
15
|
+
queryLedger
|
|
16
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moly-mcp/lido",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "Lido MCP Server — stake, unstake, wrap, govern. Works with Claude Desktop, Cursor, Windsurf, and any MCP client.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,10 +23,15 @@
|
|
|
23
23
|
"@clack/prompts": "^0.9.1",
|
|
24
24
|
"@lidofinance/lido-ethereum-sdk": "^4.7.0",
|
|
25
25
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
26
|
+
"better-sqlite3": "^11.7.0",
|
|
26
27
|
"viem": "^2.47.4",
|
|
27
28
|
"zod": "^3.24.0"
|
|
28
29
|
},
|
|
30
|
+
"optionalDependencies": {
|
|
31
|
+
"@open-wallet-standard/core": "^0.1.0"
|
|
32
|
+
},
|
|
29
33
|
"devDependencies": {
|
|
34
|
+
"@types/better-sqlite3": "^7.6.12",
|
|
30
35
|
"@types/node": "^20",
|
|
31
36
|
"tsup": "^8.4.0",
|
|
32
37
|
"typescript": "^5"
|