@darksol/terminal 0.11.0 → 0.13.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/AUDIT-2026-03-14.md +241 -0
- package/README.md +106 -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 +286 -0
- package/src/config/keys.js +16 -0
- package/src/config/store.js +34 -0
- package/src/llm/intent.js +2 -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/trading/arb-dexes.js +291 -0
- package/src/trading/arb.js +923 -0
- package/src/trading/index.js +2 -0
- package/src/ui/dashboard.js +596 -0
- package/src/wallet/history.js +47 -46
- package/src/wallet/portfolio.js +75 -87
- package/src/web/commands.js +55 -0
package/src/cli.js
CHANGED
|
@@ -2,17 +2,21 @@ 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';
|
|
14
17
|
import { snipeToken, watchSnipe } from './trading/snipe.js';
|
|
15
18
|
import { createDCA, listDCA, cancelDCA, runDCA } from './trading/dca.js';
|
|
19
|
+
import { arbScan, arbMonitor, arbExecute, arbStats, arbConfig, arbAddEndpoint, arbAddPair, arbRemovePair, arbInfo } from './trading/arb.js';
|
|
16
20
|
import { executeLifiSwap, executeLifiBridge, checkBridgeStatus, showSupportedChains } from './services/lifi.js';
|
|
17
21
|
import { topMovers, tokenDetail, compareTokens } from './services/market.js';
|
|
18
22
|
import { oracleFlip, oracleDice, oracleNumber, oracleShuffle, oracleHealth } from './services/oracle.js';
|
|
@@ -42,6 +46,7 @@ import { runSetupWizard } from './setup/wizard.js';
|
|
|
42
46
|
import { displaySoul, hasSoul, resetSoul, runSoulSetup } from './soul/index.js';
|
|
43
47
|
import { clearMemories, exportMemories, getRecentMemories, searchMemories } from './memory/index.js';
|
|
44
48
|
import { getAgentStatus, planAgentGoal, runAgentTask } from './agent/index.js';
|
|
49
|
+
import { getAuditLog, getStatus as getAutoStatus, listStrategies as listAutoStrategies, startAutonomous, stopAutonomous } from './agent/autonomous.js';
|
|
45
50
|
import { daemonStart, daemonStop, daemonStatus, daemonRestart } from './daemon/index.js';
|
|
46
51
|
import { telegramSetup, telegramStartForeground, telegramStopCommand, telegramStatusCommand, telegramSendCommand } from './services/telegram.js';
|
|
47
52
|
import { createRequire } from 'module';
|
|
@@ -329,6 +334,225 @@ export function cli(argv) {
|
|
|
329
334
|
.description('Execute pending DCA orders')
|
|
330
335
|
.action(() => runDCA());
|
|
331
336
|
|
|
337
|
+
// ═══════════════════════════════════════
|
|
338
|
+
// ARBITRAGE COMMANDS
|
|
339
|
+
// ═══════════════════════════════════════
|
|
340
|
+
const arb = program
|
|
341
|
+
.command('arb')
|
|
342
|
+
.description('Cross-DEX arbitrage - scan, monitor, execute');
|
|
343
|
+
|
|
344
|
+
arb
|
|
345
|
+
.command('scan')
|
|
346
|
+
.description('One-shot scan across DEXs for price differences')
|
|
347
|
+
.option('-c, --chain <chain>', 'Target chain', 'base')
|
|
348
|
+
.option('-p, --pair <pair>', 'Token pair to scan (e.g. WETH/USDC)')
|
|
349
|
+
.option('-m, --min-profit <usd>', 'Minimum profit threshold in USD', '0.50')
|
|
350
|
+
.option('-s, --trade-size <eth>', 'Trade size in ETH', '0.1')
|
|
351
|
+
.action((opts) => arbScan({
|
|
352
|
+
chain: opts.chain,
|
|
353
|
+
pair: opts.pair,
|
|
354
|
+
minProfit: parseFloat(opts.minProfit),
|
|
355
|
+
tradeSize: parseFloat(opts.tradeSize),
|
|
356
|
+
}));
|
|
357
|
+
|
|
358
|
+
arb
|
|
359
|
+
.command('monitor')
|
|
360
|
+
.description('Real-time block-by-block arb monitoring (WSS recommended)')
|
|
361
|
+
.option('-c, --chain <chain>', 'Target chain', 'base')
|
|
362
|
+
.option('-e, --execute', 'Auto-execute profitable arbs')
|
|
363
|
+
.option('-d, --dry-run', 'Dry-run mode (no real transactions)')
|
|
364
|
+
.option('-m, --min-profit <usd>', 'Minimum profit threshold in USD', '0.50')
|
|
365
|
+
.action((opts) => arbMonitor({
|
|
366
|
+
chain: opts.chain,
|
|
367
|
+
execute: opts.execute,
|
|
368
|
+
dryRun: opts.dryRun !== undefined ? opts.dryRun : undefined,
|
|
369
|
+
minProfit: opts.minProfit,
|
|
370
|
+
}));
|
|
371
|
+
|
|
372
|
+
arb
|
|
373
|
+
.command('stats')
|
|
374
|
+
.description('View arb history and performance stats')
|
|
375
|
+
.option('-d, --days <days>', 'Number of days to show', '7')
|
|
376
|
+
.action((opts) => arbStats({ days: opts.days }));
|
|
377
|
+
|
|
378
|
+
arb
|
|
379
|
+
.command('config')
|
|
380
|
+
.description('Interactive arb configuration (thresholds, dry-run, DEXes)')
|
|
381
|
+
.action(() => arbConfig());
|
|
382
|
+
|
|
383
|
+
arb
|
|
384
|
+
.command('add-endpoint <chain> <url>')
|
|
385
|
+
.description('Add a custom WSS or RPC endpoint for faster arb detection')
|
|
386
|
+
.action((chain, url) => arbAddEndpoint({ chain, url }));
|
|
387
|
+
|
|
388
|
+
arb
|
|
389
|
+
.command('add-pair <tokenA> <tokenB>')
|
|
390
|
+
.description('Add a token pair to the arb scan list')
|
|
391
|
+
.action((tokenA, tokenB) => arbAddPair({ tokenA, tokenB }));
|
|
392
|
+
|
|
393
|
+
arb
|
|
394
|
+
.command('remove-pair <tokenA> <tokenB>')
|
|
395
|
+
.description('Remove a token pair from the arb scan list')
|
|
396
|
+
.action((tokenA, tokenB) => arbRemovePair({ tokenA, tokenB }));
|
|
397
|
+
|
|
398
|
+
arb
|
|
399
|
+
.command('info')
|
|
400
|
+
.description('How arbitrage works, setup guide, and risk warnings')
|
|
401
|
+
.action(() => arbInfo());
|
|
402
|
+
|
|
403
|
+
const auto = program
|
|
404
|
+
.command('auto')
|
|
405
|
+
.description('Autonomous trader mode - goal-based automated execution');
|
|
406
|
+
|
|
407
|
+
auto
|
|
408
|
+
.command('start <goal>')
|
|
409
|
+
.description('Start an autonomous strategy from a natural language goal')
|
|
410
|
+
.requiredOption('--budget <amount>', 'Total USDC budget')
|
|
411
|
+
.requiredOption('--max-per-trade <amount>', 'Per-trade cap')
|
|
412
|
+
.option('--risk <level>', 'Risk level (conservative|moderate|aggressive)', 'moderate')
|
|
413
|
+
.option('--interval <minutes>', 'Evaluation interval in minutes', '5')
|
|
414
|
+
.option('--chain <chain>', 'Target chain (repeatable)', (value, previous = []) => previous.concat(value), [])
|
|
415
|
+
.option('--dry-run', 'Simulate decisions without executing swaps')
|
|
416
|
+
.action(async (goal, opts) => {
|
|
417
|
+
showMiniBanner();
|
|
418
|
+
showSection('AUTONOMOUS START');
|
|
419
|
+
const strategy = await startAutonomous(goal, {
|
|
420
|
+
budget: parseFloat(opts.budget),
|
|
421
|
+
maxPerTrade: parseFloat(opts.maxPerTrade),
|
|
422
|
+
riskLevel: opts.risk,
|
|
423
|
+
interval: parseFloat(opts.interval),
|
|
424
|
+
chains: opts.chain,
|
|
425
|
+
dryRun: opts.dryRun,
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
kvDisplay([
|
|
429
|
+
['ID', strategy.id],
|
|
430
|
+
['Goal', strategy.goal],
|
|
431
|
+
['Budget', `${strategy.budget} USDC`],
|
|
432
|
+
['Max / Trade', `${strategy.maxPerTrade} USDC`],
|
|
433
|
+
['Risk', strategy.riskLevel],
|
|
434
|
+
['Mode', strategy.dryRun ? 'dry-run' : 'live'],
|
|
435
|
+
['Next Check', strategy.nextCheckAt],
|
|
436
|
+
]);
|
|
437
|
+
console.log('');
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
auto
|
|
441
|
+
.command('stop <id>')
|
|
442
|
+
.description('Stop a running autonomous strategy')
|
|
443
|
+
.action((id) => {
|
|
444
|
+
showMiniBanner();
|
|
445
|
+
showSection('AUTONOMOUS STOP');
|
|
446
|
+
const strategy = stopAutonomous(id);
|
|
447
|
+
if (!strategy) {
|
|
448
|
+
warn(`Strategy not found: ${id}`);
|
|
449
|
+
console.log('');
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
success(`Stopped ${strategy.id}`);
|
|
453
|
+
console.log('');
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
auto
|
|
457
|
+
.command('status [id]')
|
|
458
|
+
.description('Show one autonomous strategy or all active strategies')
|
|
459
|
+
.action((id) => {
|
|
460
|
+
showMiniBanner();
|
|
461
|
+
showSection('AUTONOMOUS STATUS');
|
|
462
|
+
|
|
463
|
+
if (!id) {
|
|
464
|
+
const items = getAutoStatus();
|
|
465
|
+
if (!items.length) {
|
|
466
|
+
warn('No autonomous strategies found');
|
|
467
|
+
console.log('');
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
items.forEach((item) => {
|
|
471
|
+
kvDisplay([
|
|
472
|
+
['ID', item.id],
|
|
473
|
+
['Status', item.status],
|
|
474
|
+
['Spent', `${item.spent}/${item.budget} USDC`],
|
|
475
|
+
['Trades', String(item.tradesExecuted)],
|
|
476
|
+
['PnL', `${item.pnl}`],
|
|
477
|
+
['Next Check', item.nextCheckAt || '-'],
|
|
478
|
+
]);
|
|
479
|
+
console.log('');
|
|
480
|
+
});
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const strategy = getAutoStatus(id);
|
|
485
|
+
if (!strategy) {
|
|
486
|
+
warn(`Strategy not found: ${id}`);
|
|
487
|
+
console.log('');
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
kvDisplay([
|
|
492
|
+
['ID', strategy.id],
|
|
493
|
+
['Goal', strategy.goal],
|
|
494
|
+
['Status', strategy.status],
|
|
495
|
+
['Spent', `${strategy.spent}/${strategy.budget} USDC`],
|
|
496
|
+
['Trades', String(strategy.tradesExecuted)],
|
|
497
|
+
['PnL', `${strategy.pnl}`],
|
|
498
|
+
['Risk', strategy.riskLevel],
|
|
499
|
+
['Mode', strategy.dryRun ? 'dry-run' : 'live'],
|
|
500
|
+
['Next Check', strategy.nextCheckAt || '-'],
|
|
501
|
+
['Last Decision', strategy.lastDecision || '-'],
|
|
502
|
+
]);
|
|
503
|
+
console.log('');
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
auto
|
|
507
|
+
.command('log <id>')
|
|
508
|
+
.description('Show the recent autonomous audit log')
|
|
509
|
+
.option('--limit <n>', 'Number of audit entries', '20')
|
|
510
|
+
.action((id, opts) => {
|
|
511
|
+
showMiniBanner();
|
|
512
|
+
showSection('AUTONOMOUS AUDIT');
|
|
513
|
+
const entries = getAuditLog(id, parseInt(opts.limit, 10));
|
|
514
|
+
if (!entries.length) {
|
|
515
|
+
warn('No audit entries found');
|
|
516
|
+
console.log('');
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
entries.forEach((entry) => {
|
|
521
|
+
const headline = `${entry.timestamp} ${entry.type}${entry.action ? ` ${entry.action}` : ''}`;
|
|
522
|
+
console.log(` ${theme.gold(headline)}`);
|
|
523
|
+
if (entry.reason) console.log(` ${theme.dim(entry.reason)}`);
|
|
524
|
+
if (entry.message) console.log(` ${theme.dim(entry.message)}`);
|
|
525
|
+
if (entry.price !== undefined) console.log(` ${theme.dim(`price: ${entry.price}`)}`);
|
|
526
|
+
console.log('');
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
auto
|
|
531
|
+
.command('list')
|
|
532
|
+
.description('List all autonomous strategies')
|
|
533
|
+
.action(() => {
|
|
534
|
+
showMiniBanner();
|
|
535
|
+
showSection('AUTONOMOUS STRATEGIES');
|
|
536
|
+
const items = listAutoStrategies();
|
|
537
|
+
if (!items.length) {
|
|
538
|
+
warn('No autonomous strategies found');
|
|
539
|
+
console.log('');
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
items.forEach((item) => {
|
|
544
|
+
kvDisplay([
|
|
545
|
+
['ID', item.id],
|
|
546
|
+
['Status', item.status],
|
|
547
|
+
['Spent', `${item.spent}/${item.budget} USDC`],
|
|
548
|
+
['Trades', String(item.tradesExecuted)],
|
|
549
|
+
['PnL', `${item.pnl}`],
|
|
550
|
+
['Next Check', item.nextCheckAt || '-'],
|
|
551
|
+
]);
|
|
552
|
+
console.log('');
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
|
|
332
556
|
// ═══════════════════════════════════════
|
|
333
557
|
// MARKET COMMANDS
|
|
334
558
|
// ═══════════════════════════════════════
|
|
@@ -1132,6 +1356,52 @@ export function cli(argv) {
|
|
|
1132
1356
|
.option('-p, --port <port>', 'Health server port', '18792')
|
|
1133
1357
|
.action((opts) => daemonRestart(opts));
|
|
1134
1358
|
|
|
1359
|
+
const whale = program
|
|
1360
|
+
.command('whale')
|
|
1361
|
+
.description('Whale Radar - wallet tracking and mirror trades');
|
|
1362
|
+
|
|
1363
|
+
whale
|
|
1364
|
+
.command('track <address>')
|
|
1365
|
+
.description('Track a wallet for new activity')
|
|
1366
|
+
.option('-c, --chain <chain>', 'Chain to monitor', 'base')
|
|
1367
|
+
.option('-l, --label <label>', 'Friendly wallet label')
|
|
1368
|
+
.option('--notify', 'Enable terminal notifications', true)
|
|
1369
|
+
.action((address, opts) => trackWallet(address, opts));
|
|
1370
|
+
|
|
1371
|
+
whale
|
|
1372
|
+
.command('list')
|
|
1373
|
+
.description('List tracked whale wallets')
|
|
1374
|
+
.action(() => listTracked());
|
|
1375
|
+
|
|
1376
|
+
whale
|
|
1377
|
+
.command('stop <address>')
|
|
1378
|
+
.description('Stop tracking a wallet')
|
|
1379
|
+
.action((address) => stopTracking(address));
|
|
1380
|
+
|
|
1381
|
+
whale
|
|
1382
|
+
.command('mirror <address>')
|
|
1383
|
+
.description('Enable copy-trading for a tracked whale')
|
|
1384
|
+
.option('--max <amount>', 'Max USDC-equivalent per trade')
|
|
1385
|
+
.option('-s, --slippage <pct>', 'Mirror trade slippage %', '2')
|
|
1386
|
+
.option('--dry-run', 'Log mirror trades without executing')
|
|
1387
|
+
.action((address, opts) => mirrorTrade(address, {
|
|
1388
|
+
maxPerTrade: opts.max ? parseFloat(opts.max) : null,
|
|
1389
|
+
slippage: parseFloat(opts.slippage),
|
|
1390
|
+
dryRun: Boolean(opts.dryRun),
|
|
1391
|
+
}));
|
|
1392
|
+
|
|
1393
|
+
whale
|
|
1394
|
+
.command('activity <address>')
|
|
1395
|
+
.description('Show recent activity for a whale wallet')
|
|
1396
|
+
.option('-l, --limit <n>', 'Number of transactions', '10')
|
|
1397
|
+
.option('-c, --chain <chain>', 'Chain to query', 'base')
|
|
1398
|
+
.action((address, opts) => getWhaleActivity(address, parseInt(opts.limit, 10), opts));
|
|
1399
|
+
|
|
1400
|
+
whale
|
|
1401
|
+
.command('feed')
|
|
1402
|
+
.description('Open the live whale event feed')
|
|
1403
|
+
.action(() => startWhaleFeed());
|
|
1404
|
+
|
|
1135
1405
|
// ═══════════════════════════════════════
|
|
1136
1406
|
// TELEGRAM COMMANDS
|
|
1137
1407
|
// ═══════════════════════════════════════
|
|
@@ -1389,6 +1659,19 @@ export function cli(argv) {
|
|
|
1389
1659
|
}
|
|
1390
1660
|
});
|
|
1391
1661
|
|
|
1662
|
+
program
|
|
1663
|
+
.command('dash')
|
|
1664
|
+
.description('Launch the live terminal dashboard')
|
|
1665
|
+
.option('--refresh <seconds>', 'Refresh interval in seconds', '30')
|
|
1666
|
+
.option('--compact', 'Use the compact 2-panel layout')
|
|
1667
|
+
.action(async (opts) => {
|
|
1668
|
+
const dashboard = createDashboard({
|
|
1669
|
+
refresh: parseInt(opts.refresh, 10),
|
|
1670
|
+
compact: Boolean(opts.compact),
|
|
1671
|
+
});
|
|
1672
|
+
await dashboard.ready;
|
|
1673
|
+
});
|
|
1674
|
+
|
|
1392
1675
|
// ═══════════════════════════════════════
|
|
1393
1676
|
// FUZZY / NATURAL LANGUAGE FALLBACK
|
|
1394
1677
|
// ═══════════════════════════════════════
|
|
@@ -1717,6 +2000,8 @@ function showCommandList() {
|
|
|
1717
2000
|
['watch', 'Live price monitoring + alerts'],
|
|
1718
2001
|
['gas', 'Gas prices & cost estimates'],
|
|
1719
2002
|
['trade', 'Swap tokens, snipe, trading'],
|
|
2003
|
+
['arb', 'Cross-DEX arbitrage scanner'],
|
|
2004
|
+
['auto', 'Autonomous trader strategies'],
|
|
1720
2005
|
['bridge', 'Cross-chain bridge (LI.FI)'],
|
|
1721
2006
|
['dca', 'Dollar-cost averaging orders'],
|
|
1722
2007
|
['ai chat', 'Standalone AI chat session'],
|
|
@@ -1727,6 +2012,7 @@ function showCommandList() {
|
|
|
1727
2012
|
['memory', 'Persistent cross-session memory'],
|
|
1728
2013
|
['script', 'Execution scripts & strategies'],
|
|
1729
2014
|
['market', 'Market intel & token data'],
|
|
2015
|
+
['whale', 'Whale Radar - wallet tracking'],
|
|
1730
2016
|
['oracle', 'On-chain random oracle'],
|
|
1731
2017
|
['casino', 'The Clawsino - betting'],
|
|
1732
2018
|
['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
|
@@ -51,6 +51,40 @@ const config = new Conf({
|
|
|
51
51
|
maxOrders: 100,
|
|
52
52
|
},
|
|
53
53
|
},
|
|
54
|
+
autonomous: {
|
|
55
|
+
type: 'object',
|
|
56
|
+
default: {
|
|
57
|
+
strategies: [],
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
arb: {
|
|
61
|
+
type: 'object',
|
|
62
|
+
default: {
|
|
63
|
+
enabled: false,
|
|
64
|
+
minProfitUsd: 0.50,
|
|
65
|
+
maxTradeSize: 1.0,
|
|
66
|
+
gasCeiling: 0.01,
|
|
67
|
+
cooldownMs: 5000,
|
|
68
|
+
dryRun: true,
|
|
69
|
+
tokenAllowlist: [],
|
|
70
|
+
tokenDenylist: [],
|
|
71
|
+
endpoints: {
|
|
72
|
+
wss: {},
|
|
73
|
+
rpc: {},
|
|
74
|
+
},
|
|
75
|
+
dexes: {
|
|
76
|
+
base: ['uniswapV3', 'aerodrome', 'sushiswap'],
|
|
77
|
+
ethereum: ['uniswapV3', 'sushiswap'],
|
|
78
|
+
arbitrum: ['uniswapV3', 'sushiswap', 'camelot'],
|
|
79
|
+
optimism: ['uniswapV3', 'velodrome'],
|
|
80
|
+
polygon: ['uniswapV3', 'quickswap'],
|
|
81
|
+
},
|
|
82
|
+
pairs: [
|
|
83
|
+
{ tokenA: 'WETH', tokenB: 'USDC' },
|
|
84
|
+
{ tokenA: 'WETH', tokenB: 'USDT' },
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
},
|
|
54
88
|
services: {
|
|
55
89
|
type: 'object',
|
|
56
90
|
default: {
|
package/src/llm/intent.js
CHANGED
|
@@ -66,6 +66,8 @@ ACTIONS (use the most specific one):
|
|
|
66
66
|
- "gas" — gas price check
|
|
67
67
|
- "cards" — order a prepaid Visa/Mastercard with crypto (e.g. "order a $50 card", "get me a prepaid card")
|
|
68
68
|
- "casino" — play a casino game (coinflip, dice, hilo, slots). All bets are $1 USDC. (e.g. "flip a coin", "bet on heads", "play slots", "roll dice over 3")
|
|
69
|
+
- "arb_scan" — scan for cross-DEX arbitrage opportunities (e.g. "find arb opportunities", "scan for price differences", "check arb on base")
|
|
70
|
+
- "arb_monitor" — start real-time arbitrage monitoring (e.g. "monitor arb", "watch for arb opportunities")
|
|
69
71
|
- "unknown" — can't determine what the user wants
|
|
70
72
|
|
|
71
73
|
CASINO GAMES:
|
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 {
|