@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/wallet/history.js
CHANGED
|
@@ -1,93 +1,94 @@
|
|
|
1
1
|
import fetch from 'node-fetch';
|
|
2
2
|
import { loadWallet } from './keystore.js';
|
|
3
|
-
import { getConfig
|
|
3
|
+
import { getConfig } from '../config/store.js';
|
|
4
4
|
import { theme } from '../ui/theme.js';
|
|
5
|
-
import { spinner,
|
|
5
|
+
import { spinner, error, info, table } from '../ui/components.js';
|
|
6
6
|
import { showSection } from '../ui/banner.js';
|
|
7
7
|
|
|
8
|
-
// ══════════════════════════════════════════════════
|
|
9
|
-
// TRANSACTION HISTORY
|
|
10
|
-
// ══════════════════════════════════════════════════
|
|
11
|
-
|
|
12
8
|
const EXPLORER_APIS = {
|
|
13
|
-
base:
|
|
14
|
-
ethereum: { api: 'https://api.etherscan.io/api',
|
|
15
|
-
arbitrum: { api: 'https://api.arbiscan.io/api',
|
|
16
|
-
optimism: { api: 'https://api-optimistic.etherscan.io/api',
|
|
17
|
-
polygon:
|
|
9
|
+
base: { api: 'https://api.basescan.org/api', explorer: 'https://basescan.org' },
|
|
10
|
+
ethereum: { api: 'https://api.etherscan.io/api', explorer: 'https://etherscan.io' },
|
|
11
|
+
arbitrum: { api: 'https://api.arbiscan.io/api', explorer: 'https://arbiscan.io' },
|
|
12
|
+
optimism: { api: 'https://api-optimistic.etherscan.io/api', explorer: 'https://optimistic.etherscan.io' },
|
|
13
|
+
polygon: { api: 'https://api.polygonscan.com/api', explorer: 'https://polygonscan.com' },
|
|
18
14
|
};
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
* Show recent transaction history
|
|
22
|
-
*/
|
|
23
|
-
export async function showHistory(walletName, opts = {}) {
|
|
16
|
+
export async function fetchHistorySnapshot(walletName, opts = {}) {
|
|
24
17
|
const name = walletName || getConfig('activeWallet');
|
|
25
18
|
if (!name) {
|
|
26
|
-
|
|
27
|
-
return;
|
|
19
|
+
throw new Error('No wallet specified. Use: darksol wallet history <name>');
|
|
28
20
|
}
|
|
29
21
|
|
|
30
22
|
const walletData = loadWallet(name);
|
|
31
23
|
const address = walletData.address;
|
|
32
24
|
const chain = opts.chain || walletData.chain || getConfig('chain') || 'base';
|
|
33
|
-
const limit = parseInt(opts.limit || '10');
|
|
34
|
-
|
|
25
|
+
const limit = parseInt(opts.limit || '10', 10);
|
|
35
26
|
const explorerConfig = EXPLORER_APIS[chain];
|
|
27
|
+
|
|
36
28
|
if (!explorerConfig) {
|
|
37
|
-
|
|
29
|
+
throw new Error(`No explorer API for chain: ${chain}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const url = `${explorerConfig.api}?module=account&action=txlist&address=${address}&startblock=0&endblock=99999999&page=1&offset=${limit}&sort=desc`;
|
|
33
|
+
const resp = await fetch(url);
|
|
34
|
+
const data = await resp.json();
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
name,
|
|
38
|
+
address,
|
|
39
|
+
chain,
|
|
40
|
+
explorer: explorerConfig.explorer,
|
|
41
|
+
status: data.status,
|
|
42
|
+
transactions: Array.isArray(data.result) ? data.result : [],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function showHistory(walletName, opts = {}) {
|
|
47
|
+
const name = walletName || getConfig('activeWallet');
|
|
48
|
+
if (!name) {
|
|
49
|
+
error('No wallet specified. Use: darksol wallet history <name>');
|
|
38
50
|
return;
|
|
39
51
|
}
|
|
40
52
|
|
|
53
|
+
const chain = opts.chain || getConfig('chain') || 'base';
|
|
41
54
|
const spin = spinner(`Fetching history on ${chain}...`).start();
|
|
42
55
|
|
|
43
56
|
try {
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
const resp = await fetch(url);
|
|
47
|
-
const data = await resp.json();
|
|
57
|
+
const snapshot = await fetchHistorySnapshot(name, opts);
|
|
58
|
+
const { address, chain: resolvedChain, explorer, status, transactions } = snapshot;
|
|
48
59
|
|
|
49
|
-
if (
|
|
60
|
+
if (status !== '1' || !transactions.length) {
|
|
50
61
|
spin.succeed('No transactions found');
|
|
51
|
-
info(`No recent transactions on ${
|
|
62
|
+
info(`No recent transactions on ${resolvedChain}`);
|
|
52
63
|
return;
|
|
53
64
|
}
|
|
54
65
|
|
|
55
|
-
spin.succeed(`Found ${
|
|
66
|
+
spin.succeed(`Found ${transactions.length} transactions`);
|
|
56
67
|
|
|
57
68
|
console.log('');
|
|
58
|
-
showSection(`HISTORY
|
|
69
|
+
showSection(`HISTORY - ${name} (${resolvedChain})`);
|
|
59
70
|
console.log(theme.dim(` ${address}`));
|
|
60
71
|
console.log('');
|
|
61
72
|
|
|
62
|
-
const rows =
|
|
73
|
+
const rows = transactions.map((tx) => {
|
|
63
74
|
const isOutgoing = tx.from.toLowerCase() === address.toLowerCase();
|
|
64
|
-
const direction = isOutgoing ? theme.accent('OUT
|
|
75
|
+
const direction = isOutgoing ? theme.accent('OUT ->') : theme.success('<- IN');
|
|
65
76
|
const value = parseFloat(tx.value) / 1e18;
|
|
66
77
|
const valueStr = value > 0 ? `${value.toFixed(4)} ETH` : theme.dim('0 ETH');
|
|
67
|
-
const
|
|
68
|
-
const date = new Date(parseInt(tx.timeStamp) * 1000);
|
|
78
|
+
const statusMark = tx.isError === '0' ? theme.success('OK') : theme.accent('XX');
|
|
79
|
+
const date = new Date(parseInt(tx.timeStamp, 10) * 1000);
|
|
69
80
|
const dateStr = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
70
81
|
const timeStr = date.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit' });
|
|
71
82
|
const counterparty = isOutgoing ? tx.to : tx.from;
|
|
72
|
-
const shortAddr = counterparty ? `${counterparty.slice(0, 6)}...${counterparty.slice(-4)}` : '
|
|
73
|
-
const method = tx.functionName ? tx.functionName.split('(')[0] : (value > 0 ? 'transfer' : '
|
|
74
|
-
|
|
75
|
-
return [
|
|
76
|
-
status,
|
|
77
|
-
direction,
|
|
78
|
-
valueStr,
|
|
79
|
-
shortAddr,
|
|
80
|
-
method.slice(0, 16),
|
|
81
|
-
`${dateStr} ${timeStr}`,
|
|
82
|
-
];
|
|
83
|
+
const shortAddr = counterparty ? `${counterparty.slice(0, 6)}...${counterparty.slice(-4)}` : '--';
|
|
84
|
+
const method = tx.functionName ? tx.functionName.split('(')[0] : (value > 0 ? 'transfer' : '--');
|
|
85
|
+
return [statusMark, direction, valueStr, shortAddr, method.slice(0, 16), `${dateStr} ${timeStr}`];
|
|
83
86
|
});
|
|
84
87
|
|
|
85
88
|
table(['', 'Dir', 'Value', 'Address', 'Method', 'Date'], rows);
|
|
86
|
-
|
|
87
89
|
console.log('');
|
|
88
|
-
info(`Explorer: ${
|
|
90
|
+
info(`Explorer: ${explorer}/address/${address}`);
|
|
89
91
|
console.log('');
|
|
90
|
-
|
|
91
92
|
} catch (err) {
|
|
92
93
|
spin.fail('Failed to fetch history');
|
|
93
94
|
error(err.message);
|
package/src/wallet/portfolio.js
CHANGED
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
import { ethers } from 'ethers';
|
|
2
|
-
import { loadWallet
|
|
2
|
+
import { loadWallet } from './keystore.js';
|
|
3
3
|
import { getConfig, getRPC } from '../config/store.js';
|
|
4
4
|
import { theme } from '../ui/theme.js';
|
|
5
|
-
import { spinner, kvDisplay,
|
|
5
|
+
import { spinner, kvDisplay, error, info, table } from '../ui/components.js';
|
|
6
6
|
import { showSection } from '../ui/banner.js';
|
|
7
7
|
|
|
8
|
-
// ══════════════════════════════════════════════════
|
|
9
|
-
// MULTI-CHAIN PORTFOLIO VIEW
|
|
10
|
-
// ══════════════════════════════════════════════════
|
|
11
|
-
|
|
12
8
|
const CHAINS = {
|
|
13
|
-
base:
|
|
14
|
-
ethereum: { name: 'Ethereum', rpc: 'https://eth.llamarpc.com',
|
|
15
|
-
arbitrum: { name: 'Arbitrum', rpc: 'https://arb1.arbitrum.io/rpc',
|
|
16
|
-
optimism: { name: 'Optimism', rpc: 'https://mainnet.optimism.io',
|
|
17
|
-
polygon:
|
|
9
|
+
base: { name: 'Base', rpc: 'https://mainnet.base.org', usdc: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', explorer: 'https://basescan.org' },
|
|
10
|
+
ethereum: { name: 'Ethereum', rpc: 'https://eth.llamarpc.com', usdc: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', explorer: 'https://etherscan.io' },
|
|
11
|
+
arbitrum: { name: 'Arbitrum', rpc: 'https://arb1.arbitrum.io/rpc', usdc: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', explorer: 'https://arbiscan.io' },
|
|
12
|
+
optimism: { name: 'Optimism', rpc: 'https://mainnet.optimism.io', usdc: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', explorer: 'https://optimistic.etherscan.io' },
|
|
13
|
+
polygon: { name: 'Polygon', rpc: 'https://polygon-rpc.com', usdc: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', explorer: 'https://polygonscan.com' },
|
|
18
14
|
};
|
|
19
15
|
|
|
20
16
|
const ERC20_ABI = [
|
|
@@ -22,68 +18,50 @@ const ERC20_ABI = [
|
|
|
22
18
|
'function decimals() view returns (uint8)',
|
|
23
19
|
];
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
* Show portfolio across all EVM chains
|
|
27
|
-
*/
|
|
28
|
-
export async function showPortfolio(walletName, opts = {}) {
|
|
21
|
+
export async function fetchPortfolioSnapshot(walletName) {
|
|
29
22
|
const name = walletName || getConfig('activeWallet');
|
|
30
23
|
if (!name) {
|
|
31
|
-
|
|
32
|
-
return;
|
|
24
|
+
throw new Error('No wallet specified. Use: darksol wallet portfolio <name>');
|
|
33
25
|
}
|
|
34
26
|
|
|
35
27
|
const walletData = loadWallet(name);
|
|
36
28
|
const address = walletData.address;
|
|
37
29
|
|
|
38
|
-
console.log('');
|
|
39
|
-
showSection(`PORTFOLIO — ${name}`);
|
|
40
|
-
console.log(theme.dim(` ${address}`));
|
|
41
|
-
console.log('');
|
|
42
|
-
|
|
43
|
-
const spin = spinner('Scanning all chains...').start();
|
|
44
|
-
const results = [];
|
|
45
|
-
let totalUSD = 0;
|
|
46
|
-
|
|
47
|
-
// Fetch ETH price for USD conversion
|
|
48
30
|
let ethPrice = 0;
|
|
49
31
|
try {
|
|
50
32
|
const fetch = (await import('node-fetch')).default;
|
|
51
33
|
const priceResp = await fetch('https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd');
|
|
52
34
|
const priceData = await priceResp.json();
|
|
53
35
|
ethPrice = priceData.ethereum?.usd || 0;
|
|
54
|
-
} catch {
|
|
36
|
+
} catch {
|
|
37
|
+
ethPrice = 3000;
|
|
38
|
+
}
|
|
55
39
|
|
|
56
|
-
|
|
57
|
-
const chainPromises = Object.entries(CHAINS).map(async ([chainId, chain]) => {
|
|
40
|
+
const chains = await Promise.all(Object.entries(CHAINS).map(async ([chainId, chain]) => {
|
|
58
41
|
try {
|
|
59
42
|
const rpc = getRPC(chainId) || chain.rpc;
|
|
60
43
|
const provider = new ethers.JsonRpcProvider(rpc);
|
|
61
|
-
|
|
62
|
-
// ETH balance
|
|
63
44
|
const balance = await provider.getBalance(address);
|
|
64
|
-
const
|
|
45
|
+
const eth = parseFloat(ethers.formatEther(balance));
|
|
65
46
|
|
|
66
|
-
|
|
67
|
-
let usdcBal = 0;
|
|
47
|
+
let usdc = 0;
|
|
68
48
|
if (chain.usdc) {
|
|
69
49
|
try {
|
|
70
|
-
const
|
|
71
|
-
const raw = await
|
|
72
|
-
const decimals = await
|
|
73
|
-
|
|
74
|
-
} catch {
|
|
50
|
+
const usdcContract = new ethers.Contract(chain.usdc, ERC20_ABI, provider);
|
|
51
|
+
const raw = await usdcContract.balanceOf(address);
|
|
52
|
+
const decimals = await usdcContract.decimals();
|
|
53
|
+
usdc = parseFloat(ethers.formatUnits(raw, decimals));
|
|
54
|
+
} catch {}
|
|
75
55
|
}
|
|
76
56
|
|
|
77
|
-
const ethUSD =
|
|
78
|
-
const chainTotal = ethUSD + usdcBal;
|
|
79
|
-
|
|
57
|
+
const ethUSD = eth * ethPrice;
|
|
80
58
|
return {
|
|
81
59
|
chain: chain.name,
|
|
82
60
|
chainId,
|
|
83
|
-
eth
|
|
84
|
-
usdc
|
|
61
|
+
eth,
|
|
62
|
+
usdc,
|
|
85
63
|
ethUSD,
|
|
86
|
-
total:
|
|
64
|
+
total: ethUSD + usdc,
|
|
87
65
|
explorer: chain.explorer,
|
|
88
66
|
};
|
|
89
67
|
} catch (err) {
|
|
@@ -94,58 +72,68 @@ export async function showPortfolio(walletName, opts = {}) {
|
|
|
94
72
|
usdc: 0,
|
|
95
73
|
ethUSD: 0,
|
|
96
74
|
total: 0,
|
|
75
|
+
explorer: chain.explorer,
|
|
97
76
|
error: err.message,
|
|
98
77
|
};
|
|
99
78
|
}
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
const chainResults = await Promise.all(chainPromises);
|
|
103
|
-
spin.succeed('Scan complete');
|
|
104
|
-
|
|
105
|
-
// Build table
|
|
106
|
-
const rows = chainResults.map(r => {
|
|
107
|
-
const ethStr = r.eth > 0 ? `${r.eth.toFixed(6)} ETH` : theme.dim('0');
|
|
108
|
-
const usdcStr = r.usdc > 0 ? `$${r.usdc.toFixed(2)}` : theme.dim('$0');
|
|
109
|
-
const totalStr = r.total > 0.01 ? theme.gold(`$${r.total.toFixed(2)}`) : theme.dim('$0');
|
|
110
|
-
const status = r.error ? theme.accent('⚠') : (r.total > 0 ? theme.success('●') : theme.dim('○'));
|
|
111
|
-
totalUSD += r.total;
|
|
112
|
-
|
|
113
|
-
return [
|
|
114
|
-
`${status} ${r.chain}`,
|
|
115
|
-
ethStr,
|
|
116
|
-
usdcStr,
|
|
117
|
-
totalStr,
|
|
118
|
-
];
|
|
119
|
-
});
|
|
79
|
+
}));
|
|
120
80
|
|
|
121
|
-
|
|
122
|
-
|
|
81
|
+
const totalUSD = chains.reduce((sum, item) => sum + item.total, 0);
|
|
82
|
+
return { name, address, chains, totalUSD, ethPrice };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function showPortfolio(walletName) {
|
|
86
|
+
const name = walletName || getConfig('activeWallet');
|
|
87
|
+
if (!name) {
|
|
88
|
+
error('No wallet specified. Use: darksol wallet portfolio <name>');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
123
91
|
|
|
124
|
-
// Summary
|
|
125
92
|
console.log('');
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
93
|
+
showSection(`PORTFOLIO - ${name}`);
|
|
94
|
+
const spin = spinner('Scanning all chains...').start();
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const snapshot = await fetchPortfolioSnapshot(name);
|
|
98
|
+
const { address, chains, totalUSD, ethPrice } = snapshot;
|
|
99
|
+
spin.succeed('Scan complete');
|
|
100
|
+
|
|
101
|
+
console.log(theme.dim(` ${address}`));
|
|
135
102
|
console.log('');
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
103
|
+
|
|
104
|
+
const rows = chains.map((item) => {
|
|
105
|
+
const ethStr = item.eth > 0 ? `${item.eth.toFixed(6)} ETH` : theme.dim('0');
|
|
106
|
+
const usdcStr = item.usdc > 0 ? `$${item.usdc.toFixed(2)}` : theme.dim('$0');
|
|
107
|
+
const totalStr = item.total > 0.01 ? theme.gold(`$${item.total.toFixed(2)}`) : theme.dim('$0');
|
|
108
|
+
const status = item.error ? theme.accent('!') : (item.total > 0 ? theme.success('*') : theme.dim('o'));
|
|
109
|
+
return [`${status} ${item.chain}`, ethStr, usdcStr, totalStr];
|
|
139
110
|
});
|
|
140
|
-
}
|
|
141
111
|
|
|
142
|
-
|
|
143
|
-
|
|
112
|
+
table(['Chain', 'ETH', 'USDC', 'Total USD'], rows);
|
|
113
|
+
console.log('');
|
|
114
|
+
kvDisplay([
|
|
115
|
+
['Total Value', theme.gold(`$${totalUSD.toFixed(2)}`)],
|
|
116
|
+
['ETH Price', `$${ethPrice.toFixed(2)}`],
|
|
117
|
+
['Chains', `${chains.filter((item) => !item.error).length}/${Object.keys(CHAINS).length} connected`],
|
|
118
|
+
]);
|
|
119
|
+
|
|
120
|
+
const withBalance = chains.filter((item) => item.total > 0.01);
|
|
121
|
+
if (withBalance.length > 0) {
|
|
122
|
+
console.log('');
|
|
123
|
+
info('Explorer links:');
|
|
124
|
+
withBalance.forEach((item) => {
|
|
125
|
+
console.log(theme.dim(` ${item.chain}: ${item.explorer}/address/${address}`));
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log('');
|
|
130
|
+
return { address, chains, totalUSD, ethPrice };
|
|
131
|
+
} catch (err) {
|
|
132
|
+
spin.fail('Scan failed');
|
|
133
|
+
error(err.message);
|
|
134
|
+
}
|
|
144
135
|
}
|
|
145
136
|
|
|
146
|
-
/**
|
|
147
|
-
* Quick balance check (non-verbose, for status bar)
|
|
148
|
-
*/
|
|
149
137
|
export async function quickBalance(walletName) {
|
|
150
138
|
const name = walletName || getConfig('activeWallet');
|
|
151
139
|
if (!name) return null;
|
package/src/web/commands.js
CHANGED
|
@@ -902,6 +902,8 @@ export async function handleCommand(cmd, ws) {
|
|
|
902
902
|
return await cmdMarket(args, ws);
|
|
903
903
|
case 'trade':
|
|
904
904
|
return await cmdTrade(args, ws);
|
|
905
|
+
case 'arb':
|
|
906
|
+
return await cmdArb(args, ws);
|
|
905
907
|
case 'bridge':
|
|
906
908
|
return await cmdBridge(args, ws);
|
|
907
909
|
case 'wallet':
|
|
@@ -1351,6 +1353,59 @@ async function cmdTrade(args, ws) {
|
|
|
1351
1353
|
return {};
|
|
1352
1354
|
}
|
|
1353
1355
|
|
|
1356
|
+
// ══════════════════════════════════════════════════
|
|
1357
|
+
// ARBITRAGE
|
|
1358
|
+
// ══════════════════════════════════════════════════
|
|
1359
|
+
async function cmdArb(args, ws) {
|
|
1360
|
+
const sub = (args[0] || '').toLowerCase();
|
|
1361
|
+
|
|
1362
|
+
if (sub === 'scan') {
|
|
1363
|
+
ws.sendLine(` ${ANSI.dim}Running arb scan...${ANSI.reset}`);
|
|
1364
|
+
try {
|
|
1365
|
+
const { arbScan } = await import('../trading/arb.js');
|
|
1366
|
+
await arbScan({ chain: args[1] || getConfig('chain') || 'base' });
|
|
1367
|
+
} catch (e) {
|
|
1368
|
+
ws.sendLine(` ${ANSI.red}Scan failed: ${e.message}${ANSI.reset}`);
|
|
1369
|
+
}
|
|
1370
|
+
return {};
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
if (sub === 'stats') {
|
|
1374
|
+
try {
|
|
1375
|
+
const { arbStats } = await import('../trading/arb.js');
|
|
1376
|
+
await arbStats({ days: args[1] || '7' });
|
|
1377
|
+
} catch (e) {
|
|
1378
|
+
ws.sendLine(` ${ANSI.red}Stats failed: ${e.message}${ANSI.reset}`);
|
|
1379
|
+
}
|
|
1380
|
+
return {};
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
if (sub === 'info') {
|
|
1384
|
+
try {
|
|
1385
|
+
const { arbInfo } = await import('../trading/arb.js');
|
|
1386
|
+
await arbInfo();
|
|
1387
|
+
} catch (e) {
|
|
1388
|
+
ws.sendLine(` ${ANSI.red}${e.message}${ANSI.reset}`);
|
|
1389
|
+
}
|
|
1390
|
+
return {};
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// Default: show arb menu
|
|
1394
|
+
ws.sendLine(`${ANSI.gold} ◆ ARBITRAGE${ANSI.reset}`);
|
|
1395
|
+
ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
|
|
1396
|
+
ws.sendLine(` ${ANSI.white}Cross-DEX arbitrage scanner${ANSI.reset}`);
|
|
1397
|
+
ws.sendLine('');
|
|
1398
|
+
|
|
1399
|
+
ws.sendMenu('arb_action', '◆ Arb Actions', [
|
|
1400
|
+
{ value: 'arb scan', label: '🔍 Scan', desc: 'One-shot DEX price comparison' },
|
|
1401
|
+
{ value: 'arb stats', label: '📊 Stats', desc: 'View arb history & PnL' },
|
|
1402
|
+
{ value: 'arb info', label: '📖 Guide', desc: 'How arb works, setup tips, risks' },
|
|
1403
|
+
{ value: 'back', label: '← Back', desc: '' },
|
|
1404
|
+
]);
|
|
1405
|
+
|
|
1406
|
+
return {};
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1354
1409
|
// ══════════════════════════════════════════════════
|
|
1355
1410
|
// BRIDGE (LI.FI cross-chain)
|
|
1356
1411
|
// ══════════════════════════════════════════════════
|