aether-hub 1.0.5 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/network.js +503 -0
- package/commands/wallet.js +194 -23
- package/index.js +44 -7
- package/package.json +1 -1
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* aether-cli network - Aether Network Status
|
|
4
|
+
*
|
|
5
|
+
* Queries the Aether network for broad health metrics:
|
|
6
|
+
* - Network-wide slot, block height, slot production
|
|
7
|
+
* - Connected peers and their info
|
|
8
|
+
* - Consensus status and epoch info
|
|
9
|
+
* - TPS estimates from the network
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* aether-cli network # Interactive summary view
|
|
13
|
+
* aether-cli network --json # JSON output for scripting
|
|
14
|
+
* aether-cli network --rpc <url> # Query a specific RPC endpoint
|
|
15
|
+
* aether-cli network --peers # Detailed peer list
|
|
16
|
+
* aether-cli network --epoch # Current epoch and consensus info
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const http = require('http');
|
|
20
|
+
const https = require('https');
|
|
21
|
+
|
|
22
|
+
// ANSI colours
|
|
23
|
+
const C = {
|
|
24
|
+
reset: '\x1b[0m',
|
|
25
|
+
bright: '\x1b[1m',
|
|
26
|
+
dim: '\x1b[2m',
|
|
27
|
+
red: '\x1b[31m',
|
|
28
|
+
green: '\x1b[32m',
|
|
29
|
+
yellow: '\x1b[33m',
|
|
30
|
+
blue: '\x1b[34m',
|
|
31
|
+
cyan: '\x1b[36m',
|
|
32
|
+
magenta: '\x1b[35m',
|
|
33
|
+
white: '\x1b[37m',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const DEFAULT_RPC = process.env.AETHER_RPC || 'http://127.0.0.1:8899';
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// HTTP helpers
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
function httpRequest(rpcUrl, path, options = {}) {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
const url = new URL(path, rpcUrl);
|
|
45
|
+
const isHttps = url.protocol === 'https:';
|
|
46
|
+
const lib = isHttps ? https : http;
|
|
47
|
+
|
|
48
|
+
const reqOptions = {
|
|
49
|
+
hostname: url.hostname,
|
|
50
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
51
|
+
path: url.pathname + url.search,
|
|
52
|
+
method: 'GET',
|
|
53
|
+
timeout: 5000,
|
|
54
|
+
headers: { 'Content-Type': 'application/json' },
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const req = lib.request(reqOptions, (res) => {
|
|
58
|
+
let data = '';
|
|
59
|
+
res.on('data', (chunk) => (data += chunk));
|
|
60
|
+
res.on('end', () => {
|
|
61
|
+
try {
|
|
62
|
+
resolve(JSON.parse(data));
|
|
63
|
+
} catch {
|
|
64
|
+
resolve({ raw: data });
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
req.on('error', reject);
|
|
70
|
+
req.on('timeout', () => {
|
|
71
|
+
req.destroy();
|
|
72
|
+
reject(new Error('Request timeout'));
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
req.end();
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function httpPost(rpcUrl, path, body) {
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
const url = new URL(path, rpcUrl);
|
|
82
|
+
const isHttps = url.protocol === 'https:';
|
|
83
|
+
const lib = isHttps ? https : http;
|
|
84
|
+
const bodyStr = JSON.stringify(body);
|
|
85
|
+
|
|
86
|
+
const req = lib.request({
|
|
87
|
+
hostname: url.hostname,
|
|
88
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
89
|
+
path: url.pathname + url.search,
|
|
90
|
+
method: 'POST',
|
|
91
|
+
timeout: 5000,
|
|
92
|
+
headers: {
|
|
93
|
+
'Content-Type': 'application/json',
|
|
94
|
+
'Content-Length': Buffer.byteLength(bodyStr),
|
|
95
|
+
},
|
|
96
|
+
}, (res) => {
|
|
97
|
+
let data = '';
|
|
98
|
+
res.on('data', (chunk) => (data += chunk));
|
|
99
|
+
res.on('end', () => {
|
|
100
|
+
try { resolve(JSON.parse(data)); }
|
|
101
|
+
catch { resolve(data); }
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
req.on('error', reject);
|
|
106
|
+
req.on('timeout', () => {
|
|
107
|
+
req.destroy();
|
|
108
|
+
reject(new Error('Request timeout'));
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
req.write(bodyStr);
|
|
112
|
+
req.end();
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// Argument parsing
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
function parseArgs() {
|
|
121
|
+
const args = process.argv.slice(2);
|
|
122
|
+
const options = {
|
|
123
|
+
rpc: DEFAULT_RPC,
|
|
124
|
+
showPeers: false,
|
|
125
|
+
showEpoch: false,
|
|
126
|
+
asJson: false,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
for (let i = 0; i < args.length; i++) {
|
|
130
|
+
if (args[i] === '--rpc' || args[i] === '-r') {
|
|
131
|
+
options.rpc = args[++i];
|
|
132
|
+
} else if (args[i] === '--peers' || args[i] === '-p') {
|
|
133
|
+
options.showPeers = true;
|
|
134
|
+
} else if (args[i] === '--epoch' || args[i] === '-e') {
|
|
135
|
+
options.showEpoch = true;
|
|
136
|
+
} else if (args[i] === '--json' || args[i] === '-j') {
|
|
137
|
+
options.asJson = true;
|
|
138
|
+
} else if (args[i] === '--help' || args[i] === '-h') {
|
|
139
|
+
showHelp();
|
|
140
|
+
process.exit(0);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return options;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function showHelp() {
|
|
148
|
+
console.log(`
|
|
149
|
+
${C.bright}${C.cyan}aether-cli network${C.reset} - Aether Network Status
|
|
150
|
+
|
|
151
|
+
${C.bright}Usage:${C.reset}
|
|
152
|
+
aether-cli network [options]
|
|
153
|
+
|
|
154
|
+
${C.bright}Options:${C.reset}
|
|
155
|
+
-r, --rpc <url> RPC endpoint (default: ${DEFAULT_RPC} or $AETHER_RPC)
|
|
156
|
+
-p, --peers Show detailed peer list
|
|
157
|
+
-e, --epoch Show epoch and consensus information
|
|
158
|
+
-j, --json Output raw JSON (good for scripting)
|
|
159
|
+
-h, --help Show this help message
|
|
160
|
+
|
|
161
|
+
${C.bright}Examples:${C.reset}
|
|
162
|
+
aether-cli network # Summary view
|
|
163
|
+
aether-cli network --json # JSON output
|
|
164
|
+
aether-cli network --rpc http://api.testnet.aether.network
|
|
165
|
+
aether-cli network --peers # Detailed peer list
|
|
166
|
+
aether-cli network --epoch # Epoch/consensus info
|
|
167
|
+
`.trim());
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
// Network data fetchers
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
/** GET /v1/slot — current network slot */
|
|
175
|
+
async function getSlot(rpc) {
|
|
176
|
+
try {
|
|
177
|
+
const res = await httpRequest(rpc, '/v1/slot');
|
|
178
|
+
return res.slot ?? res.root_slot ?? null;
|
|
179
|
+
} catch {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** GET /v1/block_height — current network block height */
|
|
185
|
+
async function getBlockHeight(rpc) {
|
|
186
|
+
try {
|
|
187
|
+
const res = await httpRequest(rpc, '/v1/block_height');
|
|
188
|
+
return res.block_height ?? null;
|
|
189
|
+
} catch {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** GET /v1/validators — list of validators / peers */
|
|
195
|
+
async function getValidators(rpc) {
|
|
196
|
+
try {
|
|
197
|
+
const res = await httpRequest(rpc, '/v1/validators');
|
|
198
|
+
if (res.validators && Array.isArray(res.validators)) {
|
|
199
|
+
return res.validators;
|
|
200
|
+
}
|
|
201
|
+
if (Array.isArray(res)) return res;
|
|
202
|
+
return [];
|
|
203
|
+
} catch {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** GET /v1/epoch — current epoch and consensus info */
|
|
209
|
+
async function getEpoch(rpc) {
|
|
210
|
+
try {
|
|
211
|
+
const res = await httpRequest(rpc, '/v1/epoch');
|
|
212
|
+
return res;
|
|
213
|
+
} catch {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** POST /v1/slot_production — slot production stats */
|
|
219
|
+
async function getSlotProduction(rpc) {
|
|
220
|
+
try {
|
|
221
|
+
// Try slot production endpoint
|
|
222
|
+
const res = await httpPost(rpc, '/v1/slot_production', {});
|
|
223
|
+
return res;
|
|
224
|
+
} catch {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** GET /v1/tps — TPS estimate from network */
|
|
230
|
+
async function getTPS(rpc) {
|
|
231
|
+
try {
|
|
232
|
+
const res = await httpRequest(rpc, '/v1/tps');
|
|
233
|
+
return res.tps ?? res.tps_avg ?? res.transactions_per_second ?? null;
|
|
234
|
+
} catch {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** GET /v1/supply — token supply info */
|
|
240
|
+
async function getSupply(rpc) {
|
|
241
|
+
try {
|
|
242
|
+
const res = await httpRequest(rpc, '/v1/supply');
|
|
243
|
+
return res;
|
|
244
|
+
} catch {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
// Output formatters
|
|
251
|
+
// ---------------------------------------------------------------------------
|
|
252
|
+
|
|
253
|
+
function formatNumber(n) {
|
|
254
|
+
if (n === null || n === undefined) return 'N/A';
|
|
255
|
+
return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function peerHealthIcon(score) {
|
|
259
|
+
if (score === undefined || score === null) return `${C.dim}●${C.reset}`;
|
|
260
|
+
if (score >= 80) return `${C.green}●${C.reset}`;
|
|
261
|
+
if (score >= 50) return `${C.yellow}●${C.reset}`;
|
|
262
|
+
return `${C.red}●${C.reset}`;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function uptimeString(seconds) {
|
|
266
|
+
if (!seconds) return `${C.dim}unknown${C.reset}`;
|
|
267
|
+
const d = Math.floor(seconds / 86400);
|
|
268
|
+
const h = Math.floor((seconds % 86400) / 3600);
|
|
269
|
+
const m = Math.floor((seconds % 3600) / 60);
|
|
270
|
+
const parts = [];
|
|
271
|
+
if (d > 0) parts.push(`${d}d`);
|
|
272
|
+
if (h > 0) parts.push(`${h}h`);
|
|
273
|
+
if (m > 0) parts.push(`${m}m`);
|
|
274
|
+
return parts.length > 0 ? parts.join(' ') : `${seconds}s`;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
// Main renderers
|
|
279
|
+
// ---------------------------------------------------------------------------
|
|
280
|
+
|
|
281
|
+
function renderSummary(data, rpc) {
|
|
282
|
+
const { slot, blockHeight, peerCount, tps, supply, epochData } = data;
|
|
283
|
+
const now = new Date().toLocaleTimeString();
|
|
284
|
+
|
|
285
|
+
console.log(`
|
|
286
|
+
${C.bright}${C.cyan}╔═══════════════════════════════════════════════════════════════════╗${C.reset}
|
|
287
|
+
${C.bright}${C.cyan}║${C.reset} ${C.bright}AETHER NETWORK STATUS${C.reset}${C.cyan} ║${C.reset}
|
|
288
|
+
${C.bright}${C.cyan}╚═══════════════════════════════════════════════════════════════════╝${C.reset}
|
|
289
|
+
`);
|
|
290
|
+
console.log(` ${C.dim}RPC:${C.reset} ${rpc}`);
|
|
291
|
+
console.log(` ${C.dim}Updated:${C.reset} ${now}`);
|
|
292
|
+
console.log();
|
|
293
|
+
|
|
294
|
+
// Network health
|
|
295
|
+
const isHealthy = slot !== null && blockHeight !== null;
|
|
296
|
+
const healthIcon = isHealthy ? `${C.green}● HEALTHY${C.reset}` : `${C.red}● UNHEALTHY${C.reset}`;
|
|
297
|
+
console.log(` Network ${healthIcon}`);
|
|
298
|
+
console.log();
|
|
299
|
+
|
|
300
|
+
// Key metrics
|
|
301
|
+
console.log(` ${C.bright}┌─────────────────────────────────────────────────────────────────┐${C.reset}`);
|
|
302
|
+
console.log(` ${C.bright}│${C.reset} ${C.cyan}Current Slot${C.reset} ${C.bright}│${C.reset} ${C.green}${formatNumber(slot).padEnd(20)}${C.reset}${C.bright}│${C.reset}`);
|
|
303
|
+
console.log(` ${C.bright}│${C.reset} ${C.cyan}Block Height${C.reset} ${C.bright}│${C.reset} ${C.blue}${formatNumber(blockHeight).padEnd(20)}${C.reset}${C.bright}│${C.reset}`);
|
|
304
|
+
console.log(` ${C.bright}│${C.reset} ${C.cyan}Active Peers${C.reset} ${C.bright}│${C.reset} ${C.magenta}${formatNumber(peerCount).padEnd(20)}${C.reset}${C.bright}│${C.reset}`);
|
|
305
|
+
console.log(` ${C.bright}│${C.reset} ${C.cyan}Network TPS${C.reset} ${C.bright}│${C.reset} ${tps !== null ? (tps > 0 ? `${C.green}` : `${C.yellow}`) + tps.toFixed(2).padEnd(20) : `${C.dim}N/A`.padEnd(20)}${C.reset}${C.bright}│${C.reset}`);
|
|
306
|
+
console.log(` ${C.bright}└─────────────────────────────────────────────────────────────────┘${C.reset}`);
|
|
307
|
+
console.log();
|
|
308
|
+
|
|
309
|
+
// Epoch info if available
|
|
310
|
+
if (epochData && (epochData.epoch !== undefined || epochData.absolute_slot !== undefined)) {
|
|
311
|
+
console.log(` ${C.bright}── Epoch / Consensus ──────────────────────────────────────────${C.reset}`);
|
|
312
|
+
const ep = epochData.epoch !== undefined ? epochData.epoch : '?';
|
|
313
|
+
const slotIndex = epochData.slot_index !== undefined ? epochData.slot_index : '?';
|
|
314
|
+
const slotsInEpoch = epochData.slots_in_epoch !== undefined ? epochData.slots_in_epoch : '?';
|
|
315
|
+
const progress = slotsInEpoch !== '?' && slotsInEpoch > 0
|
|
316
|
+
? ((slotIndex / slotsInEpoch) * 100).toFixed(1) + '%'
|
|
317
|
+
: '?';
|
|
318
|
+
|
|
319
|
+
console.log(` ${C.dim}Epoch:${C.reset} ${C.bright}${ep}${C.reset} ${C.dim}Slot in epoch:${C.reset} ${slotIndex}/${slotsInEpoch} ${C.dim}(${progress})${C.reset}`);
|
|
320
|
+
if (epochData.absolute_slot !== undefined) {
|
|
321
|
+
console.log(` ${C.dim}Absolute slot:${C.reset} ${formatNumber(epochData.absolute_slot)}`);
|
|
322
|
+
}
|
|
323
|
+
if (epochData.block_height !== undefined) {
|
|
324
|
+
console.log(` ${C.dim}Block height:${C.reset} ${formatNumber(epochData.block_height)}`);
|
|
325
|
+
}
|
|
326
|
+
console.log();
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Supply if available
|
|
330
|
+
if (supply && !supply.error) {
|
|
331
|
+
console.log(` ${C.bright}── Token Supply ───────────────────────────────────────────────${C.reset}`);
|
|
332
|
+
if (supply.total !== undefined) {
|
|
333
|
+
const totalAETH = (supply.total / 1e9).toFixed(2);
|
|
334
|
+
console.log(` ${C.dim}Total supply:${C.reset} ${C.green}${formatNumber(supply.total)} lamports${C.reset} ${C.dim}(${totalAETH} AETH)${C.reset}`);
|
|
335
|
+
}
|
|
336
|
+
if (supply.circulating !== undefined) {
|
|
337
|
+
const circAETH = (supply.circulating / 1e9).toFixed(2);
|
|
338
|
+
console.log(` ${C.dim}Circulating:${C.reset} ${formatNumber(supply.circulating)} lamports ${C.dim}(${circAETH} AETH)${C.reset}`);
|
|
339
|
+
}
|
|
340
|
+
console.log();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
console.log(` ${C.dim}Tip: --peers for peer list | --epoch for consensus | --json for raw data${C.reset}`);
|
|
344
|
+
console.log();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function renderPeers(peers, rpc) {
|
|
348
|
+
console.log();
|
|
349
|
+
console.log(`${C.bright}${C.cyan}── Peer List ─────────────────────────────────────────────────${C.reset}`);
|
|
350
|
+
console.log(` ${C.dim}RPC: ${rpc}${C.reset}`);
|
|
351
|
+
console.log();
|
|
352
|
+
|
|
353
|
+
if (!peers || peers.length === 0) {
|
|
354
|
+
console.log(` ${C.yellow}⚠ No peer information available from this RPC.${C.reset}`);
|
|
355
|
+
console.log(` ${C.dim} Peers may not be exposed by your validator's RPC configuration.${C.reset}`);
|
|
356
|
+
console.log();
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
console.log(` ${C.bright}┌────────────────────────────────────────────────────────────────────────┐${C.reset}`);
|
|
361
|
+
console.log(` ${C.bright}│${C.reset} ${C.cyan}#${C.reset} ${C.cyan}Validator Address${C.reset} ${C.cyan}Tier${C.reset} ${C.cyan}Score${C.reset} ${C.cyan}Uptime${C.reset} ${C.bright}│${C.reset}`);
|
|
362
|
+
console.log(` ${C.bright}├${C.reset}${'-'.repeat(78)}${C.bright}│${C.reset}`);
|
|
363
|
+
|
|
364
|
+
peers.slice(0, 50).forEach((peer, i) => {
|
|
365
|
+
const num = (i + 1).toString().padStart(2);
|
|
366
|
+
const addr = (peer.address || peer.pubkey || peer.id || 'unknown').slice(0, 32).padEnd(34);
|
|
367
|
+
const tier = (peer.tier || peer.node_type || '?').toUpperCase().padEnd(6).slice(0, 6);
|
|
368
|
+
const score = peer.score !== undefined ? peer.score : (peer.uptime !== undefined ? Math.round(peer.uptime * 100) : null);
|
|
369
|
+
const scoreStr = score !== null ? `${score}%` : '?';
|
|
370
|
+
const uptime = uptimeString(peer.uptime_seconds || peer.uptime);
|
|
371
|
+
const scoreColor = score === null ? C.dim : score >= 80 ? C.green : score >= 50 ? C.yellow : C.red;
|
|
372
|
+
|
|
373
|
+
console.log(
|
|
374
|
+
` ${C.bright}│${C.reset} ${C.dim}${num}${C.reset} ${addr} ${tier.padEnd(6)} ${scoreColor}${(scoreStr + '%').padEnd(7)}${C.reset} ${C.dim}${uptime}${C.reset} ${C.bright}│${C.reset}`
|
|
375
|
+
);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
if (peers.length > 50) {
|
|
379
|
+
console.log(` ${C.bright}│${C.reset} ${C.dim}... and ${peers.length - 50} more peers (use --json for full list)${C.reset}`.padEnd(80) + `${C.bright}│${C.reset}`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
console.log(` ${C.bright}└────────────────────────────────────────────────────────────────────────┘${C.reset}`);
|
|
383
|
+
console.log();
|
|
384
|
+
console.log(` ${C.dim}Total peers: ${peers.length}${C.reset}`);
|
|
385
|
+
console.log();
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function renderEpoch(epochData, rpc) {
|
|
389
|
+
console.log();
|
|
390
|
+
console.log(`${C.bright}${C.cyan}── Epoch / Consensus ────────────────────────────────────────${C.reset}`);
|
|
391
|
+
console.log(` ${C.dim}RPC: ${rpc}${C.reset}`);
|
|
392
|
+
console.log();
|
|
393
|
+
|
|
394
|
+
if (!epochData) {
|
|
395
|
+
console.log(` ${C.yellow}⚠ Epoch information not available.${C.reset}`);
|
|
396
|
+
console.log(` ${C.dim} Is your validator fully synced?${C.reset}`);
|
|
397
|
+
console.log();
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const t = (label, val) => console.log(` ${C.dim}${label}:${C.reset} ${val !== undefined && val !== null ? C.bright + val : C.dim + 'N/A'}${C.reset}`);
|
|
402
|
+
|
|
403
|
+
console.log(` ${C.bright}┌─────────────────────────────────────────────────────┐${C.reset}`);
|
|
404
|
+
console.log(` ${C.bright}│${C.reset} ${C.cyan}Epoch${C.reset}${' '.repeat(45)}${C.bright}│${C.reset}`);
|
|
405
|
+
const ep = epochData.epoch !== undefined ? epochData.epoch : '?';
|
|
406
|
+
console.log(` ${C.bright}│${C.reset} ${C.green}${(ep + '').padEnd(53)}${C.reset}${C.bright}│${C.reset}`);
|
|
407
|
+
console.log(` ${C.bright}├${C.reset}${'─'.repeat(58)}${C.bright}│${C.reset}`);
|
|
408
|
+
|
|
409
|
+
if (epochData.slots_in_epoch !== undefined) {
|
|
410
|
+
console.log(` ${C.bright}│${C.reset} ${C.dim}Slots in epoch${C.reset}`.padEnd(53) + `${C.bright}│${C.reset}`);
|
|
411
|
+
console.log(` ${C.bright}│${C.reset} ${C.bright}${formatNumber(epochData.slots_in_epoch).padEnd(53)}${C.reset}${C.bright}│${C.reset}`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (epochData.slot_index !== undefined) {
|
|
415
|
+
console.log(` ${C.bright}│${C.reset} ${C.dim}Current slot in epoch${C.reset}`.padEnd(53) + `${C.bright}│${C.reset}`);
|
|
416
|
+
const progress = epochData.slots_in_epoch > 0
|
|
417
|
+
? `${epochData.slot_index} / ${epochData.slots_in_epoch} (${((epochData.slot_index / epochData.slots_in_epoch) * 100).toFixed(1)}%)`
|
|
418
|
+
: `${epochData.slot_index}`;
|
|
419
|
+
console.log(` ${C.bright}│${C.reset} ${C.bright}${progress.padEnd(53)}${C.reset}${C.bright}│${C.reset}`);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (epochData.absolute_slot !== undefined) {
|
|
423
|
+
console.log(` ${C.bright}│${C.reset} ${C.dim}Absolute slot${C.reset}`.padEnd(53) + `${C.bright}│${C.reset}`);
|
|
424
|
+
console.log(` ${C.bright}│${C.reset} ${C.bright}${formatNumber(epochData.absolute_slot).padEnd(53)}${C.reset}${C.bright}│${C.reset}`);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (epochData.block_height !== undefined) {
|
|
428
|
+
console.log(` ${C.bright}│${C.reset} ${C.dim}Block height${C.reset}`.padEnd(53) + `${C.bright}│${C.reset}`);
|
|
429
|
+
console.log(` ${C.bright}│${C.reset} ${C.bright}${formatNumber(epochData.block_height).padEnd(53)}${C.reset}${C.bright}│${C.reset}`);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (epochData.epoch_schedule) {
|
|
433
|
+
const es = epochData.epoch_schedule;
|
|
434
|
+
if (es.first_normal_epoch !== undefined) {
|
|
435
|
+
console.log(` ${C.bright}│${C.reset} ${C.dim}First normal epoch${C.reset}`.padEnd(53) + `${C.bright}│${C.reset}`);
|
|
436
|
+
console.log(` ${C.bright}│${C.reset} ${C.bright}${es.first_normal_epoch.padEnd(53)}${C.reset}${C.bright}│${C.reset}`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
console.log(` ${C.bright}└─────────────────────────────────────────────────────┘${C.reset}`);
|
|
441
|
+
console.log();
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// ---------------------------------------------------------------------------
|
|
445
|
+
// Main
|
|
446
|
+
// ---------------------------------------------------------------------------
|
|
447
|
+
|
|
448
|
+
async function main() {
|
|
449
|
+
const opts = parseArgs();
|
|
450
|
+
const rpc = opts.rpc;
|
|
451
|
+
|
|
452
|
+
if (!opts.asJson) {
|
|
453
|
+
console.log(`\n${C.cyan}Querying Aether network...${C.reset} ${C.dim}(${rpc})${C.reset}\n`);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Fetch all data in parallel
|
|
457
|
+
const [slot, blockHeight, validators, epochData, tps, supply] = await Promise.all([
|
|
458
|
+
getSlot(rpc),
|
|
459
|
+
getBlockHeight(rpc),
|
|
460
|
+
getValidators(rpc),
|
|
461
|
+
getEpoch(rpc),
|
|
462
|
+
getTPS(rpc),
|
|
463
|
+
getSupply(rpc),
|
|
464
|
+
]);
|
|
465
|
+
|
|
466
|
+
const peerCount = Array.isArray(validators) ? validators.length : 0;
|
|
467
|
+
|
|
468
|
+
const data = {
|
|
469
|
+
slot,
|
|
470
|
+
blockHeight,
|
|
471
|
+
peerCount,
|
|
472
|
+
tps,
|
|
473
|
+
supply,
|
|
474
|
+
epochData,
|
|
475
|
+
validators,
|
|
476
|
+
rpc,
|
|
477
|
+
fetchedAt: new Date().toISOString(),
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
if (opts.asJson) {
|
|
481
|
+
console.log(JSON.stringify(data, null, 2));
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (opts.showPeers) {
|
|
486
|
+
renderPeers(validators, rpc);
|
|
487
|
+
} else if (opts.showEpoch) {
|
|
488
|
+
renderEpoch(epochData, rpc);
|
|
489
|
+
} else {
|
|
490
|
+
renderSummary(data, rpc);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
module.exports = { main, networkCommand: main };
|
|
495
|
+
|
|
496
|
+
if (require.main === module) {
|
|
497
|
+
main().catch((err) => {
|
|
498
|
+
console.error(`\n${C.red}✗ Network command failed:${C.reset} ${err.message}`);
|
|
499
|
+
console.error(` ${C.dim}Check that your validator is running and RPC is accessible.${C.reset}`);
|
|
500
|
+
console.error(` ${C.dim}Set custom RPC: AETHER_RPC=http://your-rpc-url${C.reset}\n`);
|
|
501
|
+
process.exit(1);
|
|
502
|
+
});
|
|
503
|
+
}
|
package/commands/wallet.js
CHANGED
|
@@ -737,29 +737,6 @@ async function stakeWallet(rl) {
|
|
|
737
737
|
return;
|
|
738
738
|
}
|
|
739
739
|
|
|
740
|
-
// Derive the full wallet object (secret key needed for signing)
|
|
741
|
-
let keyPair;
|
|
742
|
-
try {
|
|
743
|
-
// Re-derive from public key stored in wallet file
|
|
744
|
-
// The secret key isn't stored — we'd need the mnemonic to re-derive.
|
|
745
|
-
// For signing, the CLI requires the wallet to have been created/imported in this session.
|
|
746
|
-
// We use bs58 decoded publicKey + nacl key derivation from stored entropy.
|
|
747
|
-
// Since we store only publicKey, we need the secret key for signing.
|
|
748
|
-
// Workaround: accept a --sign-with <secretkeybase58> flag for now, or
|
|
749
|
-
// require the wallet to be "active" via a session.
|
|
750
|
-
// For simplicity, derive a keypair using a stored seed phrase approach.
|
|
751
|
-
// The wallet.json only has public_key. We need nacl sign keypair.
|
|
752
|
-
// Let's require the secret key be provided for stake/transfer.
|
|
753
|
-
console.log(` ${C.red}✗ Signing requires the wallet secret key.${C.reset}`);
|
|
754
|
-
console.log(` ${C.dim}The wallet must be created/imported in this session to access the secret key.${C.reset}`);
|
|
755
|
-
console.log(` ${C.dim}For staking, use the JS SDK's offline signing flow instead.${C.reset}`);
|
|
756
|
-
console.log(` ${C.dim}See: aether-cli sdk js${C.reset}\n`);
|
|
757
|
-
return;
|
|
758
|
-
} catch (e) {
|
|
759
|
-
console.log(` ${C.red}✗ Failed to load wallet keys: ${e.message}${C.reset}\n`);
|
|
760
|
-
return;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
740
|
// Prompt for missing values interactively
|
|
764
741
|
if (!validator) {
|
|
765
742
|
console.log(` ${C.cyan}Enter validator address:${C.reset}`);
|
|
@@ -784,6 +761,30 @@ async function stakeWallet(rl) {
|
|
|
784
761
|
console.log(` ${C.green}★${C.reset} Amount: ${C.bright}${amount} AETH${C.reset} (${lamports} lamports)`);
|
|
785
762
|
console.log();
|
|
786
763
|
|
|
764
|
+
// Ask for mnemonic to derive signing keypair
|
|
765
|
+
console.log(`${C.yellow} ⚠ Signing requires your wallet passphrase.${C.reset}`);
|
|
766
|
+
const mnemonic = await askMnemonic(rl, 'Enter your 12/24-word passphrase to sign this transaction');
|
|
767
|
+
console.log();
|
|
768
|
+
|
|
769
|
+
let keyPair;
|
|
770
|
+
try {
|
|
771
|
+
keyPair = deriveKeypair(mnemonic, DERIVATION_PATH);
|
|
772
|
+
} catch (e) {
|
|
773
|
+
console.log(` ${C.red}✗ Failed to derive keypair: ${e.message}${C.reset}`);
|
|
774
|
+
console.log(` ${C.dim}Check your passphrase and try again.${C.reset}\n`);
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// Verify the derived address matches the wallet
|
|
779
|
+
const derivedAddress = formatAddress(keyPair.publicKey);
|
|
780
|
+
if (derivedAddress !== address) {
|
|
781
|
+
console.log(` ${C.red}✗ Passphrase mismatch.${C.reset}`);
|
|
782
|
+
console.log(` ${C.dim} Derived: ${derivedAddress}${C.reset}`);
|
|
783
|
+
console.log(` ${C.dim} Expected: ${address}${C.reset}`);
|
|
784
|
+
console.log(` ${C.dim}Check your passphrase and try again.${C.reset}\n`);
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
|
|
787
788
|
const confirm = await question(rl, ` ${C.yellow}Confirm stake? [y/N]${C.reset} > ${C.reset}`);
|
|
788
789
|
if (!confirm.trim().toLowerCase().startsWith('y')) {
|
|
789
790
|
console.log(` ${C.dim}Cancelled.${C.reset}\n`);
|
|
@@ -894,6 +895,30 @@ async function transferWallet(rl) {
|
|
|
894
895
|
console.log(` ${C.green}★${C.reset} Amount: ${C.bright}${amount} AETH${C.reset} (${lamports} lamports)`);
|
|
895
896
|
console.log();
|
|
896
897
|
|
|
898
|
+
// Ask for mnemonic to derive signing keypair
|
|
899
|
+
console.log(`${C.yellow} ⚠ Signing requires your wallet passphrase.${C.reset}`);
|
|
900
|
+
const mnemonic = await askMnemonic(rl, 'Enter your 12/24-word passphrase to sign this transaction');
|
|
901
|
+
console.log();
|
|
902
|
+
|
|
903
|
+
let keyPair;
|
|
904
|
+
try {
|
|
905
|
+
keyPair = deriveKeypair(mnemonic, DERIVATION_PATH);
|
|
906
|
+
} catch (e) {
|
|
907
|
+
console.log(` ${C.red}✗ Failed to derive keypair: ${e.message}${C.reset}`);
|
|
908
|
+
console.log(` ${C.dim}Check your passphrase and try again.${C.reset}\n`);
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Verify the derived address matches the wallet
|
|
913
|
+
const derivedAddress = formatAddress(keyPair.publicKey);
|
|
914
|
+
if (derivedAddress !== address) {
|
|
915
|
+
console.log(` ${C.red}✗ Passphrase mismatch.${C.reset}`);
|
|
916
|
+
console.log(` ${C.dim} Derived: ${derivedAddress}${C.reset}`);
|
|
917
|
+
console.log(` ${C.dim} Expected: ${address}${C.reset}`);
|
|
918
|
+
console.log(` ${C.dim}Check your passphrase and try again.${C.reset}\n`);
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
|
|
897
922
|
const confirm = await question(rl, ` ${C.yellow}Confirm transfer? [y/N]${C.reset} > ${C.reset}`);
|
|
898
923
|
if (!confirm.trim().toLowerCase().startsWith('y')) {
|
|
899
924
|
console.log(` ${C.dim}Cancelled.${C.reset}\n`);
|
|
@@ -938,6 +963,149 @@ async function transferWallet(rl) {
|
|
|
938
963
|
}
|
|
939
964
|
}
|
|
940
965
|
|
|
966
|
+
// ---------------------------------------------------------------------------
|
|
967
|
+
// TX HISTORY
|
|
968
|
+
// Fetch and display recent transactions for an address
|
|
969
|
+
// ---------------------------------------------------------------------------
|
|
970
|
+
|
|
971
|
+
async function txHistory(rl) {
|
|
972
|
+
console.log(`\n${C.bright}${C.cyan}── Transaction History ────────────────────────────────────${C.reset}\n`);
|
|
973
|
+
|
|
974
|
+
const args = process.argv.slice(4);
|
|
975
|
+
let address = null;
|
|
976
|
+
let limit = 20;
|
|
977
|
+
let asJson = false;
|
|
978
|
+
|
|
979
|
+
const addrIdx = args.findIndex((a) => a === '--address' || a === '-a');
|
|
980
|
+
if (addrIdx !== -1 && args[addrIdx + 1]) {
|
|
981
|
+
address = args[addrIdx + 1];
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
const limitIdx = args.findIndex((a) => a === '--limit' || a === '-l');
|
|
985
|
+
if (limitIdx !== -1 && args[limitIdx + 1]) {
|
|
986
|
+
limit = parseInt(args[limitIdx + 1], 10);
|
|
987
|
+
if (isNaN(limit) || limit < 1 || limit > 100) {
|
|
988
|
+
console.log(` ${C.red}✗ --limit must be between 1 and 100.${C.reset}\n`);
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
asJson = args.includes('--json') || args.includes('-j');
|
|
994
|
+
|
|
995
|
+
if (!address) {
|
|
996
|
+
const cfg = loadConfig();
|
|
997
|
+
address = cfg.defaultWallet;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
if (!address) {
|
|
1001
|
+
console.log(` ${C.red}✗ No wallet address specified and no default wallet set.${C.reset}`);
|
|
1002
|
+
console.log(` ${C.dim}Usage: aether tx history --address <addr> [--limit 20] [--json]${C.reset}\n`);
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
const rpcUrl = getDefaultRpc();
|
|
1007
|
+
const rawAddr = address.startsWith('ATH') ? address.slice(3) : address;
|
|
1008
|
+
|
|
1009
|
+
if (!asJson) {
|
|
1010
|
+
console.log(` ${C.green}★${C.reset} Address: ${C.bright}${address}${C.reset}`);
|
|
1011
|
+
console.log(` ${C.dim} RPC: ${rpcUrl} Limit: ${limit}${C.reset}`);
|
|
1012
|
+
console.log();
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
try {
|
|
1016
|
+
// Fetch account info first (for context)
|
|
1017
|
+
const account = await httpRequest(rpcUrl, `/v1/account/${rawAddr}`);
|
|
1018
|
+
|
|
1019
|
+
// Fetch transactions for this address
|
|
1020
|
+
const txs = await httpRequest(rpcUrl, `/v1/tx?address=${encodeURIComponent(rawAddr)}&limit=${limit}`);
|
|
1021
|
+
|
|
1022
|
+
if (asJson) {
|
|
1023
|
+
const out = {
|
|
1024
|
+
address,
|
|
1025
|
+
rpc: rpcUrl,
|
|
1026
|
+
account: account && !account.error ? {
|
|
1027
|
+
lamports: account.lamports,
|
|
1028
|
+
owner: account.owner,
|
|
1029
|
+
} : null,
|
|
1030
|
+
transactions: txs && !txs.error ? (Array.isArray(txs) ? txs : txs.transactions || []) : [],
|
|
1031
|
+
fetched_at: new Date().toISOString(),
|
|
1032
|
+
};
|
|
1033
|
+
console.log(JSON.stringify(out, null, 2));
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
if (!account || account.error) {
|
|
1038
|
+
console.log(` ${C.yellow}⚠ Account not found on chain.${C.reset}`);
|
|
1039
|
+
} else {
|
|
1040
|
+
console.log(` ${C.green}✓ Balance:${C.reset} ${C.bright}${formatAether(account.lamports || 0)}${C.reset}`);
|
|
1041
|
+
if (account.owner) {
|
|
1042
|
+
const ownerStr = Array.isArray(account.owner)
|
|
1043
|
+
? 'ATH' + bs58.encode(Buffer.from(account.owner.slice(0, 32)))
|
|
1044
|
+
: account.owner;
|
|
1045
|
+
console.log(` ${C.dim} Owner: ${ownerStr}${C.reset}`);
|
|
1046
|
+
}
|
|
1047
|
+
console.log();
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
if (!txs || txs.error) {
|
|
1051
|
+
console.log(` ${C.yellow}⚠ No transaction history available.${C.reset}`);
|
|
1052
|
+
console.log(` ${C.dim} RPC response: ${JSON.stringify(txs?.error || txs)}${C.reset}`);
|
|
1053
|
+
console.log(` ${C.dim} (New wallets with 0 txs will return empty results)${C.reset}\n`);
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
const txList = Array.isArray(txs) ? txs : txs.transactions || [];
|
|
1058
|
+
console.log(` ${C.bright}Recent Transactions (${txList.length})${C.reset}\n`);
|
|
1059
|
+
|
|
1060
|
+
if (txList.length === 0) {
|
|
1061
|
+
console.log(` ${C.dim} No transactions found for this address.${C.reset}`);
|
|
1062
|
+
console.log(` ${C.dim} This is normal for new wallets.${C.reset}\n`);
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
const typeColors = {
|
|
1067
|
+
Transfer: C.cyan,
|
|
1068
|
+
Stake: C.green,
|
|
1069
|
+
Unstake: C.yellow,
|
|
1070
|
+
ClaimRewards: C.magenta,
|
|
1071
|
+
CreateNFT: C.red,
|
|
1072
|
+
MintNFT: C.red,
|
|
1073
|
+
TransferNFT: C.cyan,
|
|
1074
|
+
UpdateMetadata: C.yellow,
|
|
1075
|
+
};
|
|
1076
|
+
|
|
1077
|
+
for (const tx of txList) {
|
|
1078
|
+
const txType = tx.tx_type || tx.type || 'Unknown';
|
|
1079
|
+
const color = typeColors[txType] || C.reset;
|
|
1080
|
+
const ts = tx.timestamp
|
|
1081
|
+
? new Date(tx.timestamp * 1000).toISOString()
|
|
1082
|
+
: 'unknown';
|
|
1083
|
+
const sig = tx.signature || tx.id || tx.tx_signature || '—';
|
|
1084
|
+
const sigShort = sig.length > 20 ? sig.slice(0, 8) + '…' + sig.slice(-8) : sig;
|
|
1085
|
+
|
|
1086
|
+
console.log(` ${C.dim}┌─ ${ts}${C.reset}`);
|
|
1087
|
+
console.log(` │ ${C.bright}${color}${txType}${C.reset} ${C.dim}sig:${C.reset} ${sigShort}`);
|
|
1088
|
+
if (tx.payload && tx.payload.data) {
|
|
1089
|
+
const d = tx.payload.data;
|
|
1090
|
+
if (d.recipient) console.log(` │ ${C.dim} → to: ${d.recipient}${C.reset}`);
|
|
1091
|
+
if (d.amount) console.log(` │ ${C.dim} amount: ${formatAether(d.amount)}${C.reset}`);
|
|
1092
|
+
if (d.validator) console.log(` │ ${C.dim} validator: ${d.validator}${C.reset}`);
|
|
1093
|
+
if (d.stake_account) console.log(` │ ${C.dim} stake_acct: ${d.stake_account}${C.reset}`);
|
|
1094
|
+
}
|
|
1095
|
+
if (tx.fee !== undefined && tx.fee > 0) {
|
|
1096
|
+
console.log(` │ ${C.dim} fee: ${tx.fee} lamports${C.reset}`);
|
|
1097
|
+
}
|
|
1098
|
+
console.log(` ${C.dim}└${C.reset}`);
|
|
1099
|
+
console.log();
|
|
1100
|
+
}
|
|
1101
|
+
console.log();
|
|
1102
|
+
} catch (err) {
|
|
1103
|
+
console.log(` ${C.red}✗ Failed to fetch transaction history:${C.reset} ${err.message}`);
|
|
1104
|
+
console.log(` ${C.dim} Is your validator running? RPC: ${rpcUrl}${C.reset}`);
|
|
1105
|
+
console.log(` ${C.dim} Set custom RPC: AETHER_RPC=https://your-rpc-url${C.reset}\n`);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
941
1109
|
// ---------------------------------------------------------------------------
|
|
942
1110
|
// Main dispatcher
|
|
943
1111
|
// ---------------------------------------------------------------------------
|
|
@@ -967,6 +1135,8 @@ async function walletCommand() {
|
|
|
967
1135
|
await stakeWallet(rl);
|
|
968
1136
|
} else if (subcmd === 'transfer') {
|
|
969
1137
|
await transferWallet(rl);
|
|
1138
|
+
} else if (subcmd === 'history' || subcmd === 'tx') {
|
|
1139
|
+
await txHistory(rl);
|
|
970
1140
|
} else {
|
|
971
1141
|
console.log(`\n ${C.red}Unknown wallet subcommand:${C.reset} ${subcmd}`);
|
|
972
1142
|
console.log(`\n Usage:`);
|
|
@@ -978,6 +1148,7 @@ async function walletCommand() {
|
|
|
978
1148
|
console.log(` ${C.cyan}aether wallet balance${C.reset} Query chain balance for an address`);
|
|
979
1149
|
console.log(` ${C.cyan}aether wallet stake${C.reset} Stake AETH to a validator`);
|
|
980
1150
|
console.log(` ${C.cyan}aether wallet transfer${C.reset} Transfer AETH to another address`);
|
|
1151
|
+
console.log(` ${C.cyan}aether wallet history${C.reset} Show recent transactions for an address`);
|
|
981
1152
|
console.log();
|
|
982
1153
|
process.exit(1);
|
|
983
1154
|
}
|
package/index.js
CHANGED
|
@@ -14,6 +14,7 @@ const { monitorLoop } = require('./commands/monitor');
|
|
|
14
14
|
const { logsCommand } = require('./commands/logs');
|
|
15
15
|
const { sdkCommand } = require('./commands/sdk');
|
|
16
16
|
const { walletCommand } = require('./commands/wallet');
|
|
17
|
+
const { networkCommand } = require('./commands/network');
|
|
17
18
|
const readline = require('readline');
|
|
18
19
|
|
|
19
20
|
// CLI version
|
|
@@ -55,10 +56,11 @@ async function showMenu() {
|
|
|
55
56
|
console.log(' ' + TIER_COLORS.FULL + '3)' + TIER_COLORS.reset + ' 📊 Monitor — Watch live validator stats');
|
|
56
57
|
console.log(' ' + TIER_COLORS.FULL + '4)' + TIER_COLORS.reset + ' 📋 Logs — Tail and colourise validator logs');
|
|
57
58
|
console.log(' ' + TIER_COLORS.FULL + '5)' + TIER_COLORS.reset + ' 📦 SDK — Get SDK links and install tools');
|
|
58
|
-
console.log(' ' + TIER_COLORS.FULL + '6)' + TIER_COLORS.reset + '
|
|
59
|
+
console.log(' ' + TIER_COLORS.FULL + '6)' + TIER_COLORS.reset + ' 🌐 Network — Aether network status (slot, peers, TPS)');
|
|
60
|
+
console.log(' ' + TIER_COLORS.FULL + '7)' + TIER_COLORS.reset + ' ❓ Help — Show all commands\n');
|
|
59
61
|
console.log(' ' + TIER_COLORS.reset + ' Type a number or command name. Press Ctrl+C to exit.\n');
|
|
60
62
|
|
|
61
|
-
const VALID_CHOICES = ['1', '2', '3', '4', '5', '6', 'doctor', 'init', 'monitor', 'logs', 'sdk', 'help'];
|
|
63
|
+
const VALID_CHOICES = ['1', '2', '3', '4', '5', '6', '7', 'doctor', 'init', 'monitor', 'logs', 'sdk', 'network', 'help'];
|
|
62
64
|
|
|
63
65
|
while (true) {
|
|
64
66
|
const answer = (await prompt(` > `)).trim().toLowerCase();
|
|
@@ -98,7 +100,14 @@ async function showMenu() {
|
|
|
98
100
|
return;
|
|
99
101
|
}
|
|
100
102
|
|
|
101
|
-
if (answer === '6' || answer === '
|
|
103
|
+
if (answer === '6' || answer === 'network') {
|
|
104
|
+
rl.close();
|
|
105
|
+
const { networkCommand } = require('./commands/network');
|
|
106
|
+
networkCommand();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (answer === '7' || answer === 'help') {
|
|
102
111
|
showHelp();
|
|
103
112
|
console.log(" Press Ctrl+C to exit or select an option above.\n");
|
|
104
113
|
continue;
|
|
@@ -182,6 +191,33 @@ const COMMANDS = {
|
|
|
182
191
|
process.argv = originalArgv;
|
|
183
192
|
},
|
|
184
193
|
},
|
|
194
|
+
tx: {
|
|
195
|
+
description: 'Transaction history — aether tx history --address <addr> [--limit 20] [--json]',
|
|
196
|
+
handler: () => {
|
|
197
|
+
const { walletCommand } = require('./commands/wallet');
|
|
198
|
+
const originalArgv = process.argv;
|
|
199
|
+
process.argv = [...originalArgv.slice(0, 2), 'wallet', 'history', ...originalArgv.slice(3)];
|
|
200
|
+
walletCommand();
|
|
201
|
+
process.argv = originalArgv;
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
network: {
|
|
205
|
+
description: 'Aether network status — slot, block height, peers, TPS, epoch info',
|
|
206
|
+
handler: () => {
|
|
207
|
+
const { networkCommand } = require('./commands/network');
|
|
208
|
+
networkCommand();
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
history: {
|
|
212
|
+
description: 'Transaction history for an address — alias for tx history',
|
|
213
|
+
handler: () => {
|
|
214
|
+
const { walletCommand } = require('./commands/wallet');
|
|
215
|
+
const originalArgv = process.argv;
|
|
216
|
+
process.argv = [...originalArgv.slice(0, 2), 'wallet', 'history', ...originalArgv.slice(3)];
|
|
217
|
+
walletCommand();
|
|
218
|
+
process.argv = originalArgv;
|
|
219
|
+
},
|
|
220
|
+
},
|
|
185
221
|
validator: {
|
|
186
222
|
description: 'Validator node management',
|
|
187
223
|
handler: () => {
|
|
@@ -249,6 +285,10 @@ Validator CLI v${VERSION}
|
|
|
249
285
|
console.log(' aether-cli monitor # Real-time validator dashboard');
|
|
250
286
|
console.log(' aether-cli validator start # Start validator node');
|
|
251
287
|
console.log(' aether-cli validator status # Check validator status');
|
|
288
|
+
console.log(' aether-cli wallet balance # Query AETH balance');
|
|
289
|
+
console.log(' aether-cli network # Network status, peers, slot info');
|
|
290
|
+
console.log(' aether-cli network --peers # Detailed peer list');
|
|
291
|
+
console.log(' aether-cli tx history # Show transaction history');
|
|
252
292
|
console.log(' aether-cli --version # Show version');
|
|
253
293
|
console.log('\nDocumentation: https://github.com/jelly-legs-ai/Jelly-legs-unsteady-workshop');
|
|
254
294
|
console.log('Spec: docs/MINING_VALIDATOR_TOOLS.md\n');
|
|
@@ -260,13 +300,10 @@ Validator CLI v${VERSION}
|
|
|
260
300
|
function parseArgs() {
|
|
261
301
|
const args = process.argv.slice(2);
|
|
262
302
|
|
|
263
|
-
// Handle
|
|
303
|
+
// Handle version flag
|
|
264
304
|
if (args.includes('--version') || args.includes('-v')) {
|
|
265
305
|
return 'version';
|
|
266
306
|
}
|
|
267
|
-
if (args.includes('--help') || args.includes('-h')) {
|
|
268
|
-
return 'help';
|
|
269
|
-
}
|
|
270
307
|
|
|
271
308
|
// No args → interactive menu
|
|
272
309
|
if (args.length === 0) {
|