aether-hub 1.0.6 → 1.1.1
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/delegations.js +462 -0
- package/commands/network.js +503 -0
- package/commands/rewards.js +600 -0
- package/commands/snapshot.js +509 -0
- package/commands/validators.js +326 -0
- package/commands/wallet.js +48 -23
- package/index.js +371 -326
- package/package.json +2 -2
|
@@ -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
|
+
}
|