@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.
- package/LICENSE +21 -0
- package/README.md +110 -0
- package/aether-cli-1.0.0.tgz +0 -0
- package/aether-cli-1.8.0.tgz +0 -0
- package/aether-hub-1.0.5.tgz +0 -0
- package/aether-hub-1.1.8.tgz +0 -0
- package/aether-hub-1.2.1.tgz +0 -0
- package/commands/account.js +280 -0
- package/commands/apy.js +499 -0
- package/commands/balance.js +241 -0
- package/commands/blockhash.js +181 -0
- package/commands/broadcast.js +387 -0
- package/commands/claim.js +490 -0
- package/commands/config.js +851 -0
- package/commands/delegations.js +582 -0
- package/commands/doctor.js +769 -0
- package/commands/emergency.js +667 -0
- package/commands/epoch.js +275 -0
- package/commands/fees.js +276 -0
- package/commands/index.js +78 -0
- package/commands/info.js +495 -0
- package/commands/init.js +816 -0
- package/commands/install.js +666 -0
- package/commands/kyc.js +272 -0
- package/commands/logs.js +315 -0
- package/commands/monitor.js +431 -0
- package/commands/multisig.js +701 -0
- package/commands/network.js +429 -0
- package/commands/nft.js +857 -0
- package/commands/ping.js +266 -0
- package/commands/price.js +253 -0
- package/commands/rewards.js +931 -0
- package/commands/sdk-test.js +477 -0
- package/commands/sdk.js +656 -0
- package/commands/slot.js +155 -0
- package/commands/snapshot.js +470 -0
- package/commands/stake-info.js +139 -0
- package/commands/stake-positions.js +205 -0
- package/commands/stake.js +516 -0
- package/commands/stats.js +396 -0
- package/commands/status.js +327 -0
- package/commands/supply.js +391 -0
- package/commands/tps.js +238 -0
- package/commands/transfer.js +495 -0
- package/commands/tx-history.js +346 -0
- package/commands/unstake.js +597 -0
- package/commands/validator-info.js +657 -0
- package/commands/validator-register.js +593 -0
- package/commands/validator-start.js +323 -0
- package/commands/validator-status.js +227 -0
- package/commands/validators.js +626 -0
- package/commands/wallet.js +1570 -0
- package/index.js +593 -0
- package/lib/errors.js +398 -0
- package/package.json +76 -0
- package/sdk/README.md +210 -0
- package/sdk/index.js +1639 -0
- package/sdk/package.json +34 -0
- package/sdk/rpc.js +254 -0
- package/sdk/test.js +85 -0
- package/test/doctor.test.js +76 -0
- 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
|
+
}
|