agentlili 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.
@@ -0,0 +1,1468 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+ import {
4
+ WalletManager,
5
+ prisma
6
+ } from "./chunk-AAYS2L5P.js";
7
+
8
+ // src/mcp/server.ts
9
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
10
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
11
+ import {
12
+ CallToolRequestSchema,
13
+ ListToolsRequestSchema,
14
+ ListPromptsRequestSchema,
15
+ GetPromptRequestSchema
16
+ } from "@modelcontextprotocol/sdk/types.js";
17
+
18
+ // src/lib/audit-log.ts
19
+ import crypto from "crypto";
20
+ function hmacKey() {
21
+ return process.env.WALLET_ENCRYPTION_KEY ?? "audit-fallback-key";
22
+ }
23
+ function computeHmac(prevHash, data) {
24
+ return crypto.createHmac("sha256", hmacKey()).update(prevHash + data).digest("hex");
25
+ }
26
+ async function readAuditLog(opts = {}) {
27
+ const where = {};
28
+ if (opts.action) where.action = opts.action;
29
+ if (opts.walletId) where.walletId = opts.walletId;
30
+ if (opts.agentId) where.agentId = opts.agentId;
31
+ if (opts.after || opts.before) {
32
+ const timestampFilter = {};
33
+ if (opts.after) timestampFilter.gt = new Date(opts.after);
34
+ if (opts.before) timestampFilter.lt = new Date(opts.before);
35
+ where.timestamp = timestampFilter;
36
+ }
37
+ const rows = await prisma.auditLog.findMany({
38
+ where,
39
+ orderBy: { seq: "desc" },
40
+ skip: opts.offset ?? 0,
41
+ take: opts.limit ?? 100
42
+ });
43
+ return rows.map((row) => ({
44
+ timestamp: row.timestamp.toISOString(),
45
+ seq: row.seq,
46
+ action: row.action,
47
+ walletId: row.walletId ?? void 0,
48
+ agentId: row.agentId ?? void 0,
49
+ ip: row.ip ?? void 0,
50
+ outcome: row.outcome,
51
+ details: row.details ?? void 0,
52
+ integrity: row.integrity
53
+ }));
54
+ }
55
+ async function verifyAuditIntegrity() {
56
+ const rows = await prisma.auditLog.findMany({
57
+ orderBy: { seq: "asc" }
58
+ });
59
+ if (rows.length === 0) return { valid: true, total: 0 };
60
+ let prevHash = "genesis";
61
+ for (const row of rows) {
62
+ const entry = {
63
+ timestamp: row.timestamp.toISOString(),
64
+ seq: row.seq,
65
+ action: row.action,
66
+ walletId: row.walletId ?? void 0,
67
+ agentId: row.agentId ?? void 0,
68
+ ip: row.ip ?? void 0,
69
+ outcome: row.outcome,
70
+ details: row.details ?? void 0
71
+ };
72
+ const rawForHmac = {
73
+ timestamp: entry.timestamp,
74
+ action: entry.action,
75
+ outcome: entry.outcome,
76
+ seq: entry.seq
77
+ };
78
+ if (entry.walletId !== void 0) rawForHmac.walletId = entry.walletId;
79
+ if (entry.agentId !== void 0) rawForHmac.agentId = entry.agentId;
80
+ if (entry.ip !== void 0) rawForHmac.ip = entry.ip;
81
+ if (entry.details !== void 0) rawForHmac.details = entry.details;
82
+ const entryData = JSON.stringify(rawForHmac);
83
+ const expected = computeHmac(prevHash, entryData);
84
+ if (expected !== row.integrity) {
85
+ return { valid: false, total: rows.length, brokenAt: row.seq };
86
+ }
87
+ prevHash = row.integrity;
88
+ }
89
+ return { valid: true, total: rows.length };
90
+ }
91
+
92
+ // src/lib/spending-limits.ts
93
+ var DEFAULT_LIMITS = {
94
+ maxPerTx: 1,
95
+ maxPerDay: 5,
96
+ enabled: true
97
+ };
98
+ var DAY_MS = 24 * 60 * 60 * 1e3;
99
+ async function getSpendingLimits(walletId) {
100
+ const row = await prisma.spendingLimits.findUnique({
101
+ where: { walletId }
102
+ });
103
+ if (!row) {
104
+ return { ...DEFAULT_LIMITS };
105
+ }
106
+ return {
107
+ maxPerTx: row.maxPerTx,
108
+ maxPerDay: row.maxPerDay,
109
+ enabled: row.enabled
110
+ };
111
+ }
112
+ async function setSpendingLimits(walletId, config) {
113
+ const current = await getSpendingLimits(walletId);
114
+ const updated = {
115
+ maxPerTx: typeof config.maxPerTx === "number" ? Math.max(0, config.maxPerTx) : current.maxPerTx,
116
+ maxPerDay: typeof config.maxPerDay === "number" ? Math.max(0, config.maxPerDay) : current.maxPerDay,
117
+ enabled: typeof config.enabled === "boolean" ? config.enabled : current.enabled
118
+ };
119
+ await prisma.spendingLimits.upsert({
120
+ where: { walletId },
121
+ create: {
122
+ walletId,
123
+ maxPerTx: updated.maxPerTx,
124
+ maxPerDay: updated.maxPerDay,
125
+ enabled: updated.enabled
126
+ },
127
+ update: {
128
+ maxPerTx: updated.maxPerTx,
129
+ maxPerDay: updated.maxPerDay,
130
+ enabled: updated.enabled
131
+ }
132
+ });
133
+ return updated;
134
+ }
135
+ async function getDailySpending(walletId) {
136
+ const cutoff = new Date(Date.now() - DAY_MS);
137
+ const result = await prisma.spendingRecord.aggregate({
138
+ where: {
139
+ walletId,
140
+ timestamp: { gte: cutoff }
141
+ },
142
+ _sum: { amount: true }
143
+ });
144
+ const total = result._sum.amount ?? 0;
145
+ return Math.round(total * 1e9) / 1e9;
146
+ }
147
+ async function getSpendingSummary(walletId) {
148
+ const limits = await getSpendingLimits(walletId);
149
+ const dailySpent = await getDailySpending(walletId);
150
+ const dailyRemaining = limits.enabled && limits.maxPerDay > 0 ? Math.max(0, limits.maxPerDay - dailySpent) : null;
151
+ return { limits, dailySpent, dailyRemaining };
152
+ }
153
+
154
+ // src/lib/agent-lifecycle.ts
155
+ var VALID_LIFECYCLE_STATES = [
156
+ "active",
157
+ "paused",
158
+ "terminated"
159
+ ];
160
+ var MAX_TRANSITIONS = 10;
161
+ var VALID_TRANSITIONS = {
162
+ active: ["paused", "terminated"],
163
+ paused: ["active", "terminated"],
164
+ terminated: []
165
+ };
166
+ async function getAgentLifecycle(walletId) {
167
+ const row = await prisma.agentLifecycle.findUnique({
168
+ where: { walletId }
169
+ });
170
+ if (!row) {
171
+ return {
172
+ state: "active",
173
+ updatedAt: "",
174
+ transitions: []
175
+ };
176
+ }
177
+ const state = VALID_LIFECYCLE_STATES.includes(row.state) ? row.state : "active";
178
+ return {
179
+ state,
180
+ updatedAt: row.updatedAt.toISOString(),
181
+ reason: row.reason ?? void 0,
182
+ transitions: row.transitions ?? []
183
+ };
184
+ }
185
+ function validateTransition(from, to) {
186
+ if (from === to) {
187
+ return `Agent is already in "${to}" state`;
188
+ }
189
+ const allowed = VALID_TRANSITIONS[from];
190
+ if (!allowed.includes(to)) {
191
+ return `Cannot transition from "${from}" to "${to}". Terminated agents cannot be restarted.`;
192
+ }
193
+ return null;
194
+ }
195
+ async function setAgentLifecycle(walletId, newState, reason) {
196
+ if (!VALID_LIFECYCLE_STATES.includes(newState)) {
197
+ throw new Error(
198
+ `Invalid lifecycle state: ${newState}. Must be one of: ${VALID_LIFECYCLE_STATES.join(", ")}`
199
+ );
200
+ }
201
+ const current = await getAgentLifecycle(walletId);
202
+ const error = validateTransition(current.state, newState);
203
+ if (error) {
204
+ throw new Error(error);
205
+ }
206
+ const now = (/* @__PURE__ */ new Date()).toISOString();
207
+ const transition = {
208
+ from: current.state,
209
+ to: newState,
210
+ timestamp: now,
211
+ reason
212
+ };
213
+ const transitions = [...current.transitions, transition].slice(
214
+ -MAX_TRANSITIONS
215
+ );
216
+ await prisma.agentLifecycle.upsert({
217
+ where: { walletId },
218
+ update: {
219
+ state: newState,
220
+ reason: reason ?? null,
221
+ transitions
222
+ },
223
+ create: {
224
+ walletId,
225
+ state: newState,
226
+ reason: reason ?? null,
227
+ transitions
228
+ }
229
+ });
230
+ return {
231
+ state: newState,
232
+ updatedAt: now,
233
+ reason,
234
+ transitions
235
+ };
236
+ }
237
+ async function isAgentExecutable(walletId) {
238
+ const lifecycle = await getAgentLifecycle(walletId);
239
+ if (lifecycle.state === "active") {
240
+ return { allowed: true, state: "active" };
241
+ }
242
+ const reason = lifecycle.state === "paused" ? "Agent is paused. Resume it to continue execution." : "Agent is terminated. Create a new agent to continue.";
243
+ return { allowed: false, state: lifecycle.state, reason };
244
+ }
245
+
246
+ // src/lib/tx-log.ts
247
+ async function readTxLog(walletId, limit = 50) {
248
+ const rows = await prisma.txLog.findMany({
249
+ where: { walletId },
250
+ orderBy: { timestamp: "desc" },
251
+ take: limit
252
+ });
253
+ return rows.map((row) => ({
254
+ timestamp: row.timestamp.toISOString(),
255
+ walletId: row.walletId,
256
+ toolName: row.toolName,
257
+ args: row.args,
258
+ result: row.result,
259
+ success: row.success
260
+ }));
261
+ }
262
+
263
+ // src/lib/agent-strategy.ts
264
+ var STRATEGY_PRESETS = {
265
+ conservative: {
266
+ id: "conservative",
267
+ name: "Conservative",
268
+ description: "Low risk \u2014 small positions, only well-known protocols, no leverage. Prioritizes capital preservation.",
269
+ maxPositionSize: 0.1,
270
+ slippageBps: 100,
271
+ allowLeverage: false,
272
+ allowUnverifiedTokens: false,
273
+ maxOpenPositions: 2,
274
+ protocolGuidance: "Only use well-established protocols: Jupiter (swaps), Marinade/Jito (liquid staking). Avoid Drift, Adrena, and leveraged products.",
275
+ promptGuidance: [
276
+ "You are operating in CONSERVATIVE mode. Capital preservation is your top priority.",
277
+ "- Maximum position size: 10% of wallet balance per trade",
278
+ "- Use tight slippage (1% / 100 bps) \u2014 reject trades with higher slippage",
279
+ "- Only interact with well-known, audited protocols (Jupiter, Orca, Marinade, Jito)",
280
+ "- Do NOT use leverage, perpetuals, or margin trading",
281
+ "- Do NOT trade unverified or low-liquidity tokens",
282
+ "- Maximum 2 open positions at a time",
283
+ "- Always check balance before and after trades",
284
+ "- Prefer liquid staking (mSOL, jitoSOL) over risky DeFi yields",
285
+ "- If unsure about a trade, err on the side of NOT executing it"
286
+ ].join("\n")
287
+ },
288
+ balanced: {
289
+ id: "balanced",
290
+ name: "Balanced",
291
+ description: "Moderate risk \u2014 standard positions, most protocols allowed, moderate slippage tolerance. Good all-around strategy.",
292
+ maxPositionSize: 0.25,
293
+ slippageBps: 300,
294
+ allowLeverage: false,
295
+ allowUnverifiedTokens: false,
296
+ maxOpenPositions: 5,
297
+ protocolGuidance: "Use established protocols freely: Jupiter, Orca, Raydium, Meteora for swaps/LP, Marinade/Jito for staking, Lulo for lending. Avoid leverage protocols.",
298
+ promptGuidance: [
299
+ "You are operating in BALANCED mode. Seek reasonable returns while managing risk.",
300
+ "- Maximum position size: 25% of wallet balance per trade",
301
+ "- Standard slippage tolerance (3% / 300 bps)",
302
+ "- Use established protocols: Jupiter, Orca, Raydium, Meteora, Marinade, Jito, Lulo",
303
+ "- Do NOT use leverage or perpetual trading",
304
+ "- Only trade tokens with reasonable liquidity",
305
+ "- Maximum 5 open positions at a time",
306
+ "- Diversify across different protocols and strategies when possible",
307
+ "- Provide liquidity on concentrated AMMs only if the range is reasonable",
308
+ "- Monitor position health and rebalance if needed"
309
+ ].join("\n")
310
+ },
311
+ aggressive: {
312
+ id: "aggressive",
313
+ name: "Aggressive",
314
+ description: "High risk \u2014 large positions, all protocols including leverage, wider slippage. Maximizes upside potential.",
315
+ maxPositionSize: 0.5,
316
+ slippageBps: 500,
317
+ allowLeverage: true,
318
+ allowUnverifiedTokens: true,
319
+ maxOpenPositions: 10,
320
+ protocolGuidance: "All protocols available: Jupiter, Orca, Raydium, Meteora, Drift, Adrena, Lulo, and experimental protocols. Leverage and perps are allowed.",
321
+ promptGuidance: [
322
+ "You are operating in AGGRESSIVE mode. Maximize returns \u2014 higher risk is acceptable.",
323
+ "- Maximum position size: 50% of wallet balance per trade",
324
+ "- Wide slippage tolerance (5% / 500 bps) for fast execution",
325
+ "- All protocols available including Drift and Adrena for perpetuals",
326
+ "- Leverage and margin trading are ALLOWED \u2014 use up to 3x leverage max",
327
+ "- New and trending tokens are allowed if requested",
328
+ "- Maximum 10 open positions at a time",
329
+ "- Actively seek yield farming and liquidity provision opportunities",
330
+ "- Execute multi-step DeFi strategies: swap \u2192 LP \u2192 stake for compound yields",
331
+ "- Act decisively \u2014 speed of execution matters"
332
+ ].join("\n")
333
+ }
334
+ };
335
+ var VALID_STRATEGY_IDS = [
336
+ "conservative",
337
+ "balanced",
338
+ "aggressive"
339
+ ];
340
+ var DEFAULT_STRATEGY_ID = "balanced";
341
+ async function getWalletStrategy(walletId) {
342
+ const row = await prisma.agentStrategyConfig.findUnique({
343
+ where: { walletId }
344
+ });
345
+ if (!row) {
346
+ return { strategyId: DEFAULT_STRATEGY_ID, updatedAt: "" };
347
+ }
348
+ const id = VALID_STRATEGY_IDS.includes(row.strategyId) ? row.strategyId : DEFAULT_STRATEGY_ID;
349
+ return {
350
+ strategyId: id,
351
+ updatedAt: row.updatedAt.toISOString()
352
+ };
353
+ }
354
+
355
+ // src/lib/agent-monitor.ts
356
+ var EXECUTING_THRESHOLD_MS = 3e4;
357
+ function deriveStatus(lastAction) {
358
+ if (!lastAction) return "idle";
359
+ const elapsed = Date.now() - new Date(lastAction.timestamp).getTime();
360
+ if (elapsed < EXECUTING_THRESHOLD_MS) {
361
+ return lastAction.success ? "executing" : "error";
362
+ }
363
+ if (!lastAction.success) return "error";
364
+ return "idle";
365
+ }
366
+ function extractLastAction(entries) {
367
+ if (entries.length === 0) return null;
368
+ const latest = entries[0];
369
+ return {
370
+ toolName: latest.toolName,
371
+ success: latest.success,
372
+ timestamp: latest.timestamp
373
+ };
374
+ }
375
+ function calcSuccessRate(entries) {
376
+ if (entries.length === 0) return 1;
377
+ const successes = entries.filter((e) => e.success).length;
378
+ return successes / entries.length;
379
+ }
380
+ async function buildAgentEntry(wallet, balance, txEntries) {
381
+ const lastAction = extractLastAction(txEntries);
382
+ const lifecycle = await getAgentLifecycle(wallet.id);
383
+ const status = lifecycle.state === "paused" ? "paused" : lifecycle.state === "terminated" ? "terminated" : deriveStatus(lastAction);
384
+ const strategy = await getWalletStrategy(wallet.id);
385
+ const spending = await getSpendingSummary(wallet.id);
386
+ return {
387
+ id: wallet.id,
388
+ publicKey: wallet.publicKey,
389
+ label: wallet.label || `Agent ${wallet.id}`,
390
+ createdAt: wallet.createdAt,
391
+ balance,
392
+ status,
393
+ lifecycleState: lifecycle.state,
394
+ strategy: strategy.strategyId,
395
+ lastAction,
396
+ spending: {
397
+ dailySpent: spending.dailySpent,
398
+ dailyLimit: spending.limits.maxPerDay,
399
+ dailyRemaining: spending.dailyRemaining,
400
+ enabled: spending.limits.enabled
401
+ },
402
+ totalActions: txEntries.length,
403
+ successRate: calcSuccessRate(txEntries)
404
+ };
405
+ }
406
+ async function buildAgentMonitor(manager) {
407
+ const allWallets = await manager.listWallets();
408
+ const wallets = allWallets.filter((w) => w.isAgent !== false);
409
+ const entries = await Promise.all(
410
+ wallets.map(async (wallet) => {
411
+ let balance = 0;
412
+ try {
413
+ balance = await manager.getBalance(wallet.publicKey);
414
+ } catch {
415
+ }
416
+ const txEntries = await readTxLog(wallet.id, 50);
417
+ return buildAgentEntry(wallet, balance, txEntries);
418
+ })
419
+ );
420
+ const statusOrder = {
421
+ executing: 0,
422
+ error: 1,
423
+ idle: 2,
424
+ paused: 3,
425
+ terminated: 4
426
+ };
427
+ entries.sort((a, b) => {
428
+ const statusDiff = statusOrder[a.status] - statusOrder[b.status];
429
+ if (statusDiff !== 0) return statusDiff;
430
+ return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
431
+ });
432
+ return {
433
+ agents: entries,
434
+ totalAgents: entries.length,
435
+ activeAgents: entries.filter((e) => e.status === "executing").length,
436
+ errorAgents: entries.filter((e) => e.status === "error").length,
437
+ pausedAgents: entries.filter((e) => e.status === "paused").length,
438
+ terminatedAgents: entries.filter((e) => e.status === "terminated").length,
439
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
440
+ };
441
+ }
442
+
443
+ // src/lib/portfolio.ts
444
+ var SOL_MINT = "So11111111111111111111111111111111111111112";
445
+ var JUPITER_PRICE_URL = "https://api.jup.ag/price/v2";
446
+ var PRICE_TIMEOUT_MS = 8e3;
447
+ var KNOWN_TOKENS = {
448
+ [SOL_MINT]: "SOL",
449
+ EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v: "USDC",
450
+ Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB: "USDT",
451
+ mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So: "mSOL",
452
+ "7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj": "stSOL",
453
+ DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263: "BONK",
454
+ JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN: "JUP"
455
+ };
456
+ function resolveSymbol(mint) {
457
+ return KNOWN_TOKENS[mint] ?? `${mint.slice(0, 4)}\u2026${mint.slice(-4)}`;
458
+ }
459
+ var ALLOC_COLORS = [
460
+ "#22c55e",
461
+ "#3b82f6",
462
+ "#f59e0b",
463
+ "#ef4444",
464
+ "#8b5cf6",
465
+ "#ec4899",
466
+ "#06b6d4",
467
+ "#f97316",
468
+ "#14b8a6",
469
+ "#6366f1"
470
+ ];
471
+ async function fetchPrices(mints) {
472
+ const prices = /* @__PURE__ */ new Map();
473
+ if (mints.length === 0) return prices;
474
+ try {
475
+ const ids = mints.join(",");
476
+ const controller = new AbortController();
477
+ const timer = setTimeout(() => controller.abort(), PRICE_TIMEOUT_MS);
478
+ const res = await fetch(`${JUPITER_PRICE_URL}?ids=${ids}`, {
479
+ signal: controller.signal
480
+ });
481
+ clearTimeout(timer);
482
+ if (!res.ok) return prices;
483
+ const json = await res.json();
484
+ for (const [mint, info] of Object.entries(json.data ?? {})) {
485
+ if (info.price) {
486
+ const p = parseFloat(info.price);
487
+ if (Number.isFinite(p) && p > 0) prices.set(mint, p);
488
+ }
489
+ }
490
+ } catch {
491
+ }
492
+ return prices;
493
+ }
494
+ async function buildPortfolio(manager) {
495
+ const wallets = await manager.listWallets();
496
+ const walletData = await Promise.all(
497
+ wallets.map(async (w) => {
498
+ const [solBalance, tokens] = await Promise.all([
499
+ manager.getBalance(w.publicKey).catch(() => 0),
500
+ manager.getTokenBalances(w.publicKey).catch(() => [])
501
+ ]);
502
+ return { wallet: w, solBalance, tokens };
503
+ })
504
+ );
505
+ const allMints = /* @__PURE__ */ new Set([SOL_MINT]);
506
+ for (const { tokens } of walletData) {
507
+ for (const t of tokens) allMints.add(t.mint);
508
+ }
509
+ const prices = await fetchPrices([...allMints]);
510
+ const solPrice = prices.get(SOL_MINT) ?? null;
511
+ const walletHoldings = walletData.map(
512
+ ({ wallet, solBalance, tokens }) => {
513
+ const solValueUsd = solPrice !== null ? solBalance * solPrice : null;
514
+ const tokenHoldings = tokens.map((t) => {
515
+ const priceUsd = prices.get(t.mint) ?? null;
516
+ const valueUsd = priceUsd !== null ? t.balance * priceUsd : null;
517
+ return {
518
+ mint: t.mint,
519
+ symbol: resolveSymbol(t.mint),
520
+ balance: t.balance,
521
+ decimals: t.decimals,
522
+ priceUsd,
523
+ valueUsd
524
+ };
525
+ });
526
+ const tokenValueSum = tokenHoldings.reduce(
527
+ (s, t) => s + (t.valueUsd ?? 0),
528
+ 0
529
+ );
530
+ const totalValueUsd2 = solValueUsd !== null ? solValueUsd + tokenValueSum : null;
531
+ return {
532
+ walletId: wallet.id,
533
+ publicKey: wallet.publicKey,
534
+ label: wallet.label || `Agent ${wallet.id.slice(0, 6)}`,
535
+ solBalance,
536
+ solValueUsd,
537
+ tokens: tokenHoldings,
538
+ totalValueUsd: totalValueUsd2
539
+ };
540
+ }
541
+ );
542
+ const allocMap = /* @__PURE__ */ new Map();
543
+ const totalSolValue = walletHoldings.reduce(
544
+ (s, w) => s + (w.solValueUsd ?? 0),
545
+ 0
546
+ );
547
+ if (totalSolValue > 0) {
548
+ allocMap.set(SOL_MINT, { label: "SOL", valueUsd: totalSolValue });
549
+ }
550
+ for (const wh of walletHoldings) {
551
+ for (const t of wh.tokens) {
552
+ if (t.valueUsd && t.valueUsd > 0) {
553
+ const ex = allocMap.get(t.mint);
554
+ if (ex) ex.valueUsd += t.valueUsd;
555
+ else allocMap.set(t.mint, { label: t.symbol, valueUsd: t.valueUsd });
556
+ }
557
+ }
558
+ }
559
+ const totalValueUsd = walletHoldings.reduce(
560
+ (s, w) => s + (w.totalValueUsd ?? 0),
561
+ 0
562
+ );
563
+ const sortedAlloc = [...allocMap.entries()].sort(
564
+ (a, b) => b[1].valueUsd - a[1].valueUsd
565
+ );
566
+ const allocation = sortedAlloc.map(
567
+ ([mint, { label, valueUsd }], i) => ({
568
+ label,
569
+ mint,
570
+ valueUsd,
571
+ percentage: totalValueUsd > 0 ? valueUsd / totalValueUsd * 100 : 0,
572
+ color: ALLOC_COLORS[i % ALLOC_COLORS.length]
573
+ })
574
+ );
575
+ return {
576
+ totalValueUsd: solPrice !== null ? totalValueUsd : null,
577
+ solPrice,
578
+ walletCount: wallets.length,
579
+ wallets: walletHoldings,
580
+ allocation,
581
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
582
+ };
583
+ }
584
+
585
+ // src/lib/fraud-monitor.ts
586
+ import crypto3 from "crypto";
587
+
588
+ // src/lib/notifications.ts
589
+ import crypto2 from "crypto";
590
+
591
+ // src/lib/event-bus.ts
592
+ var encoder = new TextEncoder();
593
+ var nextEventId = 1;
594
+ var EventBus = class {
595
+ constructor() {
596
+ this.clients = /* @__PURE__ */ new Map();
597
+ this.listeners = [];
598
+ }
599
+ /** Register a non-SSE listener. Returns a cleanup function. */
600
+ addListener(fn) {
601
+ this.listeners.push(fn);
602
+ return () => {
603
+ this.listeners = this.listeners.filter((l) => l !== fn);
604
+ };
605
+ }
606
+ /** Register a new SSE client. Returns a cleanup function. */
607
+ addClient(client) {
608
+ this.clients.set(client.id, client);
609
+ return () => {
610
+ this.clients.delete(client.id);
611
+ };
612
+ }
613
+ /** Get count of connected clients. */
614
+ get clientCount() {
615
+ return this.clients.size;
616
+ }
617
+ /** Emit a typed event to all matching clients. Returns the full event. */
618
+ emit(partial) {
619
+ const event = {
620
+ ...partial,
621
+ id: String(nextEventId++),
622
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
623
+ };
624
+ const encoded = encoder.encode(`data: ${JSON.stringify(event)}
625
+
626
+ `);
627
+ for (const [clientId, client] of this.clients) {
628
+ if (client.walletId && event.walletId && client.walletId !== event.walletId) {
629
+ continue;
630
+ }
631
+ try {
632
+ client.controller.enqueue(encoded);
633
+ } catch {
634
+ this.clients.delete(clientId);
635
+ }
636
+ }
637
+ for (const listener of this.listeners) {
638
+ try {
639
+ listener(event);
640
+ } catch {
641
+ }
642
+ }
643
+ return event;
644
+ }
645
+ /** Send a keep-alive comment to all connected clients. */
646
+ ping() {
647
+ const encoded = encoder.encode(`: ping
648
+
649
+ `);
650
+ for (const [clientId, client] of this.clients) {
651
+ try {
652
+ client.controller.enqueue(encoded);
653
+ } catch {
654
+ this.clients.delete(clientId);
655
+ }
656
+ }
657
+ }
658
+ /** Remove all clients and listeners — only for tests. */
659
+ _reset() {
660
+ this.clients.clear();
661
+ this.listeners = [];
662
+ nextEventId = 1;
663
+ }
664
+ };
665
+ var GLOBAL_KEY = "__agent_event_bus__";
666
+ function getEventBus() {
667
+ const g = globalThis;
668
+ if (!g[GLOBAL_KEY]) {
669
+ g[GLOBAL_KEY] = new EventBus();
670
+ }
671
+ return g[GLOBAL_KEY];
672
+ }
673
+ var eventBus = getEventBus();
674
+
675
+ // src/lib/fraud-monitor.ts
676
+ async function getAlerts(walletId, opts) {
677
+ const where = { walletId };
678
+ if (opts?.unacknowledgedOnly) {
679
+ where.acknowledged = false;
680
+ }
681
+ const rows = await prisma.fraudAlert.findMany({
682
+ where,
683
+ orderBy: { timestamp: "desc" }
684
+ });
685
+ return rows.map((row) => ({
686
+ id: row.id,
687
+ walletId: row.walletId,
688
+ rule: row.rule,
689
+ severity: row.severity,
690
+ timestamp: row.timestamp.toISOString(),
691
+ details: row.details,
692
+ acknowledged: row.acknowledged
693
+ }));
694
+ }
695
+ async function getFraudStats(walletId) {
696
+ const alerts = await getAlerts(walletId);
697
+ const bySeverity = {};
698
+ const byRule = {};
699
+ let unacknowledged = 0;
700
+ for (const a of alerts) {
701
+ bySeverity[a.severity] = (bySeverity[a.severity] ?? 0) + 1;
702
+ byRule[a.rule] = (byRule[a.rule] ?? 0) + 1;
703
+ if (!a.acknowledged) unacknowledged++;
704
+ }
705
+ return {
706
+ totalAlerts: alerts.length,
707
+ unacknowledged,
708
+ bySeverity,
709
+ byRule
710
+ };
711
+ }
712
+
713
+ // src/mcp/tools.ts
714
+ var _manager = null;
715
+ function getManager(ctx) {
716
+ if (!_manager) {
717
+ _manager = new WalletManager(ctx.encryptionKey, ctx.rpcUrl);
718
+ }
719
+ return _manager;
720
+ }
721
+ var TOOLS = [
722
+ // ── Wallet Operations ──
723
+ {
724
+ name: "stng_wallet_create",
725
+ description: "Create a new Solana wallet with AES-256-GCM encryption. On devnet, automatically airdrops 1 SOL. Returns wallet ID, public key, and balance.",
726
+ inputSchema: {
727
+ type: "object",
728
+ properties: {
729
+ label: {
730
+ type: "string",
731
+ description: "Human-readable label for the wallet (e.g., 'trading-bot-1')"
732
+ }
733
+ },
734
+ required: []
735
+ }
736
+ },
737
+ {
738
+ name: "stng_wallet_list",
739
+ description: "List all wallets managed by this instance. Returns wallet IDs, public keys, labels, and creation dates.",
740
+ inputSchema: {
741
+ type: "object",
742
+ properties: {},
743
+ required: []
744
+ }
745
+ },
746
+ {
747
+ name: "stng_wallet_balance",
748
+ description: "Get the SOL balance of a wallet by its public key (base58). Returns balance in SOL.",
749
+ inputSchema: {
750
+ type: "object",
751
+ properties: {
752
+ public_key: {
753
+ type: "string",
754
+ description: "Solana public key (base58 encoded)"
755
+ }
756
+ },
757
+ required: ["public_key"]
758
+ }
759
+ },
760
+ {
761
+ name: "stng_wallet_tokens",
762
+ description: "Get all SPL token balances for a wallet. Returns mint addresses, balances, and decimals.",
763
+ inputSchema: {
764
+ type: "object",
765
+ properties: {
766
+ public_key: {
767
+ type: "string",
768
+ description: "Solana public key (base58 encoded)"
769
+ }
770
+ },
771
+ required: ["public_key"]
772
+ }
773
+ },
774
+ {
775
+ name: "stng_wallet_airdrop",
776
+ description: "Request a devnet SOL airdrop to a wallet. Only works on devnet/localnet. Default: 1 SOL.",
777
+ inputSchema: {
778
+ type: "object",
779
+ properties: {
780
+ public_key: {
781
+ type: "string",
782
+ description: "Solana public key (base58 encoded)"
783
+ },
784
+ amount: {
785
+ type: "number",
786
+ description: "Amount of SOL to airdrop (default: 1)"
787
+ }
788
+ },
789
+ required: ["public_key"]
790
+ }
791
+ },
792
+ // ── FROST Threshold Signing ──
793
+ {
794
+ name: "stng_frost_create",
795
+ description: "Create a FROST 2-of-3 threshold signing wallet. Uses Shamir secret sharing (RFC 9591) so no single party holds the full key. Returns wallet ID and group public key.",
796
+ inputSchema: {
797
+ type: "object",
798
+ properties: {
799
+ label: {
800
+ type: "string",
801
+ description: "Label for the FROST wallet"
802
+ },
803
+ threshold: {
804
+ type: "number",
805
+ description: "Signing threshold (default: 2)"
806
+ },
807
+ total_shares: {
808
+ type: "number",
809
+ description: "Total key shares (default: 3)"
810
+ }
811
+ },
812
+ required: []
813
+ }
814
+ },
815
+ {
816
+ name: "stng_frost_list",
817
+ description: "List all FROST threshold wallets with their group public keys, thresholds, and share counts.",
818
+ inputSchema: {
819
+ type: "object",
820
+ properties: {},
821
+ required: []
822
+ }
823
+ },
824
+ {
825
+ name: "stng_frost_verify",
826
+ description: "Verify the integrity of a FROST wallet's key shares. Confirms shares can reconstruct a valid group signature.",
827
+ inputSchema: {
828
+ type: "object",
829
+ properties: {
830
+ wallet_id: {
831
+ type: "string",
832
+ description: "FROST wallet ID (8-char hex)"
833
+ }
834
+ },
835
+ required: ["wallet_id"]
836
+ }
837
+ },
838
+ // ── Agent Lifecycle ──
839
+ {
840
+ name: "stng_agent_status",
841
+ description: "Get the lifecycle state (active/paused/terminated) of an agent wallet, including transition history and execution eligibility.",
842
+ inputSchema: {
843
+ type: "object",
844
+ properties: {
845
+ wallet_id: {
846
+ type: "string",
847
+ description: "Agent wallet ID (8-char hex)"
848
+ }
849
+ },
850
+ required: ["wallet_id"]
851
+ }
852
+ },
853
+ {
854
+ name: "stng_agent_pause",
855
+ description: "Pause an active agent. Paused agents cannot execute transactions but retain their wallets and state.",
856
+ inputSchema: {
857
+ type: "object",
858
+ properties: {
859
+ wallet_id: {
860
+ type: "string",
861
+ description: "Agent wallet ID to pause"
862
+ },
863
+ reason: {
864
+ type: "string",
865
+ description: "Reason for pausing (logged in audit trail)"
866
+ }
867
+ },
868
+ required: ["wallet_id"]
869
+ }
870
+ },
871
+ {
872
+ name: "stng_agent_resume",
873
+ description: "Resume a paused agent, restoring its ability to execute transactions.",
874
+ inputSchema: {
875
+ type: "object",
876
+ properties: {
877
+ wallet_id: {
878
+ type: "string",
879
+ description: "Agent wallet ID to resume"
880
+ },
881
+ reason: {
882
+ type: "string",
883
+ description: "Reason for resuming (logged in audit trail)"
884
+ }
885
+ },
886
+ required: ["wallet_id"]
887
+ }
888
+ },
889
+ {
890
+ name: "stng_agent_monitor",
891
+ description: "Get fleet-wide agent monitoring dashboard. Shows all agents with balances, statuses, strategies, success rates, and spending summaries.",
892
+ inputSchema: {
893
+ type: "object",
894
+ properties: {},
895
+ required: []
896
+ }
897
+ },
898
+ // ── Spending & Security ──
899
+ {
900
+ name: "stng_spending_limits",
901
+ description: "Get or update spending limits for a wallet. Returns per-transaction limit, daily limit, and current usage.",
902
+ inputSchema: {
903
+ type: "object",
904
+ properties: {
905
+ wallet_id: {
906
+ type: "string",
907
+ description: "Wallet ID (8-char hex)"
908
+ },
909
+ action: {
910
+ type: "string",
911
+ enum: ["get", "set"],
912
+ description: "Whether to get current limits or set new ones (default: get)"
913
+ },
914
+ per_tx_limit: {
915
+ type: "number",
916
+ description: "Max SOL per transaction (only for action=set)"
917
+ },
918
+ daily_limit: {
919
+ type: "number",
920
+ description: "Max SOL per 24h (only for action=set)"
921
+ }
922
+ },
923
+ required: ["wallet_id"]
924
+ }
925
+ },
926
+ {
927
+ name: "stng_audit_log",
928
+ description: "Read the HMAC-chained audit log. Filter by action type, wallet ID, agent ID, or time range. Returns tamper-evident entries newest-first.",
929
+ inputSchema: {
930
+ type: "object",
931
+ properties: {
932
+ action: {
933
+ type: "string",
934
+ description: "Filter by action type (e.g., 'wallet_create', 'frost_sign', 'agent_tool_call')"
935
+ },
936
+ wallet_id: {
937
+ type: "string",
938
+ description: "Filter by wallet ID"
939
+ },
940
+ limit: {
941
+ type: "number",
942
+ description: "Max entries to return (default: 20)"
943
+ }
944
+ },
945
+ required: []
946
+ }
947
+ },
948
+ {
949
+ name: "stng_audit_verify",
950
+ description: "Verify the integrity of the entire HMAC-chained audit log. Detects any tampering or corruption in the chain.",
951
+ inputSchema: {
952
+ type: "object",
953
+ properties: {},
954
+ required: []
955
+ }
956
+ },
957
+ // ── Portfolio & Analytics ──
958
+ {
959
+ name: "stng_portfolio",
960
+ description: "Get a portfolio summary across all wallets. Includes SOL and token balances, USD values (via Jupiter Price API), and allocation breakdown.",
961
+ inputSchema: {
962
+ type: "object",
963
+ properties: {},
964
+ required: []
965
+ }
966
+ },
967
+ {
968
+ name: "stng_tx_history",
969
+ description: "Get recent transaction history for a wallet. Shows tool calls, results, and success/failure status.",
970
+ inputSchema: {
971
+ type: "object",
972
+ properties: {
973
+ wallet_id: {
974
+ type: "string",
975
+ description: "Wallet ID (8-char hex)"
976
+ },
977
+ limit: {
978
+ type: "number",
979
+ description: "Max entries (default: 20)"
980
+ }
981
+ },
982
+ required: ["wallet_id"]
983
+ }
984
+ },
985
+ {
986
+ name: "stng_fraud_alerts",
987
+ description: "Get fraud detection alerts for a wallet. Monitors for rapid drain, velocity spikes, new program interaction, authority changes, and repeated failures.",
988
+ inputSchema: {
989
+ type: "object",
990
+ properties: {
991
+ wallet_id: {
992
+ type: "string",
993
+ description: "Wallet ID (8-char hex)"
994
+ }
995
+ },
996
+ required: ["wallet_id"]
997
+ }
998
+ }
999
+ ];
1000
+ async function handleTool(name, args, ctx) {
1001
+ const manager = getManager(ctx);
1002
+ switch (name) {
1003
+ // ── Wallet Operations ──
1004
+ case "stng_wallet_create": {
1005
+ const label = args.label;
1006
+ const wallet = await manager.createWalletWithAirdrop(label, 1, {
1007
+ isAgent: true
1008
+ });
1009
+ return {
1010
+ wallet_id: wallet.wallet.id,
1011
+ public_key: wallet.wallet.publicKey,
1012
+ label: wallet.wallet.label,
1013
+ created_at: wallet.wallet.createdAt,
1014
+ airdrop: wallet.airdrop ? { signature: wallet.airdrop.signature, amount: wallet.airdrop.amount } : null
1015
+ };
1016
+ }
1017
+ case "stng_wallet_list": {
1018
+ const wallets = await manager.listWallets();
1019
+ return {
1020
+ count: wallets.length,
1021
+ wallets: wallets.map((w) => ({
1022
+ id: w.id,
1023
+ public_key: w.publicKey,
1024
+ label: w.label ?? null,
1025
+ created_at: w.createdAt,
1026
+ is_agent: w.isAgent ?? false
1027
+ }))
1028
+ };
1029
+ }
1030
+ case "stng_wallet_balance": {
1031
+ const pk = args.public_key;
1032
+ const balance = await manager.getBalance(pk);
1033
+ return { public_key: pk, balance_sol: balance };
1034
+ }
1035
+ case "stng_wallet_tokens": {
1036
+ const pk = args.public_key;
1037
+ const tokens = await manager.getTokenBalances(pk);
1038
+ return { public_key: pk, tokens };
1039
+ }
1040
+ case "stng_wallet_airdrop": {
1041
+ const pk = args.public_key;
1042
+ const amount = args.amount ?? 1;
1043
+ const sig = await manager.requestAirdrop(pk, amount);
1044
+ return { public_key: pk, amount, signature: sig };
1045
+ }
1046
+ // ── FROST Threshold Signing ──
1047
+ case "stng_frost_create": {
1048
+ const label = args.label;
1049
+ const threshold = args.threshold ?? 2;
1050
+ const totalShares = args.total_shares ?? 3;
1051
+ const wallet = await manager.createFrostWallet({
1052
+ threshold,
1053
+ totalShares,
1054
+ label
1055
+ });
1056
+ return {
1057
+ wallet_id: wallet.id,
1058
+ group_public_key: wallet.groupPublicKey,
1059
+ threshold: wallet.threshold,
1060
+ total_shares: wallet.totalShares,
1061
+ label: wallet.label ?? null,
1062
+ created_at: wallet.createdAt
1063
+ };
1064
+ }
1065
+ case "stng_frost_list": {
1066
+ const wallets = await manager.listFrostWallets();
1067
+ return {
1068
+ count: wallets.length,
1069
+ wallets: wallets.map((w) => ({
1070
+ id: w.id,
1071
+ group_public_key: w.groupPublicKey,
1072
+ threshold: w.threshold,
1073
+ total_shares: w.totalShares,
1074
+ label: w.label ?? null,
1075
+ created_at: w.createdAt
1076
+ }))
1077
+ };
1078
+ }
1079
+ case "stng_frost_verify": {
1080
+ const walletId = args.wallet_id;
1081
+ const valid = await manager.verifyFrostShares(walletId);
1082
+ return {
1083
+ wallet_id: walletId,
1084
+ shares_valid: valid,
1085
+ message: valid ? "All key shares are intact and can reconstruct a valid group signature." : "Share verification failed \u2014 shares may be corrupted."
1086
+ };
1087
+ }
1088
+ // ── Agent Lifecycle ──
1089
+ case "stng_agent_status": {
1090
+ const walletId = args.wallet_id;
1091
+ const lifecycle = await getAgentLifecycle(walletId);
1092
+ const executable = await isAgentExecutable(walletId);
1093
+ return {
1094
+ wallet_id: walletId,
1095
+ state: lifecycle.state,
1096
+ updated_at: lifecycle.updatedAt,
1097
+ can_execute: executable.allowed,
1098
+ reason: lifecycle.reason ?? null,
1099
+ transitions: lifecycle.transitions
1100
+ };
1101
+ }
1102
+ case "stng_agent_pause": {
1103
+ const walletId = args.wallet_id;
1104
+ const reason = args.reason;
1105
+ const result = await setAgentLifecycle(walletId, "paused", reason);
1106
+ return {
1107
+ wallet_id: walletId,
1108
+ state: result.state,
1109
+ updated_at: result.updatedAt,
1110
+ reason: result.reason ?? null
1111
+ };
1112
+ }
1113
+ case "stng_agent_resume": {
1114
+ const walletId = args.wallet_id;
1115
+ const reason = args.reason;
1116
+ const result = await setAgentLifecycle(walletId, "active", reason);
1117
+ return {
1118
+ wallet_id: walletId,
1119
+ state: result.state,
1120
+ updated_at: result.updatedAt,
1121
+ reason: result.reason ?? null
1122
+ };
1123
+ }
1124
+ case "stng_agent_monitor": {
1125
+ const data = await buildAgentMonitor(manager);
1126
+ return {
1127
+ total_agents: data.totalAgents,
1128
+ active: data.activeAgents,
1129
+ paused: data.pausedAgents,
1130
+ terminated: data.terminatedAgents,
1131
+ error: data.errorAgents,
1132
+ agents: data.agents.map((a) => ({
1133
+ id: a.id,
1134
+ public_key: a.publicKey,
1135
+ label: a.label,
1136
+ balance_sol: a.balance,
1137
+ status: a.status,
1138
+ lifecycle_state: a.lifecycleState,
1139
+ strategy: a.strategy,
1140
+ total_actions: a.totalActions,
1141
+ success_rate: a.successRate,
1142
+ last_action: a.lastAction
1143
+ })),
1144
+ fetched_at: data.fetchedAt
1145
+ };
1146
+ }
1147
+ // ── Spending & Security ──
1148
+ case "stng_spending_limits": {
1149
+ const walletId = args.wallet_id;
1150
+ const action = args.action ?? "get";
1151
+ if (action === "set") {
1152
+ const config = {};
1153
+ if (args.per_tx_limit !== void 0)
1154
+ config.maxPerTx = args.per_tx_limit;
1155
+ if (args.daily_limit !== void 0)
1156
+ config.maxPerDay = args.daily_limit;
1157
+ await setSpendingLimits(walletId, config);
1158
+ }
1159
+ const limits = await getSpendingLimits(walletId);
1160
+ const summary = await getSpendingSummary(walletId);
1161
+ return {
1162
+ wallet_id: walletId,
1163
+ limits,
1164
+ summary
1165
+ };
1166
+ }
1167
+ case "stng_audit_log": {
1168
+ const opts = {
1169
+ limit: args.limit ?? 20
1170
+ };
1171
+ if (args.action) opts.action = args.action;
1172
+ if (args.wallet_id) opts.walletId = args.wallet_id;
1173
+ const entries = await readAuditLog(opts);
1174
+ return { count: entries.length, entries };
1175
+ }
1176
+ case "stng_audit_verify": {
1177
+ const result = await verifyAuditIntegrity();
1178
+ return {
1179
+ ...result,
1180
+ message: result.valid ? `Audit log integrity verified \u2014 ${result.total} entries, chain intact.` : `Audit log integrity BROKEN at entry ${result.brokenAt} of ${result.total}.`
1181
+ };
1182
+ }
1183
+ // ── Portfolio & Analytics ──
1184
+ case "stng_portfolio": {
1185
+ const portfolio = await buildPortfolio(manager);
1186
+ return portfolio;
1187
+ }
1188
+ case "stng_tx_history": {
1189
+ const walletId = args.wallet_id;
1190
+ const limit = args.limit ?? 20;
1191
+ const entries = await readTxLog(walletId, limit);
1192
+ return { wallet_id: walletId, count: entries.length, transactions: entries };
1193
+ }
1194
+ case "stng_fraud_alerts": {
1195
+ const walletId = args.wallet_id;
1196
+ const alerts = await getAlerts(walletId);
1197
+ const stats = await getFraudStats(walletId);
1198
+ return {
1199
+ wallet_id: walletId,
1200
+ alerts,
1201
+ stats
1202
+ };
1203
+ }
1204
+ default:
1205
+ throw new Error(`Unknown tool: ${name}`);
1206
+ }
1207
+ }
1208
+
1209
+ // src/mcp/prompts.ts
1210
+ var PROMPTS = [
1211
+ {
1212
+ name: "portfolio_overview",
1213
+ description: "Get a comprehensive portfolio overview across all wallets with USD values, allocations, and balances.",
1214
+ arguments: []
1215
+ },
1216
+ {
1217
+ name: "security_audit",
1218
+ description: "Run a full security audit: verify audit log integrity, check spending limits, review fraud alerts, and FROST wallet status.",
1219
+ arguments: [
1220
+ {
1221
+ name: "wallet_id",
1222
+ description: "Specific wallet to audit (optional \u2014 omit for fleet-wide)",
1223
+ required: false
1224
+ }
1225
+ ]
1226
+ },
1227
+ {
1228
+ name: "create_agent_wallet",
1229
+ description: "Guided workflow to create a new agent wallet with spending limits and optional FROST threshold protection.",
1230
+ arguments: [
1231
+ {
1232
+ name: "label",
1233
+ description: "Name for the new agent (e.g., 'trading-bot-alpha')",
1234
+ required: true
1235
+ },
1236
+ {
1237
+ name: "use_frost",
1238
+ description: "Whether to create a FROST threshold wallet (yes/no, default: no)",
1239
+ required: false
1240
+ }
1241
+ ]
1242
+ },
1243
+ {
1244
+ name: "fleet_health",
1245
+ description: "Check the health of all agents in the fleet: statuses, balances, success rates, and spending.",
1246
+ arguments: []
1247
+ },
1248
+ {
1249
+ name: "investigate_wallet",
1250
+ description: "Deep investigation of a specific wallet: balance, tokens, transaction history, spending, fraud alerts, and lifecycle state.",
1251
+ arguments: [
1252
+ {
1253
+ name: "wallet_id",
1254
+ description: "The wallet ID to investigate",
1255
+ required: true
1256
+ }
1257
+ ]
1258
+ }
1259
+ ];
1260
+ function getPromptContent(name, args) {
1261
+ switch (name) {
1262
+ case "portfolio_overview":
1263
+ return `Please give me a comprehensive portfolio overview.
1264
+
1265
+ Steps:
1266
+ 1. Use stng_portfolio to get all wallet balances, token holdings, and USD values
1267
+ 2. Use stng_wallet_list to get wallet labels and metadata
1268
+ 3. Present a clear summary including:
1269
+ - Total portfolio value in USD
1270
+ - Per-wallet breakdown (label, SOL balance, token balances, USD value)
1271
+ - Asset allocation percentages
1272
+ - Any wallets with zero balance that may need funding`;
1273
+ case "security_audit": {
1274
+ const walletClause = args.wallet_id ? `Focus on wallet ${args.wallet_id}.` : "Audit all wallets fleet-wide.";
1275
+ return `Please run a comprehensive security audit. ${walletClause}
1276
+
1277
+ Steps:
1278
+ 1. Use stng_audit_verify to check HMAC chain integrity of the audit log
1279
+ 2. Use stng_audit_log to review recent security-relevant events (wallet_create, frost_sign, spending_limit_exceeded)
1280
+ 3. Use stng_frost_list to check FROST wallet status and verify shares with stng_frost_verify
1281
+ 4. ${args.wallet_id ? `Use stng_spending_limits for wallet ${args.wallet_id}` : "Use stng_agent_monitor to check all agents"}
1282
+ 5. ${args.wallet_id ? `Use stng_fraud_alerts for wallet ${args.wallet_id}` : "Check fraud alerts for any flagged wallets"}
1283
+
1284
+ Report:
1285
+ - Audit log integrity status (intact/broken)
1286
+ - Total audit entries and recent activity
1287
+ - FROST wallet health
1288
+ - Spending limit compliance
1289
+ - Any fraud alerts or anomalies
1290
+ - Security recommendations`;
1291
+ }
1292
+ case "create_agent_wallet": {
1293
+ const label = args.label || "new-agent";
1294
+ const useFrost = args.use_frost?.toLowerCase() === "yes";
1295
+ return `Please create a new agent wallet named "${label}".
1296
+
1297
+ Steps:
1298
+ 1. ${useFrost ? "Use stng_frost_create to create a FROST 2-of-3 threshold wallet" : "Use stng_wallet_create to create a standard wallet"} with label "${label}"
1299
+ 2. Use stng_spending_limits with action=set to configure:
1300
+ - per_tx_limit: 0.5 SOL (conservative start)
1301
+ - daily_limit: 2 SOL
1302
+ 3. Use stng_agent_status to confirm the agent is in "active" state
1303
+ 4. Use stng_wallet_balance to confirm initial balance
1304
+
1305
+ Report the new agent's:
1306
+ - Wallet ID
1307
+ - Public key
1308
+ - ${useFrost ? "FROST group public key and threshold" : "Balance"}
1309
+ - Spending limits
1310
+ - Lifecycle state`;
1311
+ }
1312
+ case "fleet_health":
1313
+ return `Please check the health of all agents in the fleet.
1314
+
1315
+ Steps:
1316
+ 1. Use stng_agent_monitor to get the full fleet dashboard
1317
+ 2. For any agents in "error" or "paused" state, use stng_agent_status for details
1318
+ 3. Use stng_portfolio to check fleet-wide balances
1319
+
1320
+ Report:
1321
+ - Total agents (active / paused / terminated / error)
1322
+ - Per-agent health summary (balance, success rate, last action, strategy)
1323
+ - Any agents that need attention (low balance, high failure rate, paused)
1324
+ - Fleet-wide statistics (total value, average success rate)`;
1325
+ case "investigate_wallet": {
1326
+ const walletId = args.wallet_id || "[WALLET_ID]";
1327
+ return `Please do a deep investigation of wallet ${walletId}.
1328
+
1329
+ Steps:
1330
+ 1. Use stng_agent_status to check lifecycle state and transition history
1331
+ 2. Use stng_spending_limits to review spending configuration and usage
1332
+ 3. Use stng_tx_history to review recent transactions
1333
+ 4. Use stng_fraud_alerts to check for any security alerts
1334
+ 5. Use stng_audit_log filtered by wallet_id=${walletId} to review audit trail
1335
+
1336
+ Report:
1337
+ - Wallet state (active/paused/terminated) and why
1338
+ - Spending: limits vs actual usage, any violations
1339
+ - Recent transactions: success/failure patterns, tools used
1340
+ - Fraud alerts: any triggered rules, severity
1341
+ - Audit trail: key events timeline
1342
+ - Recommendations for this wallet`;
1343
+ }
1344
+ default:
1345
+ return `Unknown prompt: ${name}`;
1346
+ }
1347
+ }
1348
+
1349
+ // src/mcp/server.ts
1350
+ function createServer(ctx) {
1351
+ const server = new Server(
1352
+ {
1353
+ name: "stng-defi-wallets",
1354
+ version: "0.1.0"
1355
+ },
1356
+ {
1357
+ capabilities: {
1358
+ tools: {},
1359
+ prompts: {}
1360
+ }
1361
+ }
1362
+ );
1363
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
1364
+ return { tools: [...TOOLS] };
1365
+ });
1366
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
1367
+ return { prompts: PROMPTS };
1368
+ });
1369
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
1370
+ const { name, arguments: args } = request.params;
1371
+ const prompt = PROMPTS.find((p) => p.name === name);
1372
+ if (!prompt) {
1373
+ throw new Error(`Unknown prompt: ${name}`);
1374
+ }
1375
+ const content = getPromptContent(name, args || {});
1376
+ return {
1377
+ description: prompt.description,
1378
+ messages: [
1379
+ {
1380
+ role: "user",
1381
+ content: {
1382
+ type: "text",
1383
+ text: content
1384
+ }
1385
+ }
1386
+ ]
1387
+ };
1388
+ });
1389
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1390
+ const { name, arguments: args } = request.params;
1391
+ try {
1392
+ const result = await handleTool(
1393
+ name,
1394
+ args ?? {},
1395
+ ctx
1396
+ );
1397
+ return {
1398
+ content: [
1399
+ {
1400
+ type: "text",
1401
+ text: JSON.stringify(result, null, 2)
1402
+ }
1403
+ ]
1404
+ };
1405
+ } catch (error) {
1406
+ const message = error instanceof Error ? error.message : "Unknown error";
1407
+ return {
1408
+ content: [
1409
+ {
1410
+ type: "text",
1411
+ text: JSON.stringify({ error: message })
1412
+ }
1413
+ ],
1414
+ isError: true
1415
+ };
1416
+ }
1417
+ });
1418
+ return server;
1419
+ }
1420
+ async function runMcpServer() {
1421
+ const encryptionKey = process.env.WALLET_ENCRYPTION_KEY;
1422
+ if (!encryptionKey) {
1423
+ throw new Error("WALLET_ENCRYPTION_KEY environment variable is required");
1424
+ }
1425
+ const rpcUrl = process.env.SOLANA_RPC_URL ?? "https://api.devnet.solana.com";
1426
+ const ctx = { encryptionKey, rpcUrl };
1427
+ const server = createServer(ctx);
1428
+ const transport = new StdioServerTransport();
1429
+ await server.connect(transport);
1430
+ console.error(
1431
+ `stng-defi-wallets MCP server started (${TOOLS.length} tools, ${PROMPTS.length} prompts)`
1432
+ );
1433
+ }
1434
+ var isDirectRun = process.argv[1]?.endsWith("server.ts") || process.argv[1]?.endsWith("server.js");
1435
+ if (isDirectRun) {
1436
+ if (!process.env.WALLET_ENCRYPTION_KEY) {
1437
+ console.error(
1438
+ "Error: WALLET_ENCRYPTION_KEY environment variable is required"
1439
+ );
1440
+ console.error("");
1441
+ console.error("Usage:");
1442
+ console.error(
1443
+ " WALLET_ENCRYPTION_KEY=<hex-key> npx tsx src/mcp/server.ts"
1444
+ );
1445
+ console.error("");
1446
+ console.error("Environment variables:");
1447
+ console.error(
1448
+ " WALLET_ENCRYPTION_KEY (required) 32-byte hex key for wallet encryption"
1449
+ );
1450
+ console.error(
1451
+ " SOLANA_RPC_URL (optional) Solana RPC, default: https://api.devnet.solana.com"
1452
+ );
1453
+ console.error("");
1454
+ console.error("Generate a key:");
1455
+ console.error(
1456
+ ` node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`
1457
+ );
1458
+ process.exit(1);
1459
+ }
1460
+ runMcpServer().catch((error) => {
1461
+ console.error("Fatal error:", error);
1462
+ process.exit(1);
1463
+ });
1464
+ }
1465
+ export {
1466
+ runMcpServer
1467
+ };
1468
+ //# sourceMappingURL=mcp-server.js.map