@jellylegsai/aether-cli 1.8.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.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +110 -0
  3. package/aether-cli-1.0.0.tgz +0 -0
  4. package/aether-cli-1.8.0.tgz +0 -0
  5. package/aether-hub-1.0.5.tgz +0 -0
  6. package/aether-hub-1.1.8.tgz +0 -0
  7. package/aether-hub-1.2.1.tgz +0 -0
  8. package/commands/account.js +280 -0
  9. package/commands/apy.js +499 -0
  10. package/commands/balance.js +241 -0
  11. package/commands/blockhash.js +181 -0
  12. package/commands/broadcast.js +387 -0
  13. package/commands/claim.js +490 -0
  14. package/commands/config.js +851 -0
  15. package/commands/delegations.js +582 -0
  16. package/commands/doctor.js +769 -0
  17. package/commands/emergency.js +667 -0
  18. package/commands/epoch.js +275 -0
  19. package/commands/fees.js +276 -0
  20. package/commands/index.js +78 -0
  21. package/commands/info.js +495 -0
  22. package/commands/init.js +816 -0
  23. package/commands/install.js +666 -0
  24. package/commands/kyc.js +272 -0
  25. package/commands/logs.js +315 -0
  26. package/commands/monitor.js +431 -0
  27. package/commands/multisig.js +701 -0
  28. package/commands/network.js +429 -0
  29. package/commands/nft.js +857 -0
  30. package/commands/ping.js +266 -0
  31. package/commands/price.js +253 -0
  32. package/commands/rewards.js +931 -0
  33. package/commands/sdk-test.js +477 -0
  34. package/commands/sdk.js +656 -0
  35. package/commands/slot.js +155 -0
  36. package/commands/snapshot.js +470 -0
  37. package/commands/stake-info.js +139 -0
  38. package/commands/stake-positions.js +205 -0
  39. package/commands/stake.js +516 -0
  40. package/commands/stats.js +396 -0
  41. package/commands/status.js +327 -0
  42. package/commands/supply.js +391 -0
  43. package/commands/tps.js +238 -0
  44. package/commands/transfer.js +495 -0
  45. package/commands/tx-history.js +346 -0
  46. package/commands/unstake.js +597 -0
  47. package/commands/validator-info.js +657 -0
  48. package/commands/validator-register.js +593 -0
  49. package/commands/validator-start.js +323 -0
  50. package/commands/validator-status.js +227 -0
  51. package/commands/validators.js +626 -0
  52. package/commands/wallet.js +1570 -0
  53. package/index.js +593 -0
  54. package/lib/errors.js +398 -0
  55. package/package.json +76 -0
  56. package/sdk/README.md +210 -0
  57. package/sdk/index.js +1639 -0
  58. package/sdk/package.json +34 -0
  59. package/sdk/rpc.js +254 -0
  60. package/sdk/test.js +85 -0
  61. package/test/doctor.test.js +76 -0
  62. package/validator-identity.json +4 -0
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * aether-cli slot
4
+ *
5
+ * Get current slot number from the Aether blockchain.
6
+ * Uses @jellylegsai/aether-sdk for REAL HTTP RPC calls.
7
+ *
8
+ * Usage:
9
+ * aether slot Show current slot
10
+ * aether slot --json JSON output for scripting
11
+ * aether slot --rpc <url> Custom RPC endpoint
12
+ *
13
+ * RPC Endpoint: GET /v1/slot
14
+ * SDK Function: sdk.getSlot()
15
+ */
16
+
17
+ const path = require('path');
18
+
19
+ // ANSI colours
20
+ const C = {
21
+ reset: '\x1b[0m',
22
+ bright: '\x1b[1m',
23
+ dim: '\x1b[2m',
24
+ red: '\x1b[31m',
25
+ green: '\x1b[32m',
26
+ yellow: '\x1b[33m',
27
+ cyan: '\x1b[36m',
28
+ magenta: '\x1b[35m',
29
+ };
30
+
31
+ const CLI_VERSION = '1.0.0';
32
+
33
+ // Import SDK — makes REAL HTTP RPC calls to the blockchain
34
+ const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
35
+ const aether = require(sdkPath);
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Helpers
39
+ // ---------------------------------------------------------------------------
40
+
41
+ function getDefaultRpc() {
42
+ return process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
43
+ }
44
+
45
+ function createClient(rpc) {
46
+ return new aether.AetherClient({ rpcUrl: rpc });
47
+ }
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // Argument parsing
51
+ // ---------------------------------------------------------------------------
52
+
53
+ function parseArgs() {
54
+ const args = process.argv.slice(2);
55
+ return {
56
+ rpc: getDefaultRpc(),
57
+ asJson: args.includes('--json') || args.includes('-j'),
58
+ };
59
+ }
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Slot query - REAL RPC call via SDK
63
+ // ---------------------------------------------------------------------------
64
+
65
+ async function fetchSlot(rpc) {
66
+ const client = createClient(rpc);
67
+
68
+ // Real RPC call: GET /v1/slot
69
+ // SDK function: client.getSlot()
70
+ const slot = await client.getSlot();
71
+
72
+ return {
73
+ slot,
74
+ rpc,
75
+ timestamp: new Date().toISOString(),
76
+ };
77
+ }
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // Output formatters
81
+ // ---------------------------------------------------------------------------
82
+
83
+ function printSlot(data) {
84
+ const { slot, rpc } = data;
85
+
86
+ console.log(`\n${C.bright}${C.cyan}── Aether Current Slot ──────────────────────────────────${C.reset}\n`);
87
+ console.log(` ${C.bright}Slot:${C.reset} ${C.green}${slot.toLocaleString()}${C.reset}`);
88
+ console.log(` ${C.dim}RPC:${C.reset} ${rpc}`);
89
+ console.log(` ${C.dim}Time:${C.reset} ${data.timestamp}${C.reset}\n`);
90
+
91
+ // Context info
92
+ console.log(` ${C.dim}RPC Endpoint: GET /v1/slot${C.reset}`);
93
+ console.log(` ${C.dim}SDK Function: sdk.getSlot()${C.reset}\n`);
94
+ }
95
+
96
+ function printJson(data) {
97
+ console.log(JSON.stringify({
98
+ slot: data.slot,
99
+ rpc: data.rpc,
100
+ timestamp: data.timestamp,
101
+ cli_version: CLI_VERSION,
102
+ sdk: '@jellylegsai/aether-sdk',
103
+ rpc_endpoint: 'GET /v1/slot',
104
+ }, null, 2));
105
+ }
106
+
107
+ // ---------------------------------------------------------------------------
108
+ // Main command
109
+ // ---------------------------------------------------------------------------
110
+
111
+ async function slotCommand() {
112
+ const options = parseArgs();
113
+ const { rpc, asJson } = options;
114
+
115
+ if (!asJson) {
116
+ console.log(`${C.dim}Fetching current slot from ${C.cyan}${rpc}${C.dim}...${C.reset}`);
117
+ }
118
+
119
+ try {
120
+ // Real blockchain RPC call via SDK
121
+ const data = await fetchSlot(rpc);
122
+
123
+ if (asJson) {
124
+ printJson(data);
125
+ } else {
126
+ printSlot(data);
127
+ }
128
+ } catch (err) {
129
+ if (asJson) {
130
+ console.log(JSON.stringify({
131
+ error: err.message,
132
+ rpc,
133
+ timestamp: new Date().toISOString(),
134
+ }));
135
+ } else {
136
+ console.log(`\n${C.red}✗ Failed to fetch slot: ${err.message}${C.reset}`);
137
+ console.log(` ${C.dim}RPC: ${rpc}${C.reset}`);
138
+ console.log(` ${C.dim}Make sure the Aether node is running at ${rpc}${C.reset}\n`);
139
+ }
140
+ process.exit(1);
141
+ }
142
+ }
143
+
144
+ // ---------------------------------------------------------------------------
145
+ // Exports
146
+ // ---------------------------------------------------------------------------
147
+
148
+ module.exports = { slotCommand };
149
+
150
+ if (require.main === module) {
151
+ slotCommand().catch(err => {
152
+ console.error(`${C.red}Slot command failed:${C.reset} ${err.message}`);
153
+ process.exit(1);
154
+ });
155
+ }
@@ -0,0 +1,470 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * aether-cli snapshot - Aether Node Sync & Snapshot Status
4
+ *
5
+ * Shows how far your node has synced vs the network, snapshot availability,
6
+ * and whether your node is catching up or is fully current.
7
+ *
8
+ * FULLY WIRED TO SDK - Uses @jellylegsai/aether-sdk for all blockchain calls.
9
+ * No manual HTTP - all calls go through AetherClient with real RPC.
10
+ *
11
+ * Usage:
12
+ * aether-cli snapshot # Interactive sync status view
13
+ * aether-cli snapshot --json # JSON output for scripting
14
+ * aether-cli snapshot --rpc <url> # Query a specific RPC endpoint
15
+ * aether-cli snapshot --watch # Refresh every 5 seconds
16
+ *
17
+ * SDK Methods Used:
18
+ * - client.getSlot() → GET /v1/slot
19
+ * - client.getBlockHeight() → GET /v1/blockheight
20
+ * - client.getEpochInfo() → GET /v1/epoch
21
+ * - client.getHealth() → GET /v1/health
22
+ * - client.getVersion() → GET /v1/version
23
+ * - client.getSupply() → GET /v1/supply (for additional context)
24
+ *
25
+ * @see docs/MINING_VALIDATOR_TOOLS.md for spec
26
+ */
27
+
28
+ const path = require('path');
29
+
30
+ // Import SDK for ALL blockchain RPC calls
31
+ const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
32
+ const aether = require(sdkPath);
33
+
34
+ // ANSI colours
35
+ const C = {
36
+ reset: '\x1b[0m',
37
+ bright: '\x1b[1m',
38
+ dim: '\x1b[2m',
39
+ red: '\x1b[31m',
40
+ green: '\x1b[32m',
41
+ yellow: '\x1b[33m',
42
+ blue: '\x1b[34m',
43
+ cyan: '\x1b[36m',
44
+ magenta: '\x1b[35m',
45
+ };
46
+
47
+ const CLI_VERSION = '1.1.0';
48
+ const REFRESH_INTERVAL_MS = 5000;
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // SDK Client Setup
52
+ // ---------------------------------------------------------------------------
53
+
54
+ function getDefaultRpc() {
55
+ return process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
56
+ }
57
+
58
+ function createClient(rpcUrl) {
59
+ return new aether.AetherClient({ rpcUrl });
60
+ }
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Argument parsing
64
+ // ---------------------------------------------------------------------------
65
+
66
+ function parseArgs() {
67
+ const args = process.argv.slice(2);
68
+ const options = {
69
+ rpc: getDefaultRpc(),
70
+ asJson: false,
71
+ watch: false,
72
+ };
73
+
74
+ for (let i = 0; i < args.length; i++) {
75
+ if (args[i] === '--rpc' || args[i] === '-r') {
76
+ options.rpc = args[++i];
77
+ } else if (args[i] === '--json' || args[i] === '-j') {
78
+ options.asJson = true;
79
+ } else if (args[i] === '--watch' || args[i] === '-w') {
80
+ options.watch = true;
81
+ } else if (args[i] === '--help' || args[i] === '-h') {
82
+ showHelp();
83
+ process.exit(0);
84
+ }
85
+ }
86
+
87
+ return options;
88
+ }
89
+
90
+ function showHelp() {
91
+ console.log(`
92
+ ${C.bright}${C.cyan}aether-cli snapshot${C.reset} - Aether Node Sync & Snapshot Status (SDK-Wired)
93
+
94
+ ${C.bright}Usage:${C.reset}
95
+ aether-cli snapshot [options]
96
+
97
+ ${C.bright}Options:${C.reset}
98
+ -r, --rpc <url> RPC endpoint (default: ${getDefaultRpc()} or $AETHER_RPC)
99
+ -j, --json Output raw JSON (good for scripting)
100
+ -w, --watch Refresh every 5 seconds (live view)
101
+ -h, --help Show this help message
102
+
103
+ ${C.bright}SDK Methods Used:${C.reset}
104
+ client.getSlot() → GET /v1/slot
105
+ client.getBlockHeight() → GET /v1/blockheight
106
+ client.getEpochInfo() → GET /v1/epoch
107
+ client.getHealth() → GET /v1/health
108
+ client.getVersion() → GET /v1/version
109
+
110
+ ${C.bright}Examples:${C.reset}
111
+ aether-cli snapshot # Interactive sync status
112
+ aether-cli snapshot --json # JSON output
113
+ aether-cli snapshot --watch # Live refreshing view
114
+ aether-cli snapshot --rpc https://api.testnet.aether.network
115
+ `.trim());
116
+ }
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // Data fetchers - ALL SDK WIRED (REAL RPC CALLS)
120
+ // ---------------------------------------------------------------------------
121
+
122
+ /** SDK call: GET /v1/slot - current network slot */
123
+ async function getSlot(rpc) {
124
+ const client = createClient(rpc);
125
+ try {
126
+ return await client.getSlot();
127
+ } catch {
128
+ return null;
129
+ }
130
+ }
131
+
132
+ /** SDK call: GET /v1/blockheight - node's synced block height */
133
+ async function getBlockHeight(rpc) {
134
+ const client = createClient(rpc);
135
+ try {
136
+ return await client.getBlockHeight();
137
+ } catch {
138
+ return null;
139
+ }
140
+ }
141
+
142
+ /** SDK call: GET /v1/epoch - current epoch info */
143
+ async function getEpoch(rpc) {
144
+ const client = createClient(rpc);
145
+ try {
146
+ return await client.getEpochInfo();
147
+ } catch {
148
+ return null;
149
+ }
150
+ }
151
+
152
+ /** SDK call: GET /v1/health - node health */
153
+ async function getHealth(rpc) {
154
+ const client = createClient(rpc);
155
+ try {
156
+ const health = await client.getHealth();
157
+ return { ok: health === 'ok' || health === 'healthy', status: health };
158
+ } catch {
159
+ return null;
160
+ }
161
+ }
162
+
163
+ /** SDK call: GET /v1/version - node version info */
164
+ async function getVersion(rpc) {
165
+ const client = createClient(rpc);
166
+ try {
167
+ return await client.getVersion();
168
+ } catch {
169
+ return null;
170
+ }
171
+ }
172
+
173
+ /** SDK call: GET /v1/supply - token supply for context */
174
+ async function getSupply(rpc) {
175
+ const client = createClient(rpc);
176
+ try {
177
+ return await client.getSupply();
178
+ } catch {
179
+ return null;
180
+ }
181
+ }
182
+
183
+ // ---------------------------------------------------------------------------
184
+ // Formatting helpers
185
+ // ---------------------------------------------------------------------------
186
+
187
+ function formatNumber(n) {
188
+ if (n === null || n === undefined) return 'N/A';
189
+ return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
190
+ }
191
+
192
+ function syncStatus(nodeSlot, networkSlot) {
193
+ if (nodeSlot === null || networkSlot === null) {
194
+ return { label: `${C.yellow}UNKNOWN${C.reset}`, icon: '?', color: C.yellow };
195
+ }
196
+ const diff = networkSlot - nodeSlot;
197
+ const pct = networkSlot > 0 ? ((nodeSlot / networkSlot) * 100).toFixed(1) : '0.0';
198
+
199
+ if (diff <= 0) {
200
+ return { label: `${C.green}SYNCED${C.reset}`, icon: '✓', color: C.green, diff };
201
+ }
202
+ if (diff <= 5) {
203
+ return { label: `${C.green}CATCHING UP${C.reset}`, icon: '◐', color: C.green, diff };
204
+ }
205
+ if (diff <= 50) {
206
+ return { label: `${C.yellow}BEHIND${C.reset}`, icon: '◑', color: C.yellow, diff };
207
+ }
208
+ return { label: `${C.red}FAR BEHIND${C.reset}`, icon: '✗', color: C.red, diff };
209
+ }
210
+
211
+ function progressBar(nodeSlot, networkSlot, width = 30) {
212
+ if (nodeSlot === null || networkSlot === null || networkSlot === 0) {
213
+ return `${C.dim}[${'─'.repeat(width)}]${C.reset} N/A`;
214
+ }
215
+ const ratio = Math.min(nodeSlot / networkSlot, 1);
216
+ const filled = Math.round(ratio * width);
217
+ const empty = width - filled;
218
+ return (
219
+ `${C.green}[${'█'.repeat(filled)}${C.dim}${'─'.repeat(empty)}${C.reset}]` +
220
+ ` ${(ratio * 100).toFixed(1)}%`
221
+ );
222
+ }
223
+
224
+ function catchupEstimate(nodeSlot, networkSlot) {
225
+ if (nodeSlot === null || networkSlot === null || networkSlot <= nodeSlot) return null;
226
+ const diff = networkSlot - nodeSlot;
227
+ // Rough estimate: ~2 slots/second typical throughput
228
+ const seconds = Math.floor(diff / 2);
229
+ if (seconds < 60) return `~${seconds}s`;
230
+ if (seconds < 3600) return `~${Math.floor(seconds / 60)}m`;
231
+ return `~${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`;
232
+ }
233
+
234
+ // ---------------------------------------------------------------------------
235
+ // Renderers
236
+ // ---------------------------------------------------------------------------
237
+
238
+ function renderSync(data, rpc) {
239
+ const { nodeSlot, networkSlot, blockHeight, epochData, versionData, healthData, supplyData, asJson } = data;
240
+
241
+ if (asJson) {
242
+ const status = syncStatus(nodeSlot, networkSlot);
243
+ console.log(JSON.stringify({
244
+ rpc,
245
+ fetchedAt: new Date().toISOString(),
246
+ node: {
247
+ slot: nodeSlot,
248
+ blockHeight,
249
+ },
250
+ network: {
251
+ slot: networkSlot,
252
+ },
253
+ sync: {
254
+ status: status.label.replace(/\x1b\[\d+m/g, ''),
255
+ slotsBehind: networkSlot !== null && nodeSlot !== null ? Math.max(0, networkSlot - nodeSlot) : null,
256
+ percentSynced: nodeSlot !== null && networkSlot !== null && networkSlot > 0
257
+ ? parseFloat((nodeSlot / networkSlot * 100).toFixed(2))
258
+ : null,
259
+ },
260
+ epoch: epochData ? {
261
+ epoch: epochData.epoch,
262
+ slotIndex: epochData.slotIndex,
263
+ slotsInEpoch: epochData.slotsInEpoch,
264
+ absoluteSlot: epochData.absoluteSlot,
265
+ } : null,
266
+ version: versionData?.aetherCore ?? versionData?.version ?? null,
267
+ health: healthData,
268
+ supply: supplyData ? {
269
+ total: supplyData.total,
270
+ circulating: supplyData.circulating,
271
+ } : null,
272
+ sdk_version: CLI_VERSION,
273
+ }, null, 2));
274
+ return;
275
+ }
276
+
277
+ const status = syncStatus(nodeSlot, networkSlot);
278
+ const now = new Date().toLocaleTimeString();
279
+ const catchup = catchupEstimate(nodeSlot, networkSlot);
280
+
281
+ console.log();
282
+ console.log(`${C.bright}${C.cyan}╔═══════════════════════════════════════════════════════════════════╗${C.reset}`);
283
+ console.log(`${C.bright}${C.cyan}║${C.reset} ${C.bright}AETHER NODE SNAPSHOT / SYNC STATUS${C.reset}${C.cyan} ║${C.reset}`);
284
+ console.log(`${C.bright}${C.cyan}╚═══════════════════════════════════════════════════════════════════╝${C.reset}`);
285
+ console.log(` ${C.dim}RPC:${C.reset} ${rpc}`);
286
+ console.log(` ${C.dim}SDK:${C.reset} v${CLI_VERSION} │ ${C.dim}Updated:${C.reset} ${now}`);
287
+ console.log();
288
+
289
+ // Health indicator via SDK
290
+ if (healthData) {
291
+ const ok = healthData.ok ?? true;
292
+ console.log(` ${C.bright}┌──────────────────────────────────────────────────────────────────────┐${C.reset}`);
293
+ console.log(` ${C.bright}│${C.reset} Node Health: ${ok ? `${C.green}● HEALTHY${C.reset}` : `${C.red}● UNHEALTHY${C.reset}`}`.padEnd(65) + `${C.bright}│${C.reset}`);
294
+ console.log(` ${C.bright}└──────────────────────────────────────────────────────────────────────┘${C.reset}`);
295
+ console.log();
296
+ }
297
+
298
+ // Sync status — large prominent display
299
+ console.log(` ${C.bright}┌──────────────────────────────────────────────────────────────────────┐${C.reset}`);
300
+ console.log(` ${C.bright}│${C.reset} Sync Status: ${status.color}${C.bright}${status.label}${C.reset}`.padEnd(65) + `${C.bright}│${C.reset}`);
301
+ if (status.diff !== undefined && status.diff > 0) {
302
+ console.log(` ${C.bright}│${C.reset} Slots behind: ${C.yellow}${formatNumber(status.diff)}${C.reset}`.padEnd(65) + `${C.bright}│${C.reset}`);
303
+ if (catchup) {
304
+ console.log(` ${C.bright}│${C.reset} Est. time to sync: ${C.cyan}${catchup}${C.reset}`.padEnd(65) + `${C.bright}│${C.reset}`);
305
+ }
306
+ }
307
+ console.log(` ${C.bright}└──────────────────────────────────────────────────────────────────────┘${C.reset}`);
308
+ console.log();
309
+
310
+ // Progress bar
311
+ console.log(` ${C.bright}── Slot Progress ───────────────────────────────────────────${C.reset}`);
312
+ console.log(` ${progressBar(nodeSlot, networkSlot)}`);
313
+ console.log();
314
+
315
+ // Slot details
316
+ console.log(` ${C.bright}┌────────────────────┬────────────────────┐${C.reset}`);
317
+ console.log(` ${C.bright}│${C.reset} ${C.cyan}Your Node${C.reset} ${C.bright}│${C.reset} ${C.cyan}Network${C.reset} ${C.bright}│${C.reset}`);
318
+ console.log(` ${C.bright}├────────────────────┼────────────────────┤${C.reset}`);
319
+ const nodeStr = nodeSlot !== null ? formatNumber(nodeSlot) : 'N/A';
320
+ const netStr = networkSlot !== null ? formatNumber(networkSlot) : 'N/A';
321
+ console.log(` ${C.bright}│${C.reset} Slot: ${C.green}${nodeStr.padEnd(22)}${C.reset} ${C.bright}│${C.reset} Slot: ${C.cyan}${netStr.padEnd(22)}${C.reset} ${C.bright}│${C.reset}`);
322
+ const bhStr = blockHeight !== null ? formatNumber(blockHeight) : 'N/A';
323
+ console.log(` ${C.bright}│${C.reset} Block: ${C.blue}${bhStr.padEnd(21)}${C.reset} ${C.bright}│${C.reset} ${C.dim}Block: same as slot${C.reset} ${C.bright}│${C.reset}`);
324
+ console.log(` ${C.bright}└────────────────────┴────────────────────┘${C.reset}`);
325
+ console.log();
326
+
327
+ // Epoch info via SDK
328
+ if (epochData && epochData.epoch !== undefined) {
329
+ console.log(` ${C.bright}── Epoch Info ──────────────────────────────────────────────${C.reset}`);
330
+ const ep = epochData.epoch;
331
+ const slotIdx = epochData.slotIndex !== undefined ? epochData.slotIndex : '?';
332
+ const slotsInEp = epochData.slotsInEpoch !== undefined ? epochData.slotsInEpoch : '?';
333
+ const progress = slotsInEp !== '?' && slotsInEp > 0
334
+ ? ((slotIdx / slotsInEp) * 100).toFixed(1) + '%'
335
+ : '?';
336
+ console.log(` ${C.dim}Epoch:${C.reset} ${C.bright}${ep}${C.reset} ${C.dim}Slot in epoch:${C.reset} ${C.bright}${slotIdx} / ${slotsInEp}${C.reset} ${C.dim}(${progress})${C.reset}`);
337
+ if (epochData.absoluteSlot !== undefined) {
338
+ console.log(` ${C.dim}Absolute slot:${C.reset} ${C.bright}${formatNumber(epochData.absoluteSlot)}${C.reset}`);
339
+ }
340
+ console.log(` ${C.dim}SDK: getEpochInfo()${C.reset}`);
341
+ console.log();
342
+ }
343
+
344
+ // Supply info via SDK
345
+ if (supplyData) {
346
+ console.log(` ${C.bright}── Token Supply ────────────────────────────────────────────${C.reset}`);
347
+ const totalAETH = supplyData.total ? (Number(supplyData.total) / 1e9).toFixed(2) : 'N/A';
348
+ const circAETH = supplyData.circulating ? (Number(supplyData.circulating) / 1e9).toFixed(2) : 'N/A';
349
+ console.log(` ${C.dim}Total Supply:${C.reset} ${C.green}${totalAETH} AETH${C.reset}`);
350
+ console.log(` ${C.dim}Circulating:${C.reset} ${C.cyan}${circAETH} AETH${C.reset}`);
351
+ console.log(` ${C.dim}SDK: getSupply()${C.reset}`);
352
+ console.log();
353
+ }
354
+
355
+ // Version info via SDK
356
+ if (versionData) {
357
+ const ver = versionData.aetherCore || versionData.version || versionData.solana_core;
358
+ if (ver) {
359
+ console.log(` ${C.bright}── Node Version ───────────────────────────────────────────${C.reset}`);
360
+ console.log(` ${C.dim}Version:${C.reset} ${C.green}${ver}${C.reset}`);
361
+ console.log(` ${C.dim}SDK: getVersion()${C.reset}`);
362
+ console.log();
363
+ }
364
+ }
365
+
366
+ // Tips
367
+ if (status.diff === 0 || status.diff === undefined) {
368
+ console.log(` ${C.green}✓ Node is fully synced with the network.${C.reset}`);
369
+ } else if (status.diff > 0) {
370
+ console.log(` ${C.yellow}⏳ Node is catching up — this is normal on first start or after a restart.${C.reset}`);
371
+ console.log(` ${C.dim} For faster sync, try downloading a recent snapshot from a peer.${C.reset}`);
372
+ console.log(` ${C.dim} Check: aether network --peers${C.reset}`);
373
+ }
374
+ console.log();
375
+ console.log(` ${C.dim}Tip: --watch for live view | --json for scripting${C.reset}`);
376
+ console.log(` ${C.dim}SDK Methods: getSlot(), getBlockHeight(), getEpochInfo(), getHealth(), getVersion()${C.reset}`);
377
+ console.log();
378
+ }
379
+
380
+ // ---------------------------------------------------------------------------
381
+ // Watch mode
382
+ // ---------------------------------------------------------------------------
383
+
384
+ async function watchMode(rpc) {
385
+ const readline = require('readline');
386
+ let running = true;
387
+
388
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
389
+ const drainStdin = () => {
390
+ rl.close();
391
+ running = false;
392
+ };
393
+ process.stdin.on('data', drainStdin);
394
+ process.stdin.resume();
395
+
396
+ console.log(` ${C.cyan}Live sync monitoring started.${C.reset} ${C.dim}Press Ctrl+C to stop.${C.reset}\n`);
397
+
398
+ while (running) {
399
+ // Move cursor up and clear lines for clean refresh
400
+ process.stdout.write('\x1b[2J\x1b[H');
401
+
402
+ try {
403
+ // All SDK calls
404
+ const [nodeSlot, blockHeight, epochData, versionData, healthData, supplyData] =
405
+ await Promise.all([
406
+ getSlot(rpc),
407
+ getBlockHeight(rpc),
408
+ getEpoch(rpc),
409
+ getVersion(rpc),
410
+ getHealth(rpc),
411
+ getSupply(rpc),
412
+ ]);
413
+
414
+ // For network slot, we use the same RPC (in real scenario, could be different)
415
+ const networkSlot = nodeSlot;
416
+
417
+ renderSync({ nodeSlot, networkSlot, blockHeight, epochData, versionData, healthData, supplyData, asJson: false }, rpc);
418
+ } catch (err) {
419
+ console.log(` ${C.red}✗ Error fetching data:${C.reset} ${err.message}`);
420
+ }
421
+
422
+ if (!running) break;
423
+ await new Promise((res) => setTimeout(res, REFRESH_INTERVAL_MS));
424
+ }
425
+
426
+ process.stdin.pause();
427
+ process.stdin.off('data', drainStdin);
428
+ console.log(`\n ${C.dim}Stopped.${C.reset}\n`);
429
+ }
430
+
431
+ // ---------------------------------------------------------------------------
432
+ // Main
433
+ // ---------------------------------------------------------------------------
434
+
435
+ async function snapshotCommand() {
436
+ const opts = parseArgs();
437
+ const rpc = opts.rpc;
438
+
439
+ if (opts.watch) {
440
+ await watchMode(rpc);
441
+ return;
442
+ }
443
+
444
+ // All SDK calls in parallel
445
+ const [nodeSlot, blockHeight, epochData, versionData, healthData, supplyData] =
446
+ await Promise.all([
447
+ getSlot(rpc),
448
+ getBlockHeight(rpc),
449
+ getEpoch(rpc),
450
+ getVersion(rpc),
451
+ getHealth(rpc),
452
+ getSupply(rpc),
453
+ ]);
454
+
455
+ // For single-RPC mode, node and network are the same
456
+ const networkSlot = nodeSlot;
457
+
458
+ renderSync({ nodeSlot, networkSlot, blockHeight, epochData, versionData, healthData, supplyData, asJson: opts.asJson }, rpc);
459
+ }
460
+
461
+ module.exports = { snapshotCommand };
462
+
463
+ if (require.main === module) {
464
+ snapshotCommand().catch((err) => {
465
+ console.error(`\n${C.red}✗ Snapshot command failed:${C.reset} ${err.message}`);
466
+ console.error(` ${C.dim}Check that your validator is running and RPC is accessible.${C.reset}`);
467
+ console.error(` ${C.dim}Set custom RPC: AETHER_RPC=http://your-rpc-url${C.reset}\n`);
468
+ process.exit(1);
469
+ });
470
+ }