@kevinlilili/agent-wallet 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +342 -48
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Security: ONLY reads public addresses. Never reads private keys.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
const { readFileSync, existsSync, readdirSync } = require("fs");
|
|
13
|
+
const { readFileSync, existsSync, readdirSync, statSync } = require("fs");
|
|
14
14
|
const { homedir } = require("os");
|
|
15
15
|
const { join } = require("path");
|
|
16
16
|
const { execSync } = require("child_process");
|
|
@@ -44,37 +44,74 @@ function scanAgentCash() {
|
|
|
44
44
|
return wallets;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function
|
|
47
|
+
function scanMCPWallets() {
|
|
48
48
|
const wallets = [];
|
|
49
|
+
const seen = new Set();
|
|
49
50
|
|
|
50
|
-
//
|
|
51
|
-
const spongeWallet = readJson(join(HOME, ".sponge", "wallet.json"));
|
|
52
|
-
if (spongeWallet && typeof spongeWallet.address === "string") {
|
|
53
|
-
wallets.push({ provider: "Sponge", address: spongeWallet.address, chain: "evm" });
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Check MCP configs for Sponge server env vars
|
|
51
|
+
// All known MCP config file locations and their display labels
|
|
57
52
|
const mcpConfigs = [
|
|
58
|
-
join(HOME, "Library", "Application Support", "Claude", "claude_desktop_config.json"),
|
|
59
|
-
join(HOME, ".cursor", "mcp.json"),
|
|
53
|
+
{ path: join(HOME, "Library", "Application Support", "Claude", "claude_desktop_config.json"), label: "Claude Desktop" },
|
|
54
|
+
{ path: join(HOME, ".cursor", "mcp.json"), label: "Cursor" },
|
|
55
|
+
{ path: join(HOME, ".claude.json"), label: "Claude Code" },
|
|
60
56
|
];
|
|
61
57
|
|
|
62
|
-
|
|
58
|
+
// Helpers to classify addresses
|
|
59
|
+
const isEvmAddress = (val) =>
|
|
60
|
+
typeof val === "string" && /^0x[a-fA-F0-9]{40}$/.test(val);
|
|
61
|
+
|
|
62
|
+
const isSolanaAddress = (val) =>
|
|
63
|
+
typeof val === "string" && !val.startsWith("0x") &&
|
|
64
|
+
/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(val);
|
|
65
|
+
|
|
66
|
+
const isPrivateKeyVar = (key) =>
|
|
67
|
+
key.toUpperCase().includes("PRIVATE_KEY");
|
|
68
|
+
|
|
69
|
+
const isWalletVar = (key) => {
|
|
70
|
+
const upper = key.toUpperCase();
|
|
71
|
+
return upper.endsWith("_ADDRESS") || upper.endsWith("_WALLET") || upper.endsWith("_WALLET_ADDRESS");
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function addWallet(provider, address, chain) {
|
|
75
|
+
if (seen.has(address)) return;
|
|
76
|
+
seen.add(address);
|
|
77
|
+
wallets.push({ provider, address, chain });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
for (const { path: configPath, label } of mcpConfigs) {
|
|
63
81
|
const data = readJson(configPath);
|
|
64
82
|
if (!data) continue;
|
|
65
83
|
const servers = data.mcpServers;
|
|
66
|
-
if (!servers) continue;
|
|
67
|
-
|
|
68
|
-
for (const [
|
|
69
|
-
if (!
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
84
|
+
if (!servers || typeof servers !== "object") continue;
|
|
85
|
+
|
|
86
|
+
for (const [serverName, config] of Object.entries(servers)) {
|
|
87
|
+
if (!config || typeof config !== "object") continue;
|
|
88
|
+
const providerLabel = `${serverName} (${label})`;
|
|
89
|
+
|
|
90
|
+
// Scan env vars for wallet addresses
|
|
91
|
+
if (config.env && typeof config.env === "object") {
|
|
92
|
+
for (const [key, val] of Object.entries(config.env)) {
|
|
93
|
+
// SECURITY: never read private keys
|
|
94
|
+
if (isPrivateKeyVar(key)) continue;
|
|
95
|
+
|
|
96
|
+
if (isWalletVar(key)) {
|
|
97
|
+
if (isEvmAddress(val)) {
|
|
98
|
+
addWallet(providerLabel, val, "evm");
|
|
99
|
+
} else if (isSolanaAddress(val)) {
|
|
100
|
+
addWallet(providerLabel, val, "solana");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Scan args array for wallet addresses passed as CLI arguments
|
|
107
|
+
if (Array.isArray(config.args)) {
|
|
108
|
+
for (const arg of config.args) {
|
|
109
|
+
if (typeof arg !== "string") continue;
|
|
110
|
+
if (isEvmAddress(arg)) {
|
|
111
|
+
addWallet(providerLabel, arg, "evm");
|
|
112
|
+
} else if (isSolanaAddress(arg)) {
|
|
113
|
+
addWallet(providerLabel, arg, "solana");
|
|
114
|
+
}
|
|
78
115
|
}
|
|
79
116
|
}
|
|
80
117
|
}
|
|
@@ -179,6 +216,193 @@ function scanEnvFiles() {
|
|
|
179
216
|
return wallets;
|
|
180
217
|
}
|
|
181
218
|
|
|
219
|
+
// ── Security Scanner ────────────────────────────────────────────
|
|
220
|
+
|
|
221
|
+
const SEVERITY = { CRITICAL: "critical", WARNING: "warning", INFO: "info" };
|
|
222
|
+
const RED = "\x1b[31m";
|
|
223
|
+
|
|
224
|
+
function securityScan(wallets) {
|
|
225
|
+
const findings = [];
|
|
226
|
+
|
|
227
|
+
// ── 1. Plaintext private keys in wallet files ──
|
|
228
|
+
const walletFiles = [
|
|
229
|
+
{ path: join(HOME, ".agentcash", "wallet.json"), provider: "AgentCash", keyFields: ["privateKey", "private_key", "secretKey"] },
|
|
230
|
+
{ path: join(HOME, ".agentcash", "solana-wallet.json"), provider: "AgentCash (SOL)", keyFields: ["privateKey", "private_key", "secretKey"] },
|
|
231
|
+
{ path: join(HOME, ".cdp", "wallet_data.json"), provider: "Coinbase CDP", keyFields: ["privateKey", "private_key", "seed"] },
|
|
232
|
+
{ path: join(HOME, ".latinum", "config.json"), provider: "Latinum", keyFields: ["privateKey", "private_key"] },
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
// Also check wallet_data_*.txt files
|
|
236
|
+
const searchDirs = [HOME, join(HOME, "Projects"), join(HOME, "projects"), join(HOME, "code"), join(HOME, "dev")];
|
|
237
|
+
for (const dir of searchDirs) {
|
|
238
|
+
if (!existsSync(dir)) continue;
|
|
239
|
+
try {
|
|
240
|
+
for (const file of readdirSync(dir)) {
|
|
241
|
+
if (file.startsWith("wallet_data_") && file.endsWith(".txt")) {
|
|
242
|
+
walletFiles.push({ path: join(dir, file), provider: `AgentKit (${file})`, keyFields: ["privateKey", "private_key", "seed", "export_data"] });
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} catch { /* skip */ }
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
for (const { path: fp, provider, keyFields } of walletFiles) {
|
|
249
|
+
if (!existsSync(fp)) continue;
|
|
250
|
+
const data = readJson(fp);
|
|
251
|
+
if (!data || typeof data !== "object") continue;
|
|
252
|
+
|
|
253
|
+
const exposedKeys = keyFields.filter((k) => data[k] && typeof data[k] === "string" && data[k].length > 10);
|
|
254
|
+
if (exposedKeys.length > 0) {
|
|
255
|
+
findings.push({
|
|
256
|
+
severity: SEVERITY.CRITICAL,
|
|
257
|
+
title: `Plaintext private key in ${provider} wallet file`,
|
|
258
|
+
detail: fp.replace(HOME, "~"),
|
|
259
|
+
fix: "Encrypt this file or move private key to a hardware wallet / secure vault",
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Check file permissions (should be 600, not world/group readable)
|
|
264
|
+
try {
|
|
265
|
+
const mode = statSync(fp).mode;
|
|
266
|
+
const groupRead = mode & 0o040;
|
|
267
|
+
const otherRead = mode & 0o004;
|
|
268
|
+
if (groupRead || otherRead) {
|
|
269
|
+
findings.push({
|
|
270
|
+
severity: SEVERITY.WARNING,
|
|
271
|
+
title: `${provider} wallet file is readable by other users`,
|
|
272
|
+
detail: `${fp.replace(HOME, "~")} — permissions too open`,
|
|
273
|
+
fix: `Run: chmod 600 "${fp}"`,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
} catch { /* skip */ }
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ── 2. Private keys in .env files ──
|
|
280
|
+
const envDirs = [process.cwd(), HOME, join(HOME, "Projects"), join(HOME, "projects")];
|
|
281
|
+
for (const dir of envDirs) {
|
|
282
|
+
const envPath = join(dir, ".env");
|
|
283
|
+
if (!existsSync(envPath)) continue;
|
|
284
|
+
try {
|
|
285
|
+
const lines = readFileSync(envPath, "utf-8").split("\n");
|
|
286
|
+
for (const line of lines) {
|
|
287
|
+
if (line.trim().startsWith("#")) continue;
|
|
288
|
+
const match = line.match(/^([A-Za-z_]*(?:PRIVATE_KEY|SECRET_KEY|MNEMONIC|SEED_PHRASE)[A-Za-z_]*)=/i);
|
|
289
|
+
if (match) {
|
|
290
|
+
findings.push({
|
|
291
|
+
severity: SEVERITY.CRITICAL,
|
|
292
|
+
title: `Private key variable in .env file`,
|
|
293
|
+
detail: `${envPath.replace(HOME, "~")} → ${match[1]}`,
|
|
294
|
+
fix: "Move to encrypted secrets manager or hardware wallet",
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
} catch { /* skip */ }
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ── 3. Private keys leaked into MCP configs ──
|
|
302
|
+
const mcpConfigs = [
|
|
303
|
+
{ path: join(HOME, "Library", "Application Support", "Claude", "claude_desktop_config.json"), label: "Claude Desktop" },
|
|
304
|
+
{ path: join(HOME, ".cursor", "mcp.json"), label: "Cursor" },
|
|
305
|
+
{ path: join(HOME, ".claude.json"), label: "Claude Code" },
|
|
306
|
+
];
|
|
307
|
+
for (const { path: configPath, label } of mcpConfigs) {
|
|
308
|
+
const data = readJson(configPath);
|
|
309
|
+
if (!data || !data.mcpServers) continue;
|
|
310
|
+
for (const [serverName, config] of Object.entries(data.mcpServers)) {
|
|
311
|
+
if (!config || typeof config !== "object") continue;
|
|
312
|
+
const env = config.env || {};
|
|
313
|
+
for (const key of Object.keys(env)) {
|
|
314
|
+
const upper = key.toUpperCase();
|
|
315
|
+
if (upper.includes("PRIVATE_KEY") || upper.includes("SECRET_KEY") || upper.includes("MNEMONIC") || upper.includes("SEED_PHRASE")) {
|
|
316
|
+
findings.push({
|
|
317
|
+
severity: SEVERITY.CRITICAL,
|
|
318
|
+
title: `Private key in MCP config (${label})`,
|
|
319
|
+
detail: `Server "${serverName}" → env.${key}`,
|
|
320
|
+
fix: "Remove from config, use encrypted storage instead",
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ── 4. Wallet files in dangerous locations ──
|
|
328
|
+
const dangerousDirs = ["Downloads", "Desktop", "Documents", "tmp"];
|
|
329
|
+
for (const dirName of dangerousDirs) {
|
|
330
|
+
const dir = join(HOME, dirName);
|
|
331
|
+
if (!existsSync(dir)) continue;
|
|
332
|
+
try {
|
|
333
|
+
for (const file of readdirSync(dir)) {
|
|
334
|
+
const lower = file.toLowerCase();
|
|
335
|
+
if (lower.includes("private_key") || lower.includes("keystore") || lower.includes("seed_phrase") ||
|
|
336
|
+
(lower.includes("wallet") && (lower.endsWith(".json") || lower.endsWith(".txt") || lower.endsWith(".csv")) && lower.includes("key"))) {
|
|
337
|
+
findings.push({
|
|
338
|
+
severity: SEVERITY.WARNING,
|
|
339
|
+
title: `Suspicious wallet/key file in ~/${dirName}`,
|
|
340
|
+
detail: `~/${dirName}/${file}`,
|
|
341
|
+
fix: "Move to a secure location or delete if no longer needed",
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
} catch { /* skip */ }
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ── 5. Git exposure — wallet dirs not gitignored ──
|
|
349
|
+
const sensitiveDirs = [
|
|
350
|
+
{ path: join(HOME, ".agentcash"), name: ".agentcash" },
|
|
351
|
+
{ path: join(HOME, ".cdp"), name: ".cdp" },
|
|
352
|
+
{ path: join(HOME, ".wallet-agent"), name: ".wallet-agent" },
|
|
353
|
+
{ path: join(HOME, ".openclaw"), name: ".openclaw" },
|
|
354
|
+
{ path: join(HOME, ".latinum"), name: ".latinum" },
|
|
355
|
+
];
|
|
356
|
+
for (const { path: dp, name } of sensitiveDirs) {
|
|
357
|
+
if (!existsSync(dp)) continue;
|
|
358
|
+
try {
|
|
359
|
+
const result = execSync(`git -C "${HOME}" check-ignore -q "${dp}" 2>/dev/null`, { stdio: "pipe" });
|
|
360
|
+
} catch {
|
|
361
|
+
// Non-zero exit = not ignored
|
|
362
|
+
findings.push({
|
|
363
|
+
severity: SEVERITY.WARNING,
|
|
364
|
+
title: `~/${name}/ is not git-ignored`,
|
|
365
|
+
detail: "Could accidentally be committed to a repository",
|
|
366
|
+
fix: `Add "${name}/" to your global .gitignore`,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ── 6. wallet-agent: check if keys are actually encrypted ──
|
|
372
|
+
const waPath = join(HOME, ".wallet-agent", "auth", "encrypted-keys.json");
|
|
373
|
+
if (existsSync(waPath)) {
|
|
374
|
+
const data = readJson(waPath);
|
|
375
|
+
if (data && data.keys) {
|
|
376
|
+
const allEncrypted = Object.values(data.keys).every((v) => typeof v === "string" && v.length > 100);
|
|
377
|
+
if (allEncrypted) {
|
|
378
|
+
findings.push({
|
|
379
|
+
severity: SEVERITY.INFO,
|
|
380
|
+
title: "wallet-agent keys appear encrypted",
|
|
381
|
+
detail: waPath.replace(HOME, "~"),
|
|
382
|
+
fix: null,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ── Calculate security score ──
|
|
389
|
+
const criticalCount = findings.filter((f) => f.severity === SEVERITY.CRITICAL).length;
|
|
390
|
+
const warningCount = findings.filter((f) => f.severity === SEVERITY.WARNING).length;
|
|
391
|
+
let score = 100;
|
|
392
|
+
score -= criticalCount * 25;
|
|
393
|
+
score -= warningCount * 10;
|
|
394
|
+
score = Math.max(0, Math.min(100, score));
|
|
395
|
+
|
|
396
|
+
let grade;
|
|
397
|
+
if (score >= 90) grade = "A";
|
|
398
|
+
else if (score >= 75) grade = "B";
|
|
399
|
+
else if (score >= 50) grade = "C";
|
|
400
|
+
else if (score >= 25) grade = "D";
|
|
401
|
+
else grade = "F";
|
|
402
|
+
|
|
403
|
+
return { findings, score, grade, criticalCount, warningCount };
|
|
404
|
+
}
|
|
405
|
+
|
|
182
406
|
// ── Output helpers ──────────────────────────────────────────────
|
|
183
407
|
|
|
184
408
|
const RESET = "\x1b[0m";
|
|
@@ -194,6 +418,56 @@ function shortAddr(addr) {
|
|
|
194
418
|
|
|
195
419
|
// ── Main ────────────────────────────────────────────────────────
|
|
196
420
|
|
|
421
|
+
function printSecurityReport(security) {
|
|
422
|
+
const { findings, score, grade, criticalCount, warningCount } = security;
|
|
423
|
+
|
|
424
|
+
console.log(` ${BOLD}SECURITY SCAN${RESET}`);
|
|
425
|
+
console.log("");
|
|
426
|
+
|
|
427
|
+
if (findings.length === 0) {
|
|
428
|
+
console.log(` ${GREEN}✓ No security issues found${RESET}`);
|
|
429
|
+
} else {
|
|
430
|
+
for (const f of findings) {
|
|
431
|
+
let icon, color;
|
|
432
|
+
if (f.severity === SEVERITY.CRITICAL) { icon = "✗"; color = RED; }
|
|
433
|
+
else if (f.severity === SEVERITY.WARNING) { icon = "!"; color = YELLOW; }
|
|
434
|
+
else { icon = "✓"; color = GREEN; }
|
|
435
|
+
|
|
436
|
+
console.log(` ${color}${icon}${RESET} ${f.title}`);
|
|
437
|
+
console.log(` ${DIM}${f.detail}${RESET}`);
|
|
438
|
+
if (f.fix) {
|
|
439
|
+
console.log(` ${DIM}Fix: ${f.fix}${RESET}`);
|
|
440
|
+
}
|
|
441
|
+
console.log("");
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Score bar
|
|
446
|
+
const barLen = 20;
|
|
447
|
+
const filled = Math.round((score / 100) * barLen);
|
|
448
|
+
const empty = barLen - filled;
|
|
449
|
+
let barColor = score >= 75 ? GREEN : score >= 50 ? YELLOW : RED;
|
|
450
|
+
const bar = barColor + "█".repeat(filled) + DIM + "░".repeat(empty) + RESET;
|
|
451
|
+
|
|
452
|
+
console.log(` Security Score: ${BOLD}${grade}${RESET} (${score}/100) ${bar}`);
|
|
453
|
+
if (criticalCount > 0) console.log(` ${RED}${criticalCount} critical${RESET} · ${warningCount} warnings`);
|
|
454
|
+
else if (warningCount > 0) console.log(` ${YELLOW}${warningCount} warning(s)${RESET}`);
|
|
455
|
+
console.log("");
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function generateShareableText(unique, security) {
|
|
459
|
+
const chainSet = new Set(unique.map((w) => w.chain === "solana" ? "SOL" : "EVM"));
|
|
460
|
+
const lines = [
|
|
461
|
+
`Agent Wallet Security: ${security.grade} (${security.score}/100)`,
|
|
462
|
+
`${unique.length} wallets · ${chainSet.size} chain types`,
|
|
463
|
+
];
|
|
464
|
+
if (security.criticalCount > 0) lines.push(`⚠ ${security.criticalCount} critical issues found`);
|
|
465
|
+
else lines.push(`✓ No critical issues`);
|
|
466
|
+
lines.push("");
|
|
467
|
+
lines.push("Scan yours: npx @kevinlilili/agent-wallet");
|
|
468
|
+
return lines.join("\n");
|
|
469
|
+
}
|
|
470
|
+
|
|
197
471
|
function main() {
|
|
198
472
|
const args = process.argv.slice(2);
|
|
199
473
|
const dashboardUrl = args.find((a) => a.startsWith("--url="))?.split("=")[1]
|
|
@@ -201,16 +475,19 @@ function main() {
|
|
|
201
475
|
|| "https://agent-wallet-dashboard.vercel.app";
|
|
202
476
|
const noBrowser = args.includes("--no-open");
|
|
203
477
|
const jsonOutput = args.includes("--json");
|
|
478
|
+
const securityOnly = args.includes("--security");
|
|
479
|
+
const noSecurity = args.includes("--no-security");
|
|
204
480
|
|
|
205
481
|
console.log("");
|
|
206
482
|
console.log(` ${BOLD}Agent Wallet Scanner${RESET}`);
|
|
207
|
-
console.log(` ${DIM}Scanning local configs for agent
|
|
483
|
+
console.log(` ${DIM}Scanning local configs for agent wallets & security issues...${RESET}`);
|
|
208
484
|
console.log("");
|
|
209
485
|
|
|
486
|
+
// ── Wallet Discovery ──
|
|
210
487
|
const allWallets = [];
|
|
211
488
|
const scanners = [
|
|
212
489
|
{ name: "AgentCash", fn: scanAgentCash },
|
|
213
|
-
{ name: "
|
|
490
|
+
{ name: "MCP Wallets", fn: scanMCPWallets },
|
|
214
491
|
{ name: "Coinbase AgentKit", fn: scanCoinbaseAgentKit },
|
|
215
492
|
{ name: "wallet-agent", fn: scanWalletAgent },
|
|
216
493
|
{ name: "OpenClaw", fn: scanOpenClaw },
|
|
@@ -218,39 +495,57 @@ function main() {
|
|
|
218
495
|
{ name: ".env file", fn: scanEnvFiles },
|
|
219
496
|
];
|
|
220
497
|
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
498
|
+
if (!securityOnly) {
|
|
499
|
+
for (const scanner of scanners) {
|
|
500
|
+
const found = scanner.fn();
|
|
501
|
+
if (found.length > 0) {
|
|
502
|
+
console.log(` ${GREEN}✓${RESET} ${scanner.name}: ${found.length} wallet(s)`);
|
|
503
|
+
allWallets.push(...found);
|
|
504
|
+
}
|
|
226
505
|
}
|
|
227
506
|
}
|
|
228
507
|
|
|
229
|
-
// Deduplicate by address
|
|
230
508
|
const unique = Array.from(new Map(allWallets.map((w) => [w.address, w])).values());
|
|
231
509
|
|
|
232
|
-
if (unique.length
|
|
233
|
-
console.log(` ${DIM}No agent wallets found.${RESET}`);
|
|
510
|
+
if (!securityOnly && unique.length > 0) {
|
|
234
511
|
console.log("");
|
|
235
|
-
console.log(` ${
|
|
236
|
-
console.log(
|
|
512
|
+
console.log(` ${BOLD}${unique.length} wallet(s) found${RESET}`);
|
|
513
|
+
console.log("");
|
|
514
|
+
|
|
515
|
+
for (const w of unique) {
|
|
516
|
+
const badge = w.chain === "solana" ? `${YELLOW}SOL${RESET}` : `${CYAN}EVM${RESET}`;
|
|
517
|
+
console.log(` ${w.provider.padEnd(20)} ${DIM}${shortAddr(w.address)}${RESET} ${badge}`);
|
|
518
|
+
}
|
|
519
|
+
console.log("");
|
|
520
|
+
} else if (!securityOnly && unique.length === 0) {
|
|
521
|
+
console.log(` ${DIM}No agent wallets found.${RESET}`);
|
|
237
522
|
console.log("");
|
|
238
|
-
process.exit(0);
|
|
239
523
|
}
|
|
240
524
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
525
|
+
// ── Security Scan ──
|
|
526
|
+
if (!noSecurity) {
|
|
527
|
+
const security = securityScan(unique);
|
|
528
|
+
printSecurityReport(security);
|
|
529
|
+
|
|
530
|
+
// Shareable text
|
|
531
|
+
if (unique.length > 0) {
|
|
532
|
+
const shareText = generateShareableText(unique, security);
|
|
533
|
+
console.log(` ${DIM}── Share ──${RESET}`);
|
|
534
|
+
console.log("");
|
|
535
|
+
shareText.split("\n").forEach((l) => console.log(` ${l}`));
|
|
536
|
+
console.log("");
|
|
537
|
+
}
|
|
244
538
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
539
|
+
if (jsonOutput) {
|
|
540
|
+
console.log(JSON.stringify({ wallets: unique, security: { score: security.score, grade: security.grade, findings: security.findings } }, null, 2));
|
|
541
|
+
process.exit(0);
|
|
542
|
+
}
|
|
543
|
+
} else if (jsonOutput) {
|
|
544
|
+
console.log(JSON.stringify(unique, null, 2));
|
|
545
|
+
process.exit(0);
|
|
248
546
|
}
|
|
249
547
|
|
|
250
|
-
|
|
251
|
-
if (jsonOutput) {
|
|
252
|
-
console.log("");
|
|
253
|
-
console.log(JSON.stringify(unique, null, 2));
|
|
548
|
+
if (securityOnly || unique.length === 0) {
|
|
254
549
|
process.exit(0);
|
|
255
550
|
}
|
|
256
551
|
|
|
@@ -260,7 +555,6 @@ function main() {
|
|
|
260
555
|
.join("&");
|
|
261
556
|
const url = `${dashboardUrl}?${params}`;
|
|
262
557
|
|
|
263
|
-
console.log("");
|
|
264
558
|
console.log(` ${DIM}Dashboard:${RESET} ${url}`);
|
|
265
559
|
console.log("");
|
|
266
560
|
|