@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,323 @@
1
+ /**
2
+ * aether-cli validator-start
3
+ *
4
+ * Spawns the aether-validator binary as a child process.
5
+ * Handles startup, logging, and graceful shutdown.
6
+ */
7
+
8
+ const { spawn } = require('child_process');
9
+ const path = require('path');
10
+ const fs = require('fs');
11
+ const os = require('os');
12
+
13
+ // ANSI colors
14
+ const colors = {
15
+ reset: '\x1b[0m',
16
+ bright: '\x1b[1m',
17
+ green: '\x1b[32m',
18
+ yellow: '\x1b[33m',
19
+ cyan: '\x1b[36m',
20
+ red: '\x1b[31m',
21
+ };
22
+
23
+ /**
24
+ * Find the aether-validator binary
25
+ * Searches common locations based on OS and repo layout
26
+ */
27
+ function findValidatorBinary() {
28
+ const platform = os.platform();
29
+ const isWindows = platform === 'win32';
30
+ const binaryName = isWindows ? 'aether-validator.exe' : 'aether-validator';
31
+
32
+ // Check common locations
33
+ const locations = [
34
+ // Sibling repo: Jelly-legs-unsteady-workshop/target/debug/
35
+ path.join(__dirname, '..', '..', 'Jelly-legs-unsteady-workshop', 'target', 'debug', binaryName),
36
+ path.join(__dirname, '..', '..', 'Jelly-legs-unsteady-workshop', 'target', 'release', binaryName),
37
+ // Local build in aether-cli (if someone built here)
38
+ path.join(__dirname, '..', 'target', 'debug', binaryName),
39
+ path.join(__dirname, '..', 'target', 'release', binaryName),
40
+ // System PATH
41
+ 'aether-validator' + (isWindows ? '.exe' : ''),
42
+ ];
43
+
44
+ for (const loc of locations) {
45
+ if (loc.startsWith('aether-validator')) {
46
+ // Check if it's in PATH
47
+ try {
48
+ const { execSync } = require('child_process');
49
+ const checkCmd = isWindows ? 'where' : 'which';
50
+ execSync(`${checkCmd} ${loc}`, { stdio: 'pipe' });
51
+ return { type: 'binary', path: loc, inPath: true };
52
+ } catch {
53
+ // Not in PATH, continue
54
+ }
55
+ }
56
+ if (fs.existsSync(loc)) {
57
+ return { type: 'binary', path: loc };
58
+ }
59
+ }
60
+
61
+ // Binary not found - offer to build it
62
+ return { type: 'missing', path: null };
63
+ }
64
+
65
+ /**
66
+ * Parse command line args for validator-start
67
+ * @param {Object} overrideOptions - Options passed directly (e.g. from init.js)
68
+ */
69
+ function parseArgs(overrideOptions = {}) {
70
+ const args = process.argv.slice(3); // Skip 'aether-cli validator start'
71
+
72
+ const options = {
73
+ testnet: false,
74
+ rpcAddr: '127.0.0.1:8899',
75
+ p2pAddr: '0.0.0.0:8001',
76
+ identity: null,
77
+ verbose: false,
78
+ tier: 'full',
79
+ ...overrideOptions, // Allow init.js to pass testnet/tier directly
80
+ };
81
+
82
+ for (let i = 0; i < args.length; i++) {
83
+ switch (args[i]) {
84
+ case '--testnet':
85
+ options.testnet = true;
86
+ break;
87
+ case '--rpc-addr':
88
+ options.rpcAddr = args[++i];
89
+ break;
90
+ case '--p2p-addr':
91
+ options.p2pAddr = args[++i];
92
+ break;
93
+ case '--identity':
94
+ options.identity = args[++i];
95
+ break;
96
+ case '--tier':
97
+ options.tier = args[++i];
98
+ break;
99
+ case '-v':
100
+ case '--verbose':
101
+ options.verbose = true;
102
+ break;
103
+ }
104
+ }
105
+
106
+ return options;
107
+ }
108
+
109
+ /**
110
+ * Print startup banner
111
+ */
112
+ function printBanner(options) {
113
+ const tierBadge = options.tier.toUpperCase();
114
+ const tierLabel = `[${tierBadge}]`;
115
+
116
+ console.log(`
117
+ ${colors.cyan}╔═══════════════════════════════════════════════════════════════╗
118
+ ${colors.cyan}║ ║
119
+ ${colors.cyan}║ ${colors.bright}AETHER VALIDATOR${colors.reset}${colors.cyan} ║
120
+ ${colors.cyan}║ ${colors.bright}Starting Validator Node${colors.reset}${colors.cyan} ║
121
+ ${colors.cyan}║ ${colors.bright}${tierLabel}${colors.reset}${colors.cyan} ║
122
+ ${colors.cyan}╚═══════════════════════════════════════════════════════════════╝${colors.reset}
123
+ `);
124
+
125
+ console.log(` ${colors.bright}Network:${colors.reset}`);
126
+ const modeStr = options.testnet
127
+ ? colors.yellow + 'TESTNET'
128
+ : colors.red + 'MAINNET';
129
+ console.log(` Mode: ${modeStr}`);
130
+ console.log(` Tier: ${tierLabel}`);
131
+ console.log(` RPC: http://${options.rpcAddr}`);
132
+ console.log(` P2P: ${options.p2pAddr}`);
133
+ if (options.identity) {
134
+ console.log(` Identity: ${options.identity}`);
135
+ }
136
+ console.log();
137
+ }
138
+
139
+ /**
140
+ * Build the validator binary if missing
141
+ */
142
+ function buildValidator() {
143
+ const { execSync } = require('child_process');
144
+ const platform = os.platform();
145
+ const isWindows = platform === 'win32';
146
+ const workspaceRoot = path.join(__dirname, '..', '..');
147
+ const repoPath = path.join(workspaceRoot, 'Jelly-legs-unsteady-workshop');
148
+
149
+ console.log(` ${colors.cyan}Building aether-validator...${colors.reset}`);
150
+
151
+ try {
152
+ // Use full cargo path on Windows to avoid spawnSync ENOENT
153
+ const cargoPaths = isWindows
154
+ ? [
155
+ path.join(process.env.USERPROFILE || '', '.cargo', 'bin', 'cargo.exe'),
156
+ path.join(process.env.LOCALAPPDATA || '', 'Rust', 'bin', 'cargo.exe'),
157
+ 'C:\\Users\\RM_Ga\\.cargo\\bin\\cargo.exe',
158
+ 'cargo',
159
+ ]
160
+ : ['cargo'];
161
+
162
+ let cargoCmd = 'cargo';
163
+ for (const cp of cargoPaths) {
164
+ if (cp === 'cargo' || fs.existsSync(cp)) {
165
+ cargoCmd = cp;
166
+ break;
167
+ }
168
+ }
169
+
170
+ console.log(` ${colors.cyan}Running: ${cargoCmd} build --release --package aether-validator${colors.reset}`);
171
+
172
+ // Use execSync WITHOUT shell:true — avoids Windows spawnSync cmd.exe ENOENT
173
+ execSync(`${cargoCmd} build --release --package aether-validator`, {
174
+ cwd: repoPath,
175
+ stdio: 'inherit',
176
+ });
177
+
178
+ // Re-check for binary
179
+ const result = findValidatorBinary();
180
+ if (result.type === 'binary') {
181
+ console.log(` ${colors.green}✓ Build successful!${colors.reset}`);
182
+ return result;
183
+ }
184
+
185
+ console.error(` ${colors.red}✗ Build completed but binary not found${colors.reset}`);
186
+ return null;
187
+ } catch (err) {
188
+ console.error(` ${colors.red}✗ Build failed: ${err.message}${colors.reset}`);
189
+ return null;
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Main validator start command
195
+ * @param {Object|null} options - { testnet?: boolean, tier?: string }
196
+ */
197
+ function validatorStart(options = {}) {
198
+ // Support both old string-style (tier only) and new object-style { testnet, tier }
199
+ const parsedArgs = parseArgs(typeof options === 'object' ? options : { tier: options });
200
+ const optionsObj = typeof options === 'object' ? options : {};
201
+
202
+ // Merge: explicit options override parseArgs defaults
203
+ const finalOptions = {
204
+ ...parsedArgs,
205
+ ...optionsObj,
206
+ tier: optionsObj.tier || parsedArgs.tier,
207
+ testnet: optionsObj.testnet !== undefined ? optionsObj.testnet : parsedArgs.testnet,
208
+ };
209
+
210
+ let result = findValidatorBinary();
211
+
212
+ printBanner(finalOptions);
213
+
214
+ // Handle missing binary
215
+ if (result.type === 'missing') {
216
+ console.log(` ${colors.yellow}⚠ Validator binary not found${colors.reset}`);
217
+ console.log(` ${colors.cyan}Would you like to build it now? (cargo build --bin aether-validator)${colors.reset}`);
218
+ console.log();
219
+
220
+ const readline = require('readline');
221
+ const rl = readline.createInterface({
222
+ input: process.stdin,
223
+ output: process.stdout,
224
+ });
225
+
226
+ rl.question(' Build now? [Y/n] ', (answer) => {
227
+ rl.close();
228
+
229
+ if (answer.toLowerCase() === 'n' || answer.toLowerCase() === 'no') {
230
+ console.log(` ${colors.red}Aborted. Build the validator first, then try again.${colors.reset}`);
231
+ process.exit(1);
232
+ }
233
+
234
+ const built = buildValidator();
235
+ if (!built) {
236
+ process.exit(1);
237
+ }
238
+ result = built;
239
+ startValidatorProcess(result, finalOptions);
240
+ });
241
+ return;
242
+ }
243
+
244
+ startValidatorProcess(result, finalOptions);
245
+ }
246
+
247
+ /**
248
+ * Spawn the validator process
249
+ */
250
+ function startValidatorProcess({ type, path: binaryPath, inPath }, options) {
251
+ // Build command args
252
+ const validatorArgs = ['start'];
253
+
254
+ if (options.testnet) {
255
+ validatorArgs.push('--testnet');
256
+ }
257
+ validatorArgs.push('--tier', options.tier.toLowerCase());
258
+ validatorArgs.push('--rpc-addr', options.rpcAddr);
259
+ validatorArgs.push('--p2p-addr', options.p2pAddr);
260
+ if (options.identity) {
261
+ validatorArgs.push('--identity', options.identity);
262
+ }
263
+ if (options.verbose) {
264
+ validatorArgs.push('-vvv');
265
+ }
266
+
267
+ const commandDisplay = inPath ? binaryPath : binaryPath || 'cargo run --bin aether-validator';
268
+ console.log(` ${colors.bright}Command:${colors.reset} ${commandDisplay} ${validatorArgs.join(' ')}`);
269
+ console.log();
270
+ console.log(` ${colors.yellow}Starting validator (press Ctrl+C to stop)...${colors.reset}`);
271
+ console.log();
272
+
273
+ // Determine working directory
274
+ const workspaceRoot = path.join(__dirname, '..', '..');
275
+ const repoPath = path.join(workspaceRoot, 'Jelly-legs-unsteady-workshop');
276
+
277
+ // Spawn the validator process
278
+ const child = inPath || binaryPath === 'aether-validator' || binaryPath === 'aether-validator.exe'
279
+ ? spawn(binaryPath, validatorArgs, {
280
+ stdio: ['inherit', 'pipe', 'pipe'],
281
+ })
282
+ : spawn(binaryPath, validatorArgs, {
283
+ stdio: ['inherit', 'pipe', 'pipe'],
284
+ cwd: repoPath,
285
+ });
286
+
287
+ // Colorize output
288
+ const outputColorizer = (data, isError = false) => {
289
+ const str = data.toString();
290
+ const color = isError ? colors.red : colors.reset;
291
+ process.stdout.write(`${color}${str}${colors.reset}`);
292
+ };
293
+
294
+ child.stdout.on('data', (data) => outputColorizer(data));
295
+ child.stderr.on('data', (data) => outputColorizer(data, true));
296
+
297
+ child.on('error', (err) => {
298
+ console.error(`${colors.red}Failed to start validator: ${err.message}${colors.reset}`);
299
+ process.exit(1);
300
+ });
301
+
302
+ child.on('close', (code) => {
303
+ console.log(`\n${colors.yellow}Validator exited with code ${code}${colors.reset}`);
304
+ process.exit(code);
305
+ });
306
+
307
+ // Handle Ctrl+C
308
+ process.on('SIGINT', () => {
309
+ console.log(`\n${colors.yellow}Shutting down validator...${colors.reset}`);
310
+ child.kill('SIGINT');
311
+ setTimeout(() => {
312
+ child.kill('SIGTERM');
313
+ }, 1000);
314
+ });
315
+ }
316
+
317
+ // Export for use as module
318
+ module.exports = { validatorStart };
319
+
320
+ // Run if called directly
321
+ if (require.main === module) {
322
+ validatorStart();
323
+ }
@@ -0,0 +1,227 @@
1
+ /**
2
+ * aether-cli validator-status
3
+ *
4
+ * Queries the validator's RPC endpoint and displays status information.
5
+ * Shows slot height, peer count, block production, and epoch info.
6
+ *
7
+ * Uses @jellylegsai/aether-sdk for real blockchain RPC calls.
8
+ */
9
+
10
+ const path = require('path');
11
+
12
+ // Import SDK for real blockchain RPC calls
13
+ const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
14
+ const aether = require(sdkPath);
15
+
16
+ // ANSI colors
17
+ const colors = {
18
+ reset: '\x1b[0m',
19
+ bright: '\x1b[1m',
20
+ green: '\x1b[32m',
21
+ yellow: '\x1b[33m',
22
+ cyan: '\x1b[36m',
23
+ red: '\x1b[31m',
24
+ dim: '\x1b[2m',
25
+ };
26
+
27
+ /**
28
+ * Create SDK client
29
+ */
30
+ function createClient(rpcUrl) {
31
+ return new aether.AetherClient({ rpcUrl });
32
+ }
33
+
34
+ /**
35
+ * Parse command line args
36
+ */
37
+ function parseArgs() {
38
+ const args = process.argv.slice(3); // Skip 'aether-cli validator status'
39
+
40
+ const options = {
41
+ rpcUrl: 'http://127.0.0.1:8899',
42
+ details: false,
43
+ json: false,
44
+ };
45
+
46
+ for (let i = 0; i < args.length; i++) {
47
+ switch (args[i]) {
48
+ case '--rpc-url':
49
+ options.rpcUrl = args[++i];
50
+ break;
51
+ case '-v':
52
+ case '--verbose':
53
+ case '--details':
54
+ options.details = true;
55
+ break;
56
+ case '--json':
57
+ options.json = true;
58
+ break;
59
+ }
60
+ }
61
+
62
+ return options;
63
+ }
64
+
65
+ /**
66
+ * Print the status display
67
+ */
68
+ function printStatus(status, options) {
69
+ const epochProgress = options.slotsInEpoch > 0
70
+ ? ((options.slotIndex / options.slotsInEpoch) * 100).toFixed(1)
71
+ : '0.0';
72
+
73
+ console.log();
74
+ console.log(`${colors.cyan}╔═══════════════════════════════════════════════════════════════╗`);
75
+ console.log(`${colors.cyan}║ ║`);
76
+ console.log(`${colors.cyan}║ ${colors.bright}AETHER VALIDATOR STATUS${colors.reset}${colors.cyan} ║`);
77
+ console.log(`${colors.cyan}║ ║`);
78
+ console.log(`${colors.cyan}╚═══════════════════════════════════════════════════════════════╝${colors.reset}`);
79
+ console.log();
80
+
81
+ console.log(` ${colors.bright}🌐 RPC Endpoint:${colors.reset} ${options.rpcUrl}`);
82
+ console.log();
83
+
84
+ // Check if connected
85
+ if (status.error) {
86
+ console.log(` ${colors.red}❌ Error:${colors.reset} ${status.error}`);
87
+ console.log();
88
+ console.log(` ${colors.yellow}Make sure the validator is running:${colors.reset}`);
89
+ console.log(` ${colors.bright}aether-cli validator start${colors.reset}`);
90
+ console.log();
91
+ return;
92
+ }
93
+
94
+ console.log(` ${colors.green}✅ Connected${colors.reset}`);
95
+ console.log();
96
+
97
+ console.log(` ${colors.bright}📊 Chain Status${colors.reset}`);
98
+ console.log(` ${colors.dim} ${'─'.repeat(55)}${colors.reset}`);
99
+ console.log(` Slot Height: ${colors.bright}${String(status.slot || 0).padStart(12)}${colors.reset}`);
100
+ console.log(` Block Height: ${colors.bright}${String(status.blockHeight || 0).padStart(12)}${colors.reset}`);
101
+ console.log(` Transaction Count: ${colors.bright}${String(status.transactionCount || 0).padStart(12)}${colors.reset}`);
102
+ console.log();
103
+
104
+ console.log(` ${colors.bright}🔗 Network${colors.reset}`);
105
+ console.log(` ${colors.dim} ${'─'.repeat(55)}${colors.reset}`);
106
+ console.log(` Peer Count: ${colors.bright}${String(status.peerCount || 0).padStart(12)}${colors.reset}`);
107
+ console.log();
108
+
109
+ console.log(` ${colors.bright}📈 Epoch ${status.epoch || 0}${colors.reset}`);
110
+ console.log(` ${colors.dim} ${'─'.repeat(55)}${colors.reset}`);
111
+ console.log(` Progress: ${colors.bright}${String(epochProgress + '%').padStart(12)}${colors.reset}`);
112
+ console.log(` Slot Index: ${colors.bright}${String(options.slotIndex || 0).padStart(12)}${colors.reset}`);
113
+ console.log(` Slots in Epoch: ${colors.bright}${String(options.slotsInEpoch || 0).padStart(12)}${colors.reset}`);
114
+ console.log();
115
+
116
+ if (options.details && status.blockProduction) {
117
+ const bp = status.blockProduction;
118
+ console.log(` ${colors.bright}📦 Block Production${colors.reset}`);
119
+ console.log(` ${colors.dim} ${'─'.repeat(55)}${colors.reset}`);
120
+ console.log(` Blocks Produced: ${colors.bright}${String(bp.blocksProduced || 0).padStart(12)}${colors.reset}`);
121
+ console.log(` Entries Produced: ${colors.bright}${String(bp.entriesProduced || 0).padStart(12)}${colors.reset}`);
122
+ console.log();
123
+ }
124
+
125
+ console.log(` ${colors.green}✓ Validator is running normally${colors.reset}`);
126
+ console.log();
127
+ }
128
+
129
+ /**
130
+ * Main status command
131
+ */
132
+ async function validatorStatus() {
133
+ const options = parseArgs();
134
+
135
+ let status = {
136
+ slot: 0,
137
+ blockHeight: 0,
138
+ transactionCount: 0,
139
+ peerCount: 0,
140
+ epoch: 0,
141
+ };
142
+
143
+ let epochInfo = {};
144
+ let blockProduction = {};
145
+
146
+ const client = createClient(options.rpcUrl);
147
+
148
+ try {
149
+ // Make parallel RPC calls using SDK
150
+ const [slotResult, blockHeightResult, epochInfoResult, peersResult] = await Promise.all([
151
+ client.getSlot().catch(e => ({ error: e.message })),
152
+ client.getBlockHeight().catch(e => ({ error: e.message })),
153
+ client.getEpochInfo().catch(e => ({})),
154
+ client.getClusterPeers().catch(e => ([])),
155
+ ]);
156
+
157
+ if (typeof slotResult === 'object' && slotResult.error) {
158
+ if (options.json) {
159
+ console.log(JSON.stringify({ error: slotResult.error }, null, 2));
160
+ process.exit(1);
161
+ }
162
+ console.log();
163
+ console.log(` ${colors.red}❌ Cannot connect to validator${colors.reset}`);
164
+ console.log(` ${colors.yellow}${slotResult.error}${colors.reset}`);
165
+ console.log();
166
+ console.log(` ${colors.bright}Start the validator first:${colors.reset}`);
167
+ console.log(` ${colors.cyan}aether-cli validator start${colors.reset}`);
168
+ console.log();
169
+ process.exit(1);
170
+ }
171
+
172
+ status.slot = typeof slotResult === 'number' ? slotResult : (slotResult.slot || 0);
173
+ status.blockHeight = typeof blockHeightResult === 'number' ? blockHeightResult : status.slot;
174
+ status.transactionCount = 0; // Transaction count not available via SDK
175
+ status.peerCount = Array.isArray(peersResult) ? peersResult.length : 0;
176
+
177
+ if (epochInfoResult && typeof epochInfoResult === 'object') {
178
+ epochInfo = epochInfoResult;
179
+ status.epoch = epochInfo.epoch || 0;
180
+ epochInfo.slotIndex = epochInfo.slotIndex || epochInfo.slot_index || 0;
181
+ epochInfo.slotsInEpoch = epochInfo.slotsInEpoch || epochInfo.slots_in_epoch || 432000;
182
+ }
183
+
184
+ if (options.details) {
185
+ try {
186
+ blockProduction = await client.getSlotProduction();
187
+ } catch { /* Block production not available */ }
188
+ }
189
+
190
+ if (options.json) {
191
+ console.log(JSON.stringify({
192
+ slot: status.slot,
193
+ blockHeight: status.blockHeight,
194
+ transactionCount: status.transactionCount,
195
+ peerCount: status.peerCount,
196
+ epoch: status.epoch,
197
+ epochInfo,
198
+ blockProduction,
199
+ }, null, 2));
200
+ } else {
201
+ printStatus(status, {
202
+ ...options,
203
+ ...epochInfo,
204
+ blockProduction,
205
+ });
206
+ }
207
+
208
+ } catch (err) {
209
+ if (options.json) {
210
+ console.log(JSON.stringify({ error: err.message }, null, 2));
211
+ } else {
212
+ console.log();
213
+ console.log(` ${colors.red}❌ Error querying validator${colors.reset}`);
214
+ console.log(` ${colors.yellow}${err.message}${colors.reset}`);
215
+ console.log();
216
+ }
217
+ process.exit(1);
218
+ }
219
+ }
220
+
221
+ // Export for use as module
222
+ module.exports = { validatorStatus };
223
+
224
+ // Run if called directly
225
+ if (require.main === module) {
226
+ validatorStatus();
227
+ }