aether-hub 1.0.3
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/commands/doctor.js +720 -0
- package/commands/init.js +685 -0
- package/commands/logs.js +315 -0
- package/commands/monitor.js +431 -0
- package/commands/sdk.js +381 -0
- package/commands/validator-start.js +290 -0
- package/commands/validator-status.js +268 -0
- package/index.js +275 -0
- package/package.json +51 -0
- package/test/doctor.test.js +76 -0
package/commands/sdk.js
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* aether-cli sdk
|
|
3
|
+
*
|
|
4
|
+
* Provides download links and install instructions for the Aether SDK,
|
|
5
|
+
* Aether JS client, and FLUX/ATH token libraries.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* aether-cli sdk # Show all SDK options
|
|
9
|
+
* aether-cli sdk js # Aether JS client
|
|
10
|
+
* aether-cli sdk rust # Aether Rust SDK
|
|
11
|
+
* aether-cli sdk tokens # FLUX/ATH token libraries
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const os = require('os');
|
|
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
|
+
magenta: '\x1b[35m',
|
|
26
|
+
blue: '\x1b[34m',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Print the SDK banner
|
|
31
|
+
*/
|
|
32
|
+
function printBanner() {
|
|
33
|
+
console.log(`
|
|
34
|
+
${colors.cyan}╔═══════════════════════════════════════════════════════════════╗
|
|
35
|
+
${colors.cyan}║ ║
|
|
36
|
+
${colors.cyan}║ ${colors.bright}AETHER SDK${colors.reset}${colors.cyan} ║
|
|
37
|
+
${colors.cyan}║ ${colors.bright}Developer Tools & Libraries${colors.reset}${colors.cyan} ║
|
|
38
|
+
${colors.cyan}║ ║
|
|
39
|
+
${colors.cyan}╚═══════════════════════════════════════════════════════════════╝${colors.reset}
|
|
40
|
+
`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Print a section header
|
|
45
|
+
*/
|
|
46
|
+
function printSection(title, icon = '📦') {
|
|
47
|
+
console.log();
|
|
48
|
+
console.log(`${colors.bright}${colors.cyan}${'═'.repeat(60)}${colors.reset}`);
|
|
49
|
+
console.log(`${colors.bright} ${icon} ${title}${colors.reset}`);
|
|
50
|
+
console.log(`${colors.bright}${colors.cyan}${'═'.repeat(60)}${colors.reset}`);
|
|
51
|
+
console.log();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Print a code block
|
|
56
|
+
*/
|
|
57
|
+
function printCode(code, lang = 'bash') {
|
|
58
|
+
console.log(` ${colors.dim}[ ${lang} ]${colors.reset}`);
|
|
59
|
+
console.log(` ${colors.bright}${code}${colors.reset}`);
|
|
60
|
+
console.log();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Print a link
|
|
65
|
+
*/
|
|
66
|
+
function printLink(label, url) {
|
|
67
|
+
console.log(` ${colors.cyan}🔗 ${label}:${colors.reset}`);
|
|
68
|
+
console.log(` ${colors.blue}${url}${colors.reset}`);
|
|
69
|
+
console.log();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Show all SDK options
|
|
74
|
+
*/
|
|
75
|
+
function showAllSdks() {
|
|
76
|
+
printBanner();
|
|
77
|
+
|
|
78
|
+
console.log(` ${colors.bright}Available SDKs and Libraries:${colors.reset}\n`);
|
|
79
|
+
|
|
80
|
+
console.log(` ${colors.yellow}npm${colors.reset} aether-cli sdk js - JavaScript/TypeScript client`);
|
|
81
|
+
console.log(` ${colors.yellow}npm${colors.reset} aether-cli sdk rust - Rust SDK for native development`);
|
|
82
|
+
console.log(` ${colors.yellow}npm${colors.reset} aether-cli sdk tokens - FLUX/ATH token libraries`);
|
|
83
|
+
console.log(` ${colors.yellow}npm${colors.reset} aether-cli sdk docs - Documentation portal`);
|
|
84
|
+
console.log();
|
|
85
|
+
|
|
86
|
+
// Quick start
|
|
87
|
+
printSection('⚡ Quick Start', '🚀');
|
|
88
|
+
console.log(' Get started with Aether development in 3 steps:\n');
|
|
89
|
+
console.log(` 1. ${colors.bright}Install the JS client:${colors.reset}`);
|
|
90
|
+
printCode('npm install @aether-network/client');
|
|
91
|
+
console.log(` 2. ${colors.bright}Initialize your connection:${colors.reset}`);
|
|
92
|
+
printCode('const aether = require(\'@aether-network/client\');\nconst client = new aether.Client({ rpcUrl: \'http://localhost:8899\' });');
|
|
93
|
+
console.log(` 3. ${colors.bright}Start building!${colors.reset}`);
|
|
94
|
+
console.log(` ${colors.dim}See docs for full API reference${colors.reset}`);
|
|
95
|
+
console.log();
|
|
96
|
+
|
|
97
|
+
printLink('Documentation', 'https://docs.aether.network');
|
|
98
|
+
printLink('GitHub Organization', 'https://github.com/aether-network');
|
|
99
|
+
printLink('Discord Community', 'https://discord.gg/aether');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Show JavaScript SDK info
|
|
104
|
+
*/
|
|
105
|
+
function showJsSdk() {
|
|
106
|
+
printSection('Aether JavaScript Client', '📜');
|
|
107
|
+
|
|
108
|
+
console.log(` ${colors.bright}The official Aether JavaScript/TypeScript client library.${colors.reset}`);
|
|
109
|
+
console.log(` Provides a simple API for interacting with the Aether blockchain.${colors.reset}\n`);
|
|
110
|
+
|
|
111
|
+
console.log(` ${colors.green}✓ Stable Release${colors.reset}`);
|
|
112
|
+
console.log(` ${colors.dim}Version: 1.2.0${colors.reset}`);
|
|
113
|
+
console.log();
|
|
114
|
+
|
|
115
|
+
printSection('Installation');
|
|
116
|
+
printCode('npm install @aether-network/client');
|
|
117
|
+
console.log(' or');
|
|
118
|
+
printCode('yarn add @aether-network/client');
|
|
119
|
+
console.log(' or');
|
|
120
|
+
printCode('pnpm add @aether-network/client');
|
|
121
|
+
|
|
122
|
+
printSection('Usage Example');
|
|
123
|
+
const example = `const aether = require('@aether-network/client');
|
|
124
|
+
|
|
125
|
+
// Initialize client
|
|
126
|
+
const client = new aether.Client({
|
|
127
|
+
rpcUrl: 'http://localhost:8899',
|
|
128
|
+
wsUrl: 'ws://localhost:8900',
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Get slot info
|
|
132
|
+
const slot = await client.getSlot();
|
|
133
|
+
console.log('Current slot:', slot);
|
|
134
|
+
|
|
135
|
+
// Get balance
|
|
136
|
+
const balance = await client.getBalance('pubkey...');
|
|
137
|
+
console.log('Balance:', balance);
|
|
138
|
+
|
|
139
|
+
// Send transaction
|
|
140
|
+
const tx = await client.sendTransaction({
|
|
141
|
+
from: 'sender-pubkey',
|
|
142
|
+
to: 'recipient-pubkey',
|
|
143
|
+
amount: 1000,
|
|
144
|
+
});
|
|
145
|
+
console.log('Transaction signature:', tx.signature);`;
|
|
146
|
+
|
|
147
|
+
console.log(` ${colors.dim}[ javascript ]${colors.reset}`);
|
|
148
|
+
example.split('\n').forEach(line => {
|
|
149
|
+
console.log(` ${colors.bright}${line}${colors.reset}`);
|
|
150
|
+
});
|
|
151
|
+
console.log();
|
|
152
|
+
|
|
153
|
+
printSection('API Reference');
|
|
154
|
+
console.log(` ${colors.cyan}Core Methods:${colors.reset}`);
|
|
155
|
+
console.log(` • getSlot() - Get current slot number`);
|
|
156
|
+
console.log(` • getBalance(pubkey) - Get account balance in lamports`);
|
|
157
|
+
console.log(` • getAccountInfo(pubkey) - Get full account information`);
|
|
158
|
+
console.log(` • sendTransaction(tx) - Send a signed transaction`);
|
|
159
|
+
console.log(` • confirmTransaction(sig) - Wait for transaction confirmation`);
|
|
160
|
+
console.log(` • getProgramAccounts(...) - Query accounts by program`);
|
|
161
|
+
console.log();
|
|
162
|
+
|
|
163
|
+
printLink('NPM Package', 'https://www.npmjs.com/package/@aether-network/client');
|
|
164
|
+
printLink('TypeScript Docs', 'https://docs.aether.network/sdk/js');
|
|
165
|
+
printLink('GitHub Repo', 'https://github.com/aether-network/aether-js');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Show Rust SDK info
|
|
170
|
+
*/
|
|
171
|
+
function showRustSdk() {
|
|
172
|
+
printSection('Aether Rust SDK', '🦀');
|
|
173
|
+
|
|
174
|
+
console.log(` ${colors.bright}Native Rust SDK for building Aether programs and clients.${colors.reset}`);
|
|
175
|
+
console.log(` Use this for validator plugins, custom programs, and high-performance tools.${colors.reset}\n`);
|
|
176
|
+
|
|
177
|
+
console.log(` ${colors.green}✓ Stable Release${colors.reset}`);
|
|
178
|
+
console.log(` ${colors.dim}Version: 1.2.0${colors.reset}`);
|
|
179
|
+
console.log();
|
|
180
|
+
|
|
181
|
+
printSection('Installation');
|
|
182
|
+
printCode('cargo add aether-sdk');
|
|
183
|
+
console.log(' or add to your Cargo.toml:');
|
|
184
|
+
console.log();
|
|
185
|
+
console.log(` ${colors.dim}${colors.bgRed}toml${colors.reset}`);
|
|
186
|
+
console.log(` ${colors.bright}[dependencies]${colors.reset}`);
|
|
187
|
+
console.log(` ${colors.bright}aether-sdk = "1.2"${colors.reset}`);
|
|
188
|
+
console.log();
|
|
189
|
+
|
|
190
|
+
printSection('Usage Example');
|
|
191
|
+
const rustExample = `use aether_sdk::{client::Client, pubkey::Pubkey};
|
|
192
|
+
|
|
193
|
+
#[tokio::main]
|
|
194
|
+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
195
|
+
// Initialize client
|
|
196
|
+
let client = Client::new("http://localhost:8899");
|
|
197
|
+
|
|
198
|
+
// Get slot
|
|
199
|
+
let slot = client.get_slot().await?;
|
|
200
|
+
println!("Current slot: {}", slot);
|
|
201
|
+
|
|
202
|
+
// Get balance
|
|
203
|
+
let pubkey = Pubkey::from_str("...")?;
|
|
204
|
+
let balance = client.get_balance(&pubkey).await?;
|
|
205
|
+
println!("Balance: {} lamports", balance);
|
|
206
|
+
|
|
207
|
+
Ok(())
|
|
208
|
+
}`;
|
|
209
|
+
|
|
210
|
+
console.log(` ${colors.dim}${colors.bgRed}rust${colors.reset}`);
|
|
211
|
+
rustExample.split('\n').forEach(line => {
|
|
212
|
+
console.log(` ${colors.bright}${line}${colors.reset}`);
|
|
213
|
+
});
|
|
214
|
+
console.log();
|
|
215
|
+
|
|
216
|
+
printSection('Features');
|
|
217
|
+
console.log(` ${colors.cyan}• Full RPC client${colors.reset}`);
|
|
218
|
+
console.log(` ${colors.cyan}• Program development framework${colors.reset}`);
|
|
219
|
+
console.log(` ${colors.cyan}• Account serialization/deserialization${colors.reset}`);
|
|
220
|
+
console.log(` ${colors.cyan}• Transaction building and signing${colors.reset}`);
|
|
221
|
+
console.log(` ${colors.cyan}• Async runtime support (tokio)${colors.reset}`);
|
|
222
|
+
console.log();
|
|
223
|
+
|
|
224
|
+
printLink('Crates.io', 'https://crates.io/crates/aether-sdk');
|
|
225
|
+
printLink('API Docs', 'https://docs.rs/aether-sdk');
|
|
226
|
+
printLink('GitHub Repo', 'https://github.com/aether-network/aether-rust');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Show token libraries info
|
|
231
|
+
*/
|
|
232
|
+
function showTokensSdk() {
|
|
233
|
+
printSection('FLUX / ATH Token Libraries', '🪙');
|
|
234
|
+
|
|
235
|
+
console.log(` ${colors.bright}Token libraries for FLUX (utility) and ATH (governance) tokens.${colors.reset}`);
|
|
236
|
+
console.log(` Use these to integrate Aether tokens into your applications.${colors.reset}\n`);
|
|
237
|
+
|
|
238
|
+
console.log(` ${colors.yellow}⚠ Beta Release${colors.reset}`);
|
|
239
|
+
console.log(` ${colors.dim}Version: 0.9.0 (testnet only)${colors.reset}`);
|
|
240
|
+
console.log();
|
|
241
|
+
|
|
242
|
+
printSection('Installation');
|
|
243
|
+
console.log(` ${colors.bright}JavaScript:${colors.reset}`);
|
|
244
|
+
printCode('npm install @aether-network/tokens');
|
|
245
|
+
console.log();
|
|
246
|
+
console.log(` ${colors.bright}Rust:${colors.reset}`);
|
|
247
|
+
printCode('cargo add aether-tokens');
|
|
248
|
+
|
|
249
|
+
printSection('Supported Tokens');
|
|
250
|
+
console.log();
|
|
251
|
+
console.log(` ${colors.magenta}FLUX${colors.reset} - Utility Token`);
|
|
252
|
+
console.log(` • Purpose: Transaction fees, staking rewards`);
|
|
253
|
+
console.log(` • Decimals: 9`);
|
|
254
|
+
console.log(` • Mint: ${colors.dim}flux7x... (testnet)${colors.reset}`);
|
|
255
|
+
console.log();
|
|
256
|
+
console.log(` ${colors.blue}ATH${colors.reset} - Governance Token`);
|
|
257
|
+
console.log(` • Purpose: Voting, protocol upgrades`);
|
|
258
|
+
console.log(` • Decimals: 6`);
|
|
259
|
+
console.log(` • Mint: ${colors.dim}athgov... (testnet)${colors.reset}`);
|
|
260
|
+
console.log();
|
|
261
|
+
|
|
262
|
+
printSection('Usage Example (JavaScript)');
|
|
263
|
+
const tokenExample = `const { TokenClient, TOKENS } = require('@aether-network/tokens');
|
|
264
|
+
|
|
265
|
+
const client = new TokenClient(rpcUrl);
|
|
266
|
+
|
|
267
|
+
// Get FLUX balance
|
|
268
|
+
const fluxBalance = await client.getTokenBalance(pubkey, TOKENS.FLUX);
|
|
269
|
+
console.log('FLUX:', fluxBalance);
|
|
270
|
+
|
|
271
|
+
// Transfer FLUX
|
|
272
|
+
const tx = await client.transfer({
|
|
273
|
+
mint: TOKENS.FLUX,
|
|
274
|
+
from: senderPubkey,
|
|
275
|
+
to: recipientPubkey,
|
|
276
|
+
amount: 1000,
|
|
277
|
+
});`;
|
|
278
|
+
|
|
279
|
+
console.log(` ${colors.dim}${colors.bgRed}javascript${colors.reset}`);
|
|
280
|
+
tokenExample.split('\n').forEach(line => {
|
|
281
|
+
console.log(` ${colors.bright}${line}${colors.reset}`);
|
|
282
|
+
});
|
|
283
|
+
console.log();
|
|
284
|
+
|
|
285
|
+
printLink('Token Documentation', 'https://docs.aether.network/tokens');
|
|
286
|
+
printLink('Token Registry', 'https://github.com/aether-network/token-registry');
|
|
287
|
+
printLink('Testnet Faucet', 'https://faucet.aether.network');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Show documentation portal info
|
|
292
|
+
*/
|
|
293
|
+
function showDocs() {
|
|
294
|
+
printSection('Aether Documentation', '📚');
|
|
295
|
+
|
|
296
|
+
console.log(` ${colors.bright}Comprehensive documentation for Aether developers.${colors.reset}\n`);
|
|
297
|
+
|
|
298
|
+
console.log(` ${colors.cyan}📖 Documentation Portal:${colors.reset}`);
|
|
299
|
+
console.log(` ${colors.blue}https://docs.aether.network${colors.reset}`);
|
|
300
|
+
console.log();
|
|
301
|
+
|
|
302
|
+
console.log(` ${colors.cyan}Sections:${colors.reset}`);
|
|
303
|
+
console.log(` • Getting Started - Quick start guides`);
|
|
304
|
+
console.log(` • Core Concepts - Accounts, programs, transactions`);
|
|
305
|
+
console.log(` • SDK Reference - Full API docs for JS and Rust`);
|
|
306
|
+
console.log(` • Tutorials - Step-by-step projects`);
|
|
307
|
+
console.log(` • Validator Guide - Running and maintaining validators`);
|
|
308
|
+
console.log(` • Economics - Staking, rewards, fees`);
|
|
309
|
+
console.log();
|
|
310
|
+
|
|
311
|
+
printLink('Main Docs', 'https://docs.aether.network');
|
|
312
|
+
printLink('API Reference', 'https://docs.aether.network/api');
|
|
313
|
+
printLink('Tutorials', 'https://docs.aether.network/tutorials');
|
|
314
|
+
printLink('Validator Docs', 'https://docs.aether.network/validators');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Parse command line args
|
|
319
|
+
*/
|
|
320
|
+
function parseArgs() {
|
|
321
|
+
const args = process.argv.slice(3); // Skip 'aether-cli sdk'
|
|
322
|
+
|
|
323
|
+
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
324
|
+
return 'all';
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const subcmd = args[0].toLowerCase();
|
|
328
|
+
|
|
329
|
+
switch (subcmd) {
|
|
330
|
+
case 'js':
|
|
331
|
+
case 'javascript':
|
|
332
|
+
case 'node':
|
|
333
|
+
return 'js';
|
|
334
|
+
case 'rust':
|
|
335
|
+
case 'rs':
|
|
336
|
+
return 'rust';
|
|
337
|
+
case 'tokens':
|
|
338
|
+
case 'token':
|
|
339
|
+
case 'flux':
|
|
340
|
+
case 'ath':
|
|
341
|
+
return 'tokens';
|
|
342
|
+
case 'docs':
|
|
343
|
+
case 'doc':
|
|
344
|
+
case 'documentation':
|
|
345
|
+
return 'docs';
|
|
346
|
+
default:
|
|
347
|
+
return 'all';
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Main SDK command
|
|
353
|
+
*/
|
|
354
|
+
function sdkCommand() {
|
|
355
|
+
const subcmd = parseArgs();
|
|
356
|
+
|
|
357
|
+
switch (subcmd) {
|
|
358
|
+
case 'js':
|
|
359
|
+
showJsSdk();
|
|
360
|
+
break;
|
|
361
|
+
case 'rust':
|
|
362
|
+
showRustSdk();
|
|
363
|
+
break;
|
|
364
|
+
case 'tokens':
|
|
365
|
+
showTokensSdk();
|
|
366
|
+
break;
|
|
367
|
+
case 'docs':
|
|
368
|
+
showDocs();
|
|
369
|
+
break;
|
|
370
|
+
default:
|
|
371
|
+
showAllSdks();
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Export for use as module
|
|
376
|
+
module.exports = { sdkCommand };
|
|
377
|
+
|
|
378
|
+
// Run if called directly
|
|
379
|
+
if (require.main === module) {
|
|
380
|
+
sdkCommand();
|
|
381
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
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
|
+
*/
|
|
68
|
+
function parseArgs() {
|
|
69
|
+
const args = process.argv.slice(3); // Skip 'aether-cli validator start'
|
|
70
|
+
|
|
71
|
+
const options = {
|
|
72
|
+
testnet: false,
|
|
73
|
+
rpcAddr: '127.0.0.1:8899',
|
|
74
|
+
p2pAddr: '0.0.0.0:8001',
|
|
75
|
+
identity: null,
|
|
76
|
+
verbose: false,
|
|
77
|
+
tier: 'full',
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
for (let i = 0; i < args.length; i++) {
|
|
81
|
+
switch (args[i]) {
|
|
82
|
+
case '--testnet':
|
|
83
|
+
options.testnet = true;
|
|
84
|
+
break;
|
|
85
|
+
case '--rpc-addr':
|
|
86
|
+
options.rpcAddr = args[++i];
|
|
87
|
+
break;
|
|
88
|
+
case '--p2p-addr':
|
|
89
|
+
options.p2pAddr = args[++i];
|
|
90
|
+
break;
|
|
91
|
+
case '--identity':
|
|
92
|
+
options.identity = args[++i];
|
|
93
|
+
break;
|
|
94
|
+
case '--tier':
|
|
95
|
+
options.tier = args[++i];
|
|
96
|
+
break;
|
|
97
|
+
case '-v':
|
|
98
|
+
case '--verbose':
|
|
99
|
+
options.verbose = true;
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return options;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Print startup banner
|
|
109
|
+
*/
|
|
110
|
+
function printBanner(options) {
|
|
111
|
+
const tierBadge = options.tier.toUpperCase();
|
|
112
|
+
const tierLabel = `[${tierBadge}]`;
|
|
113
|
+
|
|
114
|
+
console.log(`
|
|
115
|
+
${colors.cyan}╔═══════════════════════════════════════════════════════════════╗
|
|
116
|
+
${colors.cyan}║ ║
|
|
117
|
+
${colors.cyan}║ ${colors.bright}AETHER VALIDATOR${colors.reset}${colors.cyan} ║
|
|
118
|
+
${colors.cyan}║ ${colors.bright}Starting Validator Node${colors.reset}${colors.cyan} ║
|
|
119
|
+
${colors.cyan}║ ${colors.bright}${tierLabel}${colors.reset}${colors.cyan} ║
|
|
120
|
+
${colors.cyan}╚═══════════════════════════════════════════════════════════════╝${colors.reset}
|
|
121
|
+
`);
|
|
122
|
+
|
|
123
|
+
console.log(` ${colors.bright}Network:${colors.reset}`);
|
|
124
|
+
console.log(` Mode: ${options.testnet ? colors.yellow + 'TESTNET' : colors.green + 'MAINNET (not implemented)'}`);
|
|
125
|
+
console.log(` Tier: ${tierLabel}`);
|
|
126
|
+
console.log(` RPC: http://${options.rpcAddr}`);
|
|
127
|
+
console.log(` P2P: ${options.p2pAddr}`);
|
|
128
|
+
if (options.identity) {
|
|
129
|
+
console.log(` Identity: ${options.identity}`);
|
|
130
|
+
}
|
|
131
|
+
console.log();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Build the validator binary if missing
|
|
136
|
+
*/
|
|
137
|
+
function buildValidator() {
|
|
138
|
+
const { execSync } = require('child_process');
|
|
139
|
+
const workspaceRoot = path.join(__dirname, '..', '..');
|
|
140
|
+
const repoPath = path.join(workspaceRoot, 'Jelly-legs-unsteady-workshop');
|
|
141
|
+
|
|
142
|
+
console.log(` ${colors.cyan}Building aether-validator...${colors.reset}`);
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
execSync('cargo build --bin aether-validator', {
|
|
146
|
+
cwd: repoPath,
|
|
147
|
+
stdio: 'inherit',
|
|
148
|
+
shell: true,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Re-check for binary
|
|
152
|
+
const result = findValidatorBinary();
|
|
153
|
+
if (result.type === 'binary') {
|
|
154
|
+
console.log(` ${colors.green}✓ Build successful!${colors.reset}`);
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.error(` ${colors.red}✗ Build completed but binary not found${colors.reset}`);
|
|
159
|
+
return null;
|
|
160
|
+
} catch (err) {
|
|
161
|
+
console.error(` ${colors.red}✗ Build failed: ${err.message}${colors.reset}`);
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Main validator start command
|
|
168
|
+
*/
|
|
169
|
+
function validatorStart(overrideTier = null) {
|
|
170
|
+
const options = parseArgs();
|
|
171
|
+
|
|
172
|
+
// Allow tier override from init.js
|
|
173
|
+
if (overrideTier) {
|
|
174
|
+
options.tier = overrideTier;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let result = findValidatorBinary();
|
|
178
|
+
|
|
179
|
+
printBanner(options);
|
|
180
|
+
|
|
181
|
+
// Handle missing binary
|
|
182
|
+
if (result.type === 'missing') {
|
|
183
|
+
console.log(` ${colors.yellow}⚠ Validator binary not found${colors.reset}`);
|
|
184
|
+
console.log(` ${colors.cyan}Would you like to build it now? (cargo build --bin aether-validator)${colors.reset}`);
|
|
185
|
+
console.log();
|
|
186
|
+
|
|
187
|
+
const readline = require('readline');
|
|
188
|
+
const rl = readline.createInterface({
|
|
189
|
+
input: process.stdin,
|
|
190
|
+
output: process.stdout,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
rl.question(' Build now? [Y/n] ', (answer) => {
|
|
194
|
+
rl.close();
|
|
195
|
+
|
|
196
|
+
if (answer.toLowerCase() === 'n' || answer.toLowerCase() === 'no') {
|
|
197
|
+
console.log(` ${colors.red}Aborted. Build the validator first, then try again.${colors.reset}`);
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const built = buildValidator();
|
|
202
|
+
if (!built) {
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
result = built;
|
|
206
|
+
startValidatorProcess(result, options);
|
|
207
|
+
});
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
startValidatorProcess(result, options);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Spawn the validator process
|
|
216
|
+
*/
|
|
217
|
+
function startValidatorProcess({ type, path: binaryPath, inPath }, options) {
|
|
218
|
+
// Build command args
|
|
219
|
+
const validatorArgs = ['start'];
|
|
220
|
+
|
|
221
|
+
if (options.testnet) {
|
|
222
|
+
validatorArgs.push('--testnet');
|
|
223
|
+
}
|
|
224
|
+
validatorArgs.push('--tier', options.tier);
|
|
225
|
+
validatorArgs.push('--rpc-addr', options.rpcAddr);
|
|
226
|
+
validatorArgs.push('--p2p-addr', options.p2pAddr);
|
|
227
|
+
if (options.identity) {
|
|
228
|
+
validatorArgs.push('--identity', options.identity);
|
|
229
|
+
}
|
|
230
|
+
if (options.verbose) {
|
|
231
|
+
validatorArgs.push('-vvv');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const commandDisplay = inPath ? binaryPath : binaryPath || 'cargo run --bin aether-validator';
|
|
235
|
+
console.log(` ${colors.bright}Command:${colors.reset} ${commandDisplay} ${validatorArgs.join(' ')}`);
|
|
236
|
+
console.log();
|
|
237
|
+
console.log(` ${colors.yellow}Starting validator (press Ctrl+C to stop)...${colors.reset}`);
|
|
238
|
+
console.log();
|
|
239
|
+
|
|
240
|
+
// Determine working directory
|
|
241
|
+
const workspaceRoot = path.join(__dirname, '..', '..');
|
|
242
|
+
const repoPath = path.join(workspaceRoot, 'Jelly-legs-unsteady-workshop');
|
|
243
|
+
|
|
244
|
+
// Spawn the validator process
|
|
245
|
+
const child = inPath || binaryPath === 'aether-validator' || binaryPath === 'aether-validator.exe'
|
|
246
|
+
? spawn(binaryPath, validatorArgs, {
|
|
247
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
248
|
+
})
|
|
249
|
+
: spawn(binaryPath, validatorArgs, {
|
|
250
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
251
|
+
cwd: repoPath,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Colorize output
|
|
255
|
+
const outputColorizer = (data, isError = false) => {
|
|
256
|
+
const str = data.toString();
|
|
257
|
+
const color = isError ? colors.red : colors.reset;
|
|
258
|
+
process.stdout.write(`${color}${str}${colors.reset}`);
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
child.stdout.on('data', (data) => outputColorizer(data));
|
|
262
|
+
child.stderr.on('data', (data) => outputColorizer(data, true));
|
|
263
|
+
|
|
264
|
+
child.on('error', (err) => {
|
|
265
|
+
console.error(`${colors.red}Failed to start validator: ${err.message}${colors.reset}`);
|
|
266
|
+
process.exit(1);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
child.on('close', (code) => {
|
|
270
|
+
console.log(`\n${colors.yellow}Validator exited with code ${code}${colors.reset}`);
|
|
271
|
+
process.exit(code);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Handle Ctrl+C
|
|
275
|
+
process.on('SIGINT', () => {
|
|
276
|
+
console.log(`\n${colors.yellow}Shutting down validator...${colors.reset}`);
|
|
277
|
+
child.kill('SIGINT');
|
|
278
|
+
setTimeout(() => {
|
|
279
|
+
child.kill('SIGTERM');
|
|
280
|
+
}, 1000);
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Export for use as module
|
|
285
|
+
module.exports = { validatorStart };
|
|
286
|
+
|
|
287
|
+
// Run if called directly
|
|
288
|
+
if (require.main === module) {
|
|
289
|
+
validatorStart();
|
|
290
|
+
}
|