@darksol/terminal 0.11.0 → 0.12.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/README.md +96 -1
- package/package.json +1 -1
- package/src/agent/autonomous.js +465 -0
- package/src/agent/strategy-evaluator.js +166 -0
- package/src/cli.js +218 -0
- package/src/config/keys.js +16 -0
- package/src/config/store.js +6 -0
- package/src/services/gas.js +35 -42
- package/src/services/watch.js +67 -61
- package/src/services/whale-monitor.js +388 -0
- package/src/services/whale.js +421 -0
- package/src/ui/dashboard.js +596 -0
- package/src/wallet/history.js +47 -46
- package/src/wallet/portfolio.js +75 -87
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { topMovers } from '../services/market.js';
|
|
2
|
+
import { checkPrices } from '../services/watch.js';
|
|
3
|
+
import { getConfig } from '../config/store.js';
|
|
4
|
+
import { estimateGasCost, getProvider, quickPrice } from '../utils/helpers.js';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_COOLDOWN_MS = 15 * 60 * 1000;
|
|
7
|
+
|
|
8
|
+
const evaluatorDeps = {
|
|
9
|
+
quickPrice,
|
|
10
|
+
topMovers,
|
|
11
|
+
checkPrices,
|
|
12
|
+
getProvider,
|
|
13
|
+
estimateGasCost,
|
|
14
|
+
now: () => Date.now(),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function riskProfile(level = 'moderate') {
|
|
18
|
+
const profiles = {
|
|
19
|
+
conservative: { gasGwei: 1.5, cooldownMs: 30 * 60 * 1000, minConfidence: 0.75 },
|
|
20
|
+
moderate: { gasGwei: 3, cooldownMs: 15 * 60 * 1000, minConfidence: 0.6 },
|
|
21
|
+
aggressive: { gasGwei: 10, cooldownMs: 5 * 60 * 1000, minConfidence: 0.45 },
|
|
22
|
+
};
|
|
23
|
+
return profiles[level] || profiles.moderate;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalizeToken(symbol) {
|
|
27
|
+
return typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function collectMarketData(strategy, deps = evaluatorDeps) {
|
|
31
|
+
const primaryToken = normalizeToken(
|
|
32
|
+
strategy?.plan?.primaryToken
|
|
33
|
+
|| strategy?.plan?.entry?.token
|
|
34
|
+
|| strategy?.intent?.tokenOut
|
|
35
|
+
|| strategy?.intent?.token
|
|
36
|
+
);
|
|
37
|
+
const quoteToken = normalizeToken(strategy?.plan?.quoteToken || strategy?.intent?.tokenIn || 'USDC');
|
|
38
|
+
const currentPrice = primaryToken ? await deps.quickPrice(primaryToken) : null;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
if (primaryToken) {
|
|
42
|
+
await deps.checkPrices([primaryToken]);
|
|
43
|
+
}
|
|
44
|
+
} catch {}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
primaryToken,
|
|
48
|
+
quoteToken,
|
|
49
|
+
currentPrice,
|
|
50
|
+
price: currentPrice?.price ? Number(currentPrice.price) : null,
|
|
51
|
+
liquidity: currentPrice?.liquidity ? Number(currentPrice.liquidity) : 0,
|
|
52
|
+
change24h: currentPrice?.change24h !== undefined ? Number(currentPrice.change24h) : null,
|
|
53
|
+
volume24h: currentPrice?.volume24h ? Number(currentPrice.volume24h) : 0,
|
|
54
|
+
timestamp: new Date(deps.now()).toISOString(),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function evaluateConditions(strategy) {
|
|
59
|
+
const deps = evaluatorDeps;
|
|
60
|
+
const profile = riskProfile(strategy?.riskLevel);
|
|
61
|
+
const marketData = await collectMarketData(strategy, deps);
|
|
62
|
+
const provider = deps.getProvider(strategy?.chains?.[0] || getConfig('chain') || 'base');
|
|
63
|
+
const gas = await deps.estimateGasCost(provider, 180000n);
|
|
64
|
+
const gasGwei = Number(gas.gwei || 0);
|
|
65
|
+
const gasTooHigh = gasGwei > profile.gasGwei;
|
|
66
|
+
|
|
67
|
+
const lastTradeAt = strategy?.lastTradeAt ? new Date(strategy.lastTradeAt).getTime() : 0;
|
|
68
|
+
const cooldownMs = strategy?.plan?.cooldownMs || profile.cooldownMs || DEFAULT_COOLDOWN_MS;
|
|
69
|
+
const inCooldown = Boolean(lastTradeAt) && (deps.now() - lastTradeAt) < cooldownMs;
|
|
70
|
+
const minLiquidity = Number(strategy?.plan?.filters?.minLiquidity || 0);
|
|
71
|
+
const priceBelow = strategy?.plan?.entry?.priceBelow;
|
|
72
|
+
const priceAbove = strategy?.plan?.entry?.priceAbove;
|
|
73
|
+
const hasPrice = typeof marketData.price === 'number' && !Number.isNaN(marketData.price);
|
|
74
|
+
|
|
75
|
+
const meetsLiquidity = marketData.liquidity >= minLiquidity;
|
|
76
|
+
const meetsPriceBelow = !priceBelow || (hasPrice && marketData.price <= priceBelow);
|
|
77
|
+
const meetsPriceAbove = !priceAbove || (hasPrice && marketData.price >= priceAbove);
|
|
78
|
+
const hasBudget = Number(strategy?.budget || 0) > Number(strategy?.spent || 0);
|
|
79
|
+
const maxPerTrade = Number(strategy?.maxPerTrade || strategy?.budget || 0);
|
|
80
|
+
const remainingBudget = Math.max(0, Number(strategy?.budget || 0) - Number(strategy?.spent || 0));
|
|
81
|
+
const tradeAmount = Math.min(maxPerTrade, remainingBudget);
|
|
82
|
+
const withinTradeLimit = tradeAmount > 0 && tradeAmount <= maxPerTrade;
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
...marketData,
|
|
86
|
+
gasGwei,
|
|
87
|
+
gasTooHigh,
|
|
88
|
+
inCooldown,
|
|
89
|
+
cooldownMs,
|
|
90
|
+
remainingBudget,
|
|
91
|
+
tradeAmount,
|
|
92
|
+
withinTradeLimit,
|
|
93
|
+
meetsLiquidity,
|
|
94
|
+
meetsEntry: hasBudget && meetsLiquidity && meetsPriceBelow && meetsPriceAbove,
|
|
95
|
+
meetsExit: Boolean(
|
|
96
|
+
hasPrice
|
|
97
|
+
&& (
|
|
98
|
+
(strategy?.plan?.exit?.takeProfitPrice && marketData.price >= strategy.plan.exit.takeProfitPrice)
|
|
99
|
+
|| (strategy?.plan?.exit?.stopLossPrice && marketData.price <= strategy.plan.exit.stopLossPrice)
|
|
100
|
+
)
|
|
101
|
+
),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function shouldTrade(strategy, marketData) {
|
|
106
|
+
const profile = riskProfile(strategy?.riskLevel);
|
|
107
|
+
const conditions = marketData || await evaluateConditions(strategy);
|
|
108
|
+
const amount = Number(conditions.tradeAmount || 0);
|
|
109
|
+
|
|
110
|
+
if (!conditions.currentPrice) {
|
|
111
|
+
return { action: 'hold', reason: 'No market data for target token', confidence: 0.1 };
|
|
112
|
+
}
|
|
113
|
+
if (conditions.gasTooHigh) {
|
|
114
|
+
return { action: 'hold', reason: `Gas too high (${conditions.gasGwei.toFixed(2)} gwei)`, confidence: 0.15 };
|
|
115
|
+
}
|
|
116
|
+
if (!conditions.withinTradeLimit) {
|
|
117
|
+
return { action: 'hold', reason: 'Per-trade or budget limit reached', confidence: 0.1 };
|
|
118
|
+
}
|
|
119
|
+
if (conditions.inCooldown) {
|
|
120
|
+
return { action: 'hold', reason: 'Token cooldown active', confidence: 0.2 };
|
|
121
|
+
}
|
|
122
|
+
if (conditions.meetsExit && Number(strategy?.positionSize || 0) > 0) {
|
|
123
|
+
return { action: 'sell', reason: 'Exit conditions met', confidence: 0.8 };
|
|
124
|
+
}
|
|
125
|
+
if (!conditions.meetsEntry) {
|
|
126
|
+
const reasons = [];
|
|
127
|
+
if (!conditions.meetsLiquidity) reasons.push('liquidity below filter');
|
|
128
|
+
if (strategy?.plan?.entry?.priceBelow && conditions.price > strategy.plan.entry.priceBelow) reasons.push('price above entry threshold');
|
|
129
|
+
if (strategy?.plan?.entry?.priceAbove && conditions.price < strategy.plan.entry.priceAbove) reasons.push('price below momentum threshold');
|
|
130
|
+
if (amount <= 0) reasons.push('budget exhausted');
|
|
131
|
+
return { action: 'hold', reason: reasons.join(', ') || 'Entry conditions not met', confidence: 0.25 };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let confidence = 0.55;
|
|
135
|
+
if (strategy?.plan?.entry?.priceBelow && conditions.price <= strategy.plan.entry.priceBelow) confidence += 0.15;
|
|
136
|
+
if (conditions.liquidity >= Number(strategy?.plan?.filters?.minLiquidity || 0) * 2) confidence += 0.1;
|
|
137
|
+
if (typeof conditions.change24h === 'number') {
|
|
138
|
+
if (strategy?.plan?.mode === 'dca') confidence += 0.05;
|
|
139
|
+
else if (conditions.change24h > 0) confidence += 0.1;
|
|
140
|
+
else confidence -= 0.05;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
confidence = Math.max(0, Math.min(0.99, confidence));
|
|
144
|
+
if (confidence < profile.minConfidence) {
|
|
145
|
+
return { action: 'hold', reason: 'Signal below risk threshold', confidence };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
action: 'buy',
|
|
150
|
+
reason: `Entry conditions met for ${conditions.primaryToken} using ${amount.toFixed(2)} ${conditions.quoteToken}`,
|
|
151
|
+
confidence,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function __setEvaluatorDeps(overrides = {}) {
|
|
156
|
+
Object.assign(evaluatorDeps, overrides);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function __resetEvaluatorDeps() {
|
|
160
|
+
evaluatorDeps.quickPrice = quickPrice;
|
|
161
|
+
evaluatorDeps.topMovers = topMovers;
|
|
162
|
+
evaluatorDeps.checkPrices = checkPrices;
|
|
163
|
+
evaluatorDeps.getProvider = getProvider;
|
|
164
|
+
evaluatorDeps.estimateGasCost = estimateGasCost;
|
|
165
|
+
evaluatorDeps.now = () => Date.now();
|
|
166
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -2,12 +2,15 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { showBanner, showMiniBanner, showSection } from './ui/banner.js';
|
|
3
3
|
import { theme } from './ui/theme.js';
|
|
4
4
|
import { kvDisplay, success, error, warn, info } from './ui/components.js';
|
|
5
|
+
import { createDashboard } from './ui/dashboard.js';
|
|
5
6
|
import { getConfig, setConfig, getAllConfig, getRPC, setRPC, configPath } from './config/store.js';
|
|
6
7
|
import { createWallet, importWallet, showWallets, getBalance, useWallet, exportWallet, sendFunds, receiveAddress } from './wallet/manager.js';
|
|
7
8
|
import { showPortfolio } from './wallet/portfolio.js';
|
|
8
9
|
import { showHistory } from './wallet/history.js';
|
|
9
10
|
import { showGas } from './services/gas.js';
|
|
10
11
|
import { watchPrice, checkPrices } from './services/watch.js';
|
|
12
|
+
import { getWhaleActivity, listTracked, mirrorTrade, stopTracking, trackWallet } from './services/whale.js';
|
|
13
|
+
import { startWhaleFeed } from './services/whale-monitor.js';
|
|
11
14
|
import { mailSetup, mailCreate, mailInboxes, mailSend, mailList, mailRead, mailReply, mailForward, mailThreads, mailDelete, mailUse, mailStats, mailStatus } from './services/mail.js';
|
|
12
15
|
import { startWebShell } from './web/server.js';
|
|
13
16
|
import { executeSwap } from './trading/swap.js';
|
|
@@ -42,6 +45,7 @@ import { runSetupWizard } from './setup/wizard.js';
|
|
|
42
45
|
import { displaySoul, hasSoul, resetSoul, runSoulSetup } from './soul/index.js';
|
|
43
46
|
import { clearMemories, exportMemories, getRecentMemories, searchMemories } from './memory/index.js';
|
|
44
47
|
import { getAgentStatus, planAgentGoal, runAgentTask } from './agent/index.js';
|
|
48
|
+
import { getAuditLog, getStatus as getAutoStatus, listStrategies as listAutoStrategies, startAutonomous, stopAutonomous } from './agent/autonomous.js';
|
|
45
49
|
import { daemonStart, daemonStop, daemonStatus, daemonRestart } from './daemon/index.js';
|
|
46
50
|
import { telegramSetup, telegramStartForeground, telegramStopCommand, telegramStatusCommand, telegramSendCommand } from './services/telegram.js';
|
|
47
51
|
import { createRequire } from 'module';
|
|
@@ -329,6 +333,159 @@ export function cli(argv) {
|
|
|
329
333
|
.description('Execute pending DCA orders')
|
|
330
334
|
.action(() => runDCA());
|
|
331
335
|
|
|
336
|
+
const auto = program
|
|
337
|
+
.command('auto')
|
|
338
|
+
.description('Autonomous trader mode - goal-based automated execution');
|
|
339
|
+
|
|
340
|
+
auto
|
|
341
|
+
.command('start <goal>')
|
|
342
|
+
.description('Start an autonomous strategy from a natural language goal')
|
|
343
|
+
.requiredOption('--budget <amount>', 'Total USDC budget')
|
|
344
|
+
.requiredOption('--max-per-trade <amount>', 'Per-trade cap')
|
|
345
|
+
.option('--risk <level>', 'Risk level (conservative|moderate|aggressive)', 'moderate')
|
|
346
|
+
.option('--interval <minutes>', 'Evaluation interval in minutes', '5')
|
|
347
|
+
.option('--chain <chain>', 'Target chain (repeatable)', (value, previous = []) => previous.concat(value), [])
|
|
348
|
+
.option('--dry-run', 'Simulate decisions without executing swaps')
|
|
349
|
+
.action(async (goal, opts) => {
|
|
350
|
+
showMiniBanner();
|
|
351
|
+
showSection('AUTONOMOUS START');
|
|
352
|
+
const strategy = await startAutonomous(goal, {
|
|
353
|
+
budget: parseFloat(opts.budget),
|
|
354
|
+
maxPerTrade: parseFloat(opts.maxPerTrade),
|
|
355
|
+
riskLevel: opts.risk,
|
|
356
|
+
interval: parseFloat(opts.interval),
|
|
357
|
+
chains: opts.chain,
|
|
358
|
+
dryRun: opts.dryRun,
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
kvDisplay([
|
|
362
|
+
['ID', strategy.id],
|
|
363
|
+
['Goal', strategy.goal],
|
|
364
|
+
['Budget', `${strategy.budget} USDC`],
|
|
365
|
+
['Max / Trade', `${strategy.maxPerTrade} USDC`],
|
|
366
|
+
['Risk', strategy.riskLevel],
|
|
367
|
+
['Mode', strategy.dryRun ? 'dry-run' : 'live'],
|
|
368
|
+
['Next Check', strategy.nextCheckAt],
|
|
369
|
+
]);
|
|
370
|
+
console.log('');
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
auto
|
|
374
|
+
.command('stop <id>')
|
|
375
|
+
.description('Stop a running autonomous strategy')
|
|
376
|
+
.action((id) => {
|
|
377
|
+
showMiniBanner();
|
|
378
|
+
showSection('AUTONOMOUS STOP');
|
|
379
|
+
const strategy = stopAutonomous(id);
|
|
380
|
+
if (!strategy) {
|
|
381
|
+
warn(`Strategy not found: ${id}`);
|
|
382
|
+
console.log('');
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
success(`Stopped ${strategy.id}`);
|
|
386
|
+
console.log('');
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
auto
|
|
390
|
+
.command('status [id]')
|
|
391
|
+
.description('Show one autonomous strategy or all active strategies')
|
|
392
|
+
.action((id) => {
|
|
393
|
+
showMiniBanner();
|
|
394
|
+
showSection('AUTONOMOUS STATUS');
|
|
395
|
+
|
|
396
|
+
if (!id) {
|
|
397
|
+
const items = getAutoStatus();
|
|
398
|
+
if (!items.length) {
|
|
399
|
+
warn('No autonomous strategies found');
|
|
400
|
+
console.log('');
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
items.forEach((item) => {
|
|
404
|
+
kvDisplay([
|
|
405
|
+
['ID', item.id],
|
|
406
|
+
['Status', item.status],
|
|
407
|
+
['Spent', `${item.spent}/${item.budget} USDC`],
|
|
408
|
+
['Trades', String(item.tradesExecuted)],
|
|
409
|
+
['PnL', `${item.pnl}`],
|
|
410
|
+
['Next Check', item.nextCheckAt || '-'],
|
|
411
|
+
]);
|
|
412
|
+
console.log('');
|
|
413
|
+
});
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const strategy = getAutoStatus(id);
|
|
418
|
+
if (!strategy) {
|
|
419
|
+
warn(`Strategy not found: ${id}`);
|
|
420
|
+
console.log('');
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
kvDisplay([
|
|
425
|
+
['ID', strategy.id],
|
|
426
|
+
['Goal', strategy.goal],
|
|
427
|
+
['Status', strategy.status],
|
|
428
|
+
['Spent', `${strategy.spent}/${strategy.budget} USDC`],
|
|
429
|
+
['Trades', String(strategy.tradesExecuted)],
|
|
430
|
+
['PnL', `${strategy.pnl}`],
|
|
431
|
+
['Risk', strategy.riskLevel],
|
|
432
|
+
['Mode', strategy.dryRun ? 'dry-run' : 'live'],
|
|
433
|
+
['Next Check', strategy.nextCheckAt || '-'],
|
|
434
|
+
['Last Decision', strategy.lastDecision || '-'],
|
|
435
|
+
]);
|
|
436
|
+
console.log('');
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
auto
|
|
440
|
+
.command('log <id>')
|
|
441
|
+
.description('Show the recent autonomous audit log')
|
|
442
|
+
.option('--limit <n>', 'Number of audit entries', '20')
|
|
443
|
+
.action((id, opts) => {
|
|
444
|
+
showMiniBanner();
|
|
445
|
+
showSection('AUTONOMOUS AUDIT');
|
|
446
|
+
const entries = getAuditLog(id, parseInt(opts.limit, 10));
|
|
447
|
+
if (!entries.length) {
|
|
448
|
+
warn('No audit entries found');
|
|
449
|
+
console.log('');
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
entries.forEach((entry) => {
|
|
454
|
+
const headline = `${entry.timestamp} ${entry.type}${entry.action ? ` ${entry.action}` : ''}`;
|
|
455
|
+
console.log(` ${theme.gold(headline)}`);
|
|
456
|
+
if (entry.reason) console.log(` ${theme.dim(entry.reason)}`);
|
|
457
|
+
if (entry.message) console.log(` ${theme.dim(entry.message)}`);
|
|
458
|
+
if (entry.price !== undefined) console.log(` ${theme.dim(`price: ${entry.price}`)}`);
|
|
459
|
+
console.log('');
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
auto
|
|
464
|
+
.command('list')
|
|
465
|
+
.description('List all autonomous strategies')
|
|
466
|
+
.action(() => {
|
|
467
|
+
showMiniBanner();
|
|
468
|
+
showSection('AUTONOMOUS STRATEGIES');
|
|
469
|
+
const items = listAutoStrategies();
|
|
470
|
+
if (!items.length) {
|
|
471
|
+
warn('No autonomous strategies found');
|
|
472
|
+
console.log('');
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
items.forEach((item) => {
|
|
477
|
+
kvDisplay([
|
|
478
|
+
['ID', item.id],
|
|
479
|
+
['Status', item.status],
|
|
480
|
+
['Spent', `${item.spent}/${item.budget} USDC`],
|
|
481
|
+
['Trades', String(item.tradesExecuted)],
|
|
482
|
+
['PnL', `${item.pnl}`],
|
|
483
|
+
['Next Check', item.nextCheckAt || '-'],
|
|
484
|
+
]);
|
|
485
|
+
console.log('');
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
|
|
332
489
|
// ═══════════════════════════════════════
|
|
333
490
|
// MARKET COMMANDS
|
|
334
491
|
// ═══════════════════════════════════════
|
|
@@ -1132,6 +1289,52 @@ export function cli(argv) {
|
|
|
1132
1289
|
.option('-p, --port <port>', 'Health server port', '18792')
|
|
1133
1290
|
.action((opts) => daemonRestart(opts));
|
|
1134
1291
|
|
|
1292
|
+
const whale = program
|
|
1293
|
+
.command('whale')
|
|
1294
|
+
.description('Whale Radar - wallet tracking and mirror trades');
|
|
1295
|
+
|
|
1296
|
+
whale
|
|
1297
|
+
.command('track <address>')
|
|
1298
|
+
.description('Track a wallet for new activity')
|
|
1299
|
+
.option('-c, --chain <chain>', 'Chain to monitor', 'base')
|
|
1300
|
+
.option('-l, --label <label>', 'Friendly wallet label')
|
|
1301
|
+
.option('--notify', 'Enable terminal notifications', true)
|
|
1302
|
+
.action((address, opts) => trackWallet(address, opts));
|
|
1303
|
+
|
|
1304
|
+
whale
|
|
1305
|
+
.command('list')
|
|
1306
|
+
.description('List tracked whale wallets')
|
|
1307
|
+
.action(() => listTracked());
|
|
1308
|
+
|
|
1309
|
+
whale
|
|
1310
|
+
.command('stop <address>')
|
|
1311
|
+
.description('Stop tracking a wallet')
|
|
1312
|
+
.action((address) => stopTracking(address));
|
|
1313
|
+
|
|
1314
|
+
whale
|
|
1315
|
+
.command('mirror <address>')
|
|
1316
|
+
.description('Enable copy-trading for a tracked whale')
|
|
1317
|
+
.option('--max <amount>', 'Max USDC-equivalent per trade')
|
|
1318
|
+
.option('-s, --slippage <pct>', 'Mirror trade slippage %', '2')
|
|
1319
|
+
.option('--dry-run', 'Log mirror trades without executing')
|
|
1320
|
+
.action((address, opts) => mirrorTrade(address, {
|
|
1321
|
+
maxPerTrade: opts.max ? parseFloat(opts.max) : null,
|
|
1322
|
+
slippage: parseFloat(opts.slippage),
|
|
1323
|
+
dryRun: Boolean(opts.dryRun),
|
|
1324
|
+
}));
|
|
1325
|
+
|
|
1326
|
+
whale
|
|
1327
|
+
.command('activity <address>')
|
|
1328
|
+
.description('Show recent activity for a whale wallet')
|
|
1329
|
+
.option('-l, --limit <n>', 'Number of transactions', '10')
|
|
1330
|
+
.option('-c, --chain <chain>', 'Chain to query', 'base')
|
|
1331
|
+
.action((address, opts) => getWhaleActivity(address, parseInt(opts.limit, 10), opts));
|
|
1332
|
+
|
|
1333
|
+
whale
|
|
1334
|
+
.command('feed')
|
|
1335
|
+
.description('Open the live whale event feed')
|
|
1336
|
+
.action(() => startWhaleFeed());
|
|
1337
|
+
|
|
1135
1338
|
// ═══════════════════════════════════════
|
|
1136
1339
|
// TELEGRAM COMMANDS
|
|
1137
1340
|
// ═══════════════════════════════════════
|
|
@@ -1389,6 +1592,19 @@ export function cli(argv) {
|
|
|
1389
1592
|
}
|
|
1390
1593
|
});
|
|
1391
1594
|
|
|
1595
|
+
program
|
|
1596
|
+
.command('dash')
|
|
1597
|
+
.description('Launch the live terminal dashboard')
|
|
1598
|
+
.option('--refresh <seconds>', 'Refresh interval in seconds', '30')
|
|
1599
|
+
.option('--compact', 'Use the compact 2-panel layout')
|
|
1600
|
+
.action(async (opts) => {
|
|
1601
|
+
const dashboard = createDashboard({
|
|
1602
|
+
refresh: parseInt(opts.refresh, 10),
|
|
1603
|
+
compact: Boolean(opts.compact),
|
|
1604
|
+
});
|
|
1605
|
+
await dashboard.ready;
|
|
1606
|
+
});
|
|
1607
|
+
|
|
1392
1608
|
// ═══════════════════════════════════════
|
|
1393
1609
|
// FUZZY / NATURAL LANGUAGE FALLBACK
|
|
1394
1610
|
// ═══════════════════════════════════════
|
|
@@ -1717,6 +1933,7 @@ function showCommandList() {
|
|
|
1717
1933
|
['watch', 'Live price monitoring + alerts'],
|
|
1718
1934
|
['gas', 'Gas prices & cost estimates'],
|
|
1719
1935
|
['trade', 'Swap tokens, snipe, trading'],
|
|
1936
|
+
['auto', 'Autonomous trader strategies'],
|
|
1720
1937
|
['bridge', 'Cross-chain bridge (LI.FI)'],
|
|
1721
1938
|
['dca', 'Dollar-cost averaging orders'],
|
|
1722
1939
|
['ai chat', 'Standalone AI chat session'],
|
|
@@ -1727,6 +1944,7 @@ function showCommandList() {
|
|
|
1727
1944
|
['memory', 'Persistent cross-session memory'],
|
|
1728
1945
|
['script', 'Execution scripts & strategies'],
|
|
1729
1946
|
['market', 'Market intel & token data'],
|
|
1947
|
+
['whale', 'Whale Radar - wallet tracking'],
|
|
1730
1948
|
['oracle', 'On-chain random oracle'],
|
|
1731
1949
|
['casino', 'The Clawsino - betting'],
|
|
1732
1950
|
['poker', 'GTO Poker Arena — heads-up holdem'],
|
package/src/config/keys.js
CHANGED
|
@@ -126,6 +126,14 @@ export const SERVICES = {
|
|
|
126
126
|
docsUrl: 'https://docs.dexscreener.com',
|
|
127
127
|
validate: (key) => key.length > 10,
|
|
128
128
|
},
|
|
129
|
+
etherscan: {
|
|
130
|
+
name: 'Etherscan',
|
|
131
|
+
category: 'data',
|
|
132
|
+
description: 'Explorer APIs — Etherscan, Basescan, Arbiscan, Polygonscan',
|
|
133
|
+
envVar: 'ETHERSCAN_API_KEY',
|
|
134
|
+
docsUrl: 'https://etherscan.io/apis',
|
|
135
|
+
validate: (key) => key.length > 10,
|
|
136
|
+
},
|
|
129
137
|
defillama: {
|
|
130
138
|
name: 'DefiLlama',
|
|
131
139
|
category: 'data',
|
|
@@ -298,6 +306,14 @@ export function getKeyFromEnv(service) {
|
|
|
298
306
|
return null;
|
|
299
307
|
}
|
|
300
308
|
|
|
309
|
+
/**
|
|
310
|
+
* Get an API key without prompting.
|
|
311
|
+
* Prefers auto-stored keys, then environment variables.
|
|
312
|
+
*/
|
|
313
|
+
export function getApiKey(service) {
|
|
314
|
+
return getKeyAuto(service) || getKeyFromEnv(service);
|
|
315
|
+
}
|
|
316
|
+
|
|
301
317
|
/**
|
|
302
318
|
* Remove a key
|
|
303
319
|
*/
|
package/src/config/store.js
CHANGED
package/src/services/gas.js
CHANGED
|
@@ -1,49 +1,49 @@
|
|
|
1
1
|
import { ethers } from 'ethers';
|
|
2
2
|
import { getConfig, getRPC } from '../config/store.js';
|
|
3
3
|
import { theme } from '../ui/theme.js';
|
|
4
|
-
import { spinner, kvDisplay,
|
|
4
|
+
import { spinner, kvDisplay, error, info } from '../ui/components.js';
|
|
5
5
|
import { showSection } from '../ui/banner.js';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Show current gas prices for active chain
|
|
13
|
-
*/
|
|
14
|
-
export async function showGas(chain) {
|
|
15
|
-
chain = chain || getConfig('chain') || 'base';
|
|
16
|
-
const rpc = getRPC(chain);
|
|
7
|
+
export async function fetchGasSnapshot(chain) {
|
|
8
|
+
const resolvedChain = chain || getConfig('chain') || 'base';
|
|
9
|
+
const rpc = getRPC(resolvedChain);
|
|
17
10
|
const provider = new ethers.JsonRpcProvider(rpc);
|
|
11
|
+
const feeData = await provider.getFeeData();
|
|
12
|
+
const block = await provider.getBlock('latest');
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
chain: resolvedChain,
|
|
16
|
+
blockNumber: block?.number || null,
|
|
17
|
+
feeData,
|
|
18
|
+
gasPrice: feeData.gasPrice ? parseFloat(ethers.formatUnits(feeData.gasPrice, 'gwei')) : 0,
|
|
19
|
+
maxFee: feeData.maxFeePerGas ? parseFloat(ethers.formatUnits(feeData.maxFeePerGas, 'gwei')) : 0,
|
|
20
|
+
maxPriority: feeData.maxPriorityFeePerGas ? parseFloat(ethers.formatUnits(feeData.maxPriorityFeePerGas, 'gwei')) : 0,
|
|
21
|
+
baseFee: block?.baseFeePerGas ? parseFloat(ethers.formatUnits(block.baseFeePerGas, 'gwei')) : 0,
|
|
22
|
+
ethPrice: await getETHPrice(),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
18
25
|
|
|
19
|
-
|
|
26
|
+
export async function showGas(chain) {
|
|
27
|
+
const resolvedChain = chain || getConfig('chain') || 'base';
|
|
28
|
+
const spin = spinner(`Fetching gas on ${resolvedChain}...`).start();
|
|
20
29
|
|
|
21
30
|
try {
|
|
22
|
-
const
|
|
23
|
-
const block = await provider.getBlock('latest');
|
|
24
|
-
|
|
31
|
+
const snapshot = await fetchGasSnapshot(resolvedChain);
|
|
25
32
|
spin.succeed('Gas data fetched');
|
|
26
33
|
|
|
27
|
-
const gasPrice = feeData.gasPrice ? parseFloat(ethers.formatUnits(feeData.gasPrice, 'gwei')) : 0;
|
|
28
|
-
const maxFee = feeData.maxFeePerGas ? parseFloat(ethers.formatUnits(feeData.maxFeePerGas, 'gwei')) : 0;
|
|
29
|
-
const maxPriority = feeData.maxPriorityFeePerGas ? parseFloat(ethers.formatUnits(feeData.maxPriorityFeePerGas, 'gwei')) : 0;
|
|
30
|
-
const baseFee = block?.baseFeePerGas ? parseFloat(ethers.formatUnits(block.baseFeePerGas, 'gwei')) : 0;
|
|
31
|
-
|
|
32
34
|
console.log('');
|
|
33
|
-
showSection(`GAS
|
|
35
|
+
showSection(`GAS - ${resolvedChain.toUpperCase()}`);
|
|
34
36
|
kvDisplay([
|
|
35
|
-
['Gas Price', `${gasPrice.toFixed(4)} gwei`],
|
|
36
|
-
['Base Fee', `${baseFee.toFixed(4)} gwei`],
|
|
37
|
-
['Max Fee', `${maxFee.toFixed(4)} gwei`],
|
|
38
|
-
['Priority Fee', `${maxPriority.toFixed(4)} gwei`],
|
|
39
|
-
['Block', `#${
|
|
37
|
+
['Gas Price', `${snapshot.gasPrice.toFixed(4)} gwei`],
|
|
38
|
+
['Base Fee', `${snapshot.baseFee.toFixed(4)} gwei`],
|
|
39
|
+
['Max Fee', `${snapshot.maxFee.toFixed(4)} gwei`],
|
|
40
|
+
['Priority Fee', `${snapshot.maxPriority.toFixed(4)} gwei`],
|
|
41
|
+
['Block', `#${snapshot.blockNumber || '?'}`],
|
|
40
42
|
]);
|
|
41
43
|
|
|
42
|
-
// Estimate common operations
|
|
43
44
|
console.log('');
|
|
44
45
|
showSection('ESTIMATED COSTS');
|
|
45
46
|
|
|
46
|
-
const ethPrice = await getETHPrice();
|
|
47
47
|
const estimates = [
|
|
48
48
|
{ name: 'ETH Transfer', gas: 21000n },
|
|
49
49
|
{ name: 'ERC-20 Transfer', gas: 65000n },
|
|
@@ -54,29 +54,25 @@ export async function showGas(chain) {
|
|
|
54
54
|
];
|
|
55
55
|
|
|
56
56
|
estimates.forEach(({ name, gas }) => {
|
|
57
|
-
const costWei = gas * (feeData.gasPrice || 0n);
|
|
57
|
+
const costWei = gas * (snapshot.feeData.gasPrice || 0n);
|
|
58
58
|
const costETH = parseFloat(ethers.formatEther(costWei));
|
|
59
|
-
const costUSD = costETH * ethPrice;
|
|
60
|
-
const label = name.padEnd(24);
|
|
59
|
+
const costUSD = costETH * snapshot.ethPrice;
|
|
61
60
|
const ethStr = costETH < 0.000001 ? '<$0.01' : `${costETH.toFixed(6)} ETH`;
|
|
62
61
|
const usdStr = costUSD < 0.01 ? '<$0.01' : `$${costUSD.toFixed(2)}`;
|
|
63
|
-
console.log(` ${theme.dim(
|
|
62
|
+
console.log(` ${theme.dim(name.padEnd(24))} ${ethStr.padEnd(16)} ${theme.gold(usdStr)}`);
|
|
64
63
|
});
|
|
65
64
|
|
|
66
65
|
console.log('');
|
|
67
|
-
info(`ETH price: $${ethPrice.toFixed(2)}`);
|
|
66
|
+
info(`ETH price: $${snapshot.ethPrice.toFixed(2)}`);
|
|
68
67
|
console.log('');
|
|
69
68
|
|
|
70
|
-
return
|
|
69
|
+
return snapshot;
|
|
71
70
|
} catch (err) {
|
|
72
71
|
spin.fail('Failed to fetch gas data');
|
|
73
72
|
error(err.message);
|
|
74
73
|
}
|
|
75
74
|
}
|
|
76
75
|
|
|
77
|
-
/**
|
|
78
|
-
* Get ETH price (CoinGecko, fallback estimate)
|
|
79
|
-
*/
|
|
80
76
|
async function getETHPrice() {
|
|
81
77
|
try {
|
|
82
78
|
const fetch = (await import('node-fetch')).default;
|
|
@@ -88,12 +84,9 @@ async function getETHPrice() {
|
|
|
88
84
|
}
|
|
89
85
|
}
|
|
90
86
|
|
|
91
|
-
/**
|
|
92
|
-
* Estimate gas for a specific transaction (pre-trade check)
|
|
93
|
-
*/
|
|
94
87
|
export async function estimateTradeGas(txParams, chain) {
|
|
95
|
-
|
|
96
|
-
const rpc = getRPC(
|
|
88
|
+
const resolvedChain = chain || getConfig('chain') || 'base';
|
|
89
|
+
const rpc = getRPC(resolvedChain);
|
|
97
90
|
const provider = new ethers.JsonRpcProvider(rpc);
|
|
98
91
|
|
|
99
92
|
try {
|