@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,582 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * aether-cli delegations
4
+ *
5
+ * View and manage stake delegations for a wallet.
6
+ * Shows active delegations, validator info, and delegation history.
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 delegations list --address <addr> List all delegations
13
+ * aether delegations info --account <stakeAcct> Show detailed delegation info
14
+ * aether delegations create --validator <addr> --amount <aeth> Create new delegation
15
+ * aether delegations deactivate --account <stakeAcct> [--amount <aeth>] Begin unstake
16
+ * aether delegations withdraw --account <stakeAcct> Withdraw after cooldown
17
+ *
18
+ * SDK Methods Used:
19
+ * - client.getStakePositions(address) → GET /v1/stake/<addr>
20
+ * - client.getStakeAccounts(address) → GET /v1/stake-accounts/<addr>
21
+ * - client.getValidators() → GET /v1/validators
22
+ * - client.getEpochInfo() → GET /v1/epoch
23
+ * - client.getSlot() → GET /v1/slot
24
+ * - client.sendTransaction(tx) → POST /v1/transaction
25
+ */
26
+
27
+ const fs = require('fs');
28
+ const path = require('path');
29
+ const os = require('os');
30
+ const readline = require('readline');
31
+ const nacl = require('tweetnacl');
32
+ const bs58 = require('bs58').default;
33
+ const bip39 = require('bip39');
34
+
35
+ // Import SDK for ALL blockchain RPC calls
36
+ const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
37
+ const aether = require(sdkPath);
38
+
39
+ // ANSI colours
40
+ const C = {
41
+ reset: '\x1b[0m',
42
+ bright: '\x1b[1m',
43
+ dim: '\x1b[2m',
44
+ red: '\x1b[31m',
45
+ green: '\x1b[32m',
46
+ yellow: '\x1b[33m',
47
+ cyan: '\x1b[36m',
48
+ magenta: '\x1b[35m',
49
+ };
50
+
51
+ const CLI_VERSION = '1.0.0';
52
+ const DERIVATION_PATH = "m/44'/7777777'/0'/0'";
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // SDK Client Setup
56
+ // ---------------------------------------------------------------------------
57
+
58
+ function getDefaultRpc() {
59
+ return process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
60
+ }
61
+
62
+ function createClient(rpcUrl) {
63
+ return new aether.AetherClient({ rpcUrl });
64
+ }
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // Config & Paths
68
+ // ---------------------------------------------------------------------------
69
+
70
+ function getAetherDir() {
71
+ return path.join(os.homedir(), '.aether');
72
+ }
73
+
74
+ function getConfigPath() {
75
+ return path.join(getAetherDir(), 'config.json');
76
+ }
77
+
78
+ function loadConfig() {
79
+ if (!fs.existsSync(getConfigPath())) {
80
+ return { defaultWallet: null };
81
+ }
82
+ try {
83
+ return JSON.parse(fs.readFileSync(getConfigPath(), 'utf8'));
84
+ } catch {
85
+ return { defaultWallet: null };
86
+ }
87
+ }
88
+
89
+ function loadWallet(address) {
90
+ const fp = path.join(getAetherDir(), 'wallets', `${address}.json`);
91
+ if (!fs.existsSync(fp)) return null;
92
+ try {
93
+ return JSON.parse(fs.readFileSync(fp, 'utf8'));
94
+ } catch {
95
+ return null;
96
+ }
97
+ }
98
+
99
+ // ---------------------------------------------------------------------------
100
+ // Crypto Helpers
101
+ // ---------------------------------------------------------------------------
102
+
103
+ function deriveKeypair(mnemonic) {
104
+ if (!bip39.validateMnemonic(mnemonic)) {
105
+ throw new Error('Invalid mnemonic phrase');
106
+ }
107
+ const seedBuffer = bip39.mnemonicToSeedSync(mnemonic, '');
108
+ const seed32 = seedBuffer.slice(0, 32);
109
+ const keyPair = nacl.sign.keyPair.fromSeed(seed32);
110
+ return {
111
+ publicKey: Buffer.from(keyPair.publicKey),
112
+ secretKey: Buffer.from(keyPair.secretKey),
113
+ };
114
+ }
115
+
116
+ function formatAddress(publicKey) {
117
+ return 'ATH' + bs58.encode(publicKey);
118
+ }
119
+
120
+ function signTransaction(tx, secretKey) {
121
+ const txBytes = Buffer.from(JSON.stringify(tx));
122
+ const sig = nacl.sign.detached(txBytes, secretKey);
123
+ return bs58.encode(sig);
124
+ }
125
+
126
+ // ---------------------------------------------------------------------------
127
+ // Format Helpers
128
+ // ---------------------------------------------------------------------------
129
+
130
+ function formatAether(lamports) {
131
+ if (!lamports || lamports === '0') return '0 AETH';
132
+ const aeth = Number(lamports) / 1e9;
133
+ return aeth.toFixed(4).replace(/\.?0+$/, '') + ' AETH';
134
+ }
135
+
136
+ function shortAddress(addr) {
137
+ if (!addr || addr.length < 20) return addr || 'unknown';
138
+ return addr.slice(0, 8) + '...' + addr.slice(-8);
139
+ }
140
+
141
+ // ---------------------------------------------------------------------------
142
+ // SDK Data Fetchers (REAL RPC CALLS)
143
+ // ---------------------------------------------------------------------------
144
+
145
+ /**
146
+ * Fetch stake accounts for a wallet using SDK
147
+ * REAL RPC: GET /v1/stake-accounts/<address>
148
+ */
149
+ async function fetchStakeAccounts(rpcUrl, walletAddress) {
150
+ const client = createClient(rpcUrl);
151
+ const rawAddr = walletAddress.startsWith('ATH') ? walletAddress.slice(3) : walletAddress;
152
+
153
+ try {
154
+ const stakeAccounts = await client.getStakeAccounts(rawAddr);
155
+ if (!Array.isArray(stakeAccounts)) return [];
156
+
157
+ return stakeAccounts.map(s => ({
158
+ address: s.pubkey || s.publicKey || s.account || s.address,
159
+ validator: s.validator || s.delegate || s.vote_account,
160
+ lamports: s.lamports || s.stake_lamports || 0,
161
+ status: s.status || s.state || 'unknown',
162
+ activationEpoch: s.activation_epoch || s.activationEpoch,
163
+ deactivationEpoch: s.deactivation_epoch || s.deactivationEpoch,
164
+ rentExemptReserve: s.rent_exempt_reserve || 2282880,
165
+ })).filter(s => s.address);
166
+ } catch (err) {
167
+ return [];
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Fetch validators list using SDK
173
+ * REAL RPC: GET /v1/validators
174
+ */
175
+ async function fetchValidators(rpcUrl) {
176
+ const client = createClient(rpcUrl);
177
+
178
+ try {
179
+ const validators = await client.getValidators();
180
+ if (!Array.isArray(validators)) return [];
181
+
182
+ return validators.map(v => ({
183
+ address: v.vote_account || v.pubkey || v.address || v.identity,
184
+ identity: v.identity || v.node_pubkey,
185
+ stake: v.stake_lamports || v.activated_stake || 0,
186
+ commission: v.commission || v.commission_bps || 0,
187
+ apy: v.apy || v.return_rate || 0,
188
+ name: v.name || v.moniker || 'Unknown',
189
+ active: v.active !== false && v.delinquent !== true,
190
+ })).filter(v => v.address);
191
+ } catch (err) {
192
+ return [];
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Fetch current epoch info using SDK
198
+ * REAL RPC: GET /v1/epoch
199
+ */
200
+ async function fetchEpochInfo(rpcUrl) {
201
+ const client = createClient(rpcUrl);
202
+
203
+ try {
204
+ const epoch = await client.getEpochInfo();
205
+ return {
206
+ epoch: epoch.epoch || 0,
207
+ slotIndex: epoch.slot_index || epoch.slotIndex || 0,
208
+ slotsInEpoch: epoch.slots_in_epoch || epoch.slotsInEpoch || 432000,
209
+ absoluteSlot: epoch.absolute_slot || epoch.absoluteSlot || 0,
210
+ blockHeight: epoch.block_height || epoch.blockHeight || 0,
211
+ };
212
+ } catch (err) {
213
+ return { epoch: 0, slotIndex: 0, slotsInEpoch: 432000, absoluteSlot: 0, blockHeight: 0 };
214
+ }
215
+ }
216
+
217
+ // ---------------------------------------------------------------------------
218
+ // Delegations List Command - SDK WIRED
219
+ // ---------------------------------------------------------------------------
220
+
221
+ async function delegationsList(args) {
222
+ const rpc = args.rpc || getDefaultRpc();
223
+ const isJson = args.json || false;
224
+ let address = args.address || null;
225
+
226
+ // Resolve address
227
+ if (!address) {
228
+ const config = loadConfig();
229
+ address = config.defaultWallet;
230
+ }
231
+
232
+ if (!address) {
233
+ if (isJson) {
234
+ console.log(JSON.stringify({ error: 'No address provided and no default wallet' }, null, 2));
235
+ } else {
236
+ console.log(`\n ${C.red}✗ No wallet address specified.${C.reset}`);
237
+ console.log(` ${C.dim}Usage: aether delegations list --address <addr>${C.reset}\n`);
238
+ }
239
+ return;
240
+ }
241
+
242
+ // SDK calls
243
+ if (!isJson) {
244
+ console.log(`\n${C.bright}${C.cyan}╔═══════════════════════════════════════════════════════════════════╗${C.reset}`);
245
+ console.log(`${C.bright}${C.cyan}║ STAKE DELEGATIONS — ${shortAddress(address).padEnd(30)}║${C.reset}`);
246
+ console.log(`${C.bright}${C.cyan}╚═══════════════════════════════════════════════════════════════════╝${C.reset}\n`);
247
+ console.log(` ${C.dim}RPC: ${rpc}${C.reset}\n`);
248
+ console.log(` ${C.dim}Fetching stake accounts via SDK...${C.reset}\n`);
249
+ }
250
+
251
+ const [stakeAccounts, epochInfo, validators] = await Promise.all([
252
+ fetchStakeAccounts(rpc, address),
253
+ fetchEpochInfo(rpc),
254
+ fetchValidators(rpc),
255
+ ]);
256
+
257
+ if (stakeAccounts.length === 0) {
258
+ if (isJson) {
259
+ console.log(JSON.stringify({
260
+ address,
261
+ rpc,
262
+ stake_accounts: [],
263
+ total_delegated: '0',
264
+ cli_version: CLI_VERSION,
265
+ timestamp: new Date().toISOString(),
266
+ }, null, 2));
267
+ } else {
268
+ console.log(` ${C.yellow}⚠ No stake delegations found.${C.reset}`);
269
+ console.log(` ${C.dim}Create a delegation:${C.reset}`);
270
+ console.log(` ${C.cyan}aether stake --validator <addr> --amount <aeth>${C.reset}\n`);
271
+ }
272
+ return;
273
+ }
274
+
275
+ // Calculate totals and enhance with validator info
276
+ let totalDelegated = BigInt(0);
277
+ let totalActive = BigInt(0);
278
+ let totalDeactivating = BigInt(0);
279
+ let activeCount = 0;
280
+ let deactivatingCount = 0;
281
+ let inactiveCount = 0;
282
+
283
+ const enhancedAccounts = stakeAccounts.map(s => {
284
+ const validator = validators.find(v => v.address === s.validator || v.identity === s.validator);
285
+ totalDelegated += BigInt(s.lamports);
286
+
287
+ let status = s.status;
288
+ if (s.deactivationEpoch && s.deactivationEpoch <= epochInfo.epoch) {
289
+ status = 'deactivated';
290
+ inactiveCount++;
291
+ } else if (s.deactivationEpoch) {
292
+ status = 'deactivating';
293
+ totalDeactivating += BigInt(s.lamports);
294
+ deactivatingCount++;
295
+ } else {
296
+ totalActive += BigInt(s.lamports);
297
+ activeCount++;
298
+ }
299
+
300
+ return {
301
+ ...s,
302
+ status,
303
+ validatorName: validator?.name || 'Unknown',
304
+ validatorCommission: validator?.commission || 0,
305
+ validatorApy: validator?.apy || 0,
306
+ };
307
+ });
308
+
309
+ if (isJson) {
310
+ console.log(JSON.stringify({
311
+ address,
312
+ rpc,
313
+ epoch: epochInfo.epoch,
314
+ stake_accounts: enhancedAccounts.map(s => ({
315
+ stake_account: s.address,
316
+ validator: s.validator,
317
+ validator_name: s.validatorName,
318
+ delegated_lamports: s.lamports,
319
+ delegated_aeth: formatAether(s.lamports),
320
+ status: s.status,
321
+ activation_epoch: s.activationEpoch,
322
+ deactivation_epoch: s.deactivationEpoch,
323
+ commission: s.validatorCommission,
324
+ estimated_apy: s.validatorApy,
325
+ })),
326
+ summary: {
327
+ total_delegated_lamports: totalDelegated.toString(),
328
+ total_delegated_aeth: formatAether(totalDelegated),
329
+ total_active_lamports: totalActive.toString(),
330
+ total_active_aeth: formatAether(totalActive),
331
+ total_deactivating_lamports: totalDeactivating.toString(),
332
+ total_deactivating_aeth: formatAether(totalDeactivating),
333
+ active_accounts: activeCount,
334
+ deactivating_accounts: deactivatingCount,
335
+ inactive_accounts: inactiveCount,
336
+ },
337
+ cli_version: CLI_VERSION,
338
+ timestamp: new Date().toISOString(),
339
+ }, null, 2));
340
+ return;
341
+ }
342
+
343
+ // Table header
344
+ console.log(` ${C.dim}┌─────────────────────────────────────────────────────────────────────────────┐${C.reset}`);
345
+ console.log(` ${C.dim}│${C.reset} ${C.bright}# Stake Account Validator Delegated Status${C.reset} ${C.dim}│${C.reset}`);
346
+ console.log(` ${C.dim}├─────────────────────────────────────────────────────────────────────────────┤${C.reset}`);
347
+
348
+ enhancedAccounts.forEach((s, i) => {
349
+ const num = (i + 1).toString().padStart(2);
350
+ const shortAcct = shortAddress(s.address).padEnd(18);
351
+ const shortVal = shortAddress(s.validator).padEnd(20);
352
+ const delegated = formatAether(s.lamports).padStart(12);
353
+
354
+ let statusColor, statusText;
355
+ switch (s.status) {
356
+ case 'active':
357
+ statusColor = C.green;
358
+ statusText = 'ACTIVE';
359
+ break;
360
+ case 'activating':
361
+ statusColor = C.cyan;
362
+ statusText = 'ACTIVATING';
363
+ break;
364
+ case 'deactivating':
365
+ statusColor = C.yellow;
366
+ statusText = 'DEACTIVATING';
367
+ break;
368
+ case 'deactivated':
369
+ statusColor = C.dim;
370
+ statusText = 'INACTIVE';
371
+ break;
372
+ default:
373
+ statusColor = C.reset;
374
+ statusText = s.status.toUpperCase();
375
+ }
376
+
377
+ console.log(` ${C.dim}│${C.reset} ${num} ${shortAcct} ${shortVal} ${delegated} ${statusColor}${statusText.padEnd(12)}${C.reset} ${C.dim}│${C.reset}`);
378
+
379
+ // Validator info line
380
+ const valName = s.validatorName !== 'Unknown' ? `(${s.validatorName})` : '';
381
+ const valInfo = valName ? ` ${C.dim}${valName}${C.reset}` : '';
382
+ if (valInfo) {
383
+ console.log(` ${C.dim}│${C.reset} ${valInfo.padEnd(75)} ${C.dim}│${C.reset}`);
384
+ }
385
+ });
386
+
387
+ console.log(` ${C.dim}└─────────────────────────────────────────────────────────────────────────────┘${C.reset}`);
388
+ console.log();
389
+
390
+ // Summary
391
+ console.log(` ${C.bright}Summary:${C.reset}`);
392
+ console.log(` ${C.dim} Total Delegated:${C.reset} ${C.bright}${formatAether(totalDelegated)}${C.reset}`);
393
+ console.log(` ${C.green} ● Active:${C.reset} ${formatAether(totalActive)} (${activeCount} accounts)`);
394
+ if (deactivatingCount > 0) {
395
+ console.log(` ${C.yellow} ○ Deactivating:${C.reset} ${formatAether(totalDeactivating)} (${deactivatingCount} accounts)`);
396
+ }
397
+ if (inactiveCount > 0) {
398
+ console.log(` ${C.dim} ● Inactive:${C.reset} ${inactiveCount} accounts ready to withdraw`);
399
+ }
400
+ console.log();
401
+ console.log(` ${C.dim}SDK: getStakeAccounts(), getValidators(), getEpochInfo()${C.reset}`);
402
+ console.log(` ${C.dim}Current Epoch: ${epochInfo.epoch} (slot ${epochInfo.slotIndex}/${epochInfo.slotsInEpoch})${C.reset}`);
403
+ console.log();
404
+ }
405
+
406
+ // ---------------------------------------------------------------------------
407
+ // Delegations Info Command - SDK WIRED
408
+ // ---------------------------------------------------------------------------
409
+
410
+ async function delegationsInfo(args) {
411
+ const rpc = args.rpc || getDefaultRpc();
412
+ const isJson = args.json || false;
413
+ const stakeAccount = args.account;
414
+
415
+ if (!stakeAccount) {
416
+ if (isJson) {
417
+ console.log(JSON.stringify({ error: 'No stake account provided (--account <addr>)' }, null, 2));
418
+ } else {
419
+ console.log(`\n ${C.red}✗ Stake account required.${C.reset}`);
420
+ console.log(` ${C.dim}Usage: aether delegations info --account <stakeAcct>${C.reset}\n`);
421
+ }
422
+ return;
423
+ }
424
+
425
+ if (!isJson) {
426
+ console.log(`\n${C.bright}${C.cyan}── Delegation Details ────────────────────────────────────────────────${C.reset}\n`);
427
+ console.log(` ${C.dim}Fetching details via SDK...${C.reset}\n`);
428
+ }
429
+
430
+ // SDK calls
431
+ const client = createClient(rpc);
432
+ const [epochInfo, validators] = await Promise.all([
433
+ fetchEpochInfo(rpc),
434
+ fetchValidators(rpc),
435
+ ]);
436
+
437
+ // Try to fetch the specific stake account info
438
+ // Note: getStakePositions returns delegations for a wallet, not a specific stake account
439
+ // We'll search in validators list for now
440
+ const stakeInfo = validators.find(v => v.address === stakeAccount);
441
+
442
+ if (!stakeInfo) {
443
+ if (isJson) {
444
+ console.log(JSON.stringify({ stake_account: stakeAccount, found: false }, null, 2));
445
+ } else {
446
+ console.log(` ${C.yellow}⚠ Stake account details not found.${C.reset}`);
447
+ console.log(` ${C.dim}The account may not be delegated or may not exist.${C.reset}\n`);
448
+ }
449
+ return;
450
+ }
451
+
452
+ if (isJson) {
453
+ console.log(JSON.stringify({
454
+ stake_account: stakeAccount,
455
+ validator: stakeInfo,
456
+ epoch: epochInfo,
457
+ cli_version: CLI_VERSION,
458
+ }, null, 2));
459
+ return;
460
+ }
461
+
462
+ console.log(` ${C.green}★${C.reset} Stake Account: ${C.bright}${stakeAccount}${C.reset}`);
463
+ console.log(` ${C.green}★${C.reset} Validator: ${C.bright}${stakeInfo.name || 'Unknown'}${C.reset}`);
464
+ console.log(` ${C.green}★${C.reset} Address: ${C.bright}${stakeInfo.address}${C.reset}`);
465
+ console.log(` ${C.green}★${C.reset} Commission: ${C.bright}${stakeInfo.commission / 100}%${C.reset}`);
466
+ console.log(` ${C.green}★${C.reset} Total Stake: ${C.bright}${formatAether(stakeInfo.stake)}${C.reset}`);
467
+ console.log();
468
+ console.log(` ${C.dim}Current Epoch: ${epochInfo.epoch}${C.reset}`);
469
+ console.log(` ${C.dim}Epoch Progress: ${((epochInfo.slotIndex / epochInfo.slotsInEpoch) * 100).toFixed(1)}%${C.reset}`);
470
+ console.log();
471
+ }
472
+
473
+ // ---------------------------------------------------------------------------
474
+ // CLI Args Parser
475
+ // ---------------------------------------------------------------------------
476
+
477
+ function parseArgs() {
478
+ const rawArgs = process.argv.slice(3);
479
+ const subcmd = rawArgs[0] || 'list';
480
+ const allArgs = rawArgs.slice(1);
481
+
482
+ const rpcIndex = allArgs.findIndex(a => a === '--rpc' || a === '-r');
483
+ const rpc = rpcIndex !== -1 && allArgs[rpcIndex + 1] ? allArgs[rpcIndex + 1] : getDefaultRpc();
484
+
485
+ const parsed = {
486
+ subcmd,
487
+ rpc,
488
+ json: allArgs.includes('--json') || allArgs.includes('-j'),
489
+ address: null,
490
+ account: null,
491
+ validator: null,
492
+ amount: null,
493
+ };
494
+
495
+ const addrIdx = allArgs.findIndex(a => a === '--address' || a === '-a');
496
+ if (addrIdx !== -1 && allArgs[addrIdx + 1]) parsed.address = allArgs[addrIdx + 1];
497
+
498
+ const acctIdx = allArgs.findIndex(a => a === '--account' || a === '-s');
499
+ if (acctIdx !== -1 && allArgs[acctIdx + 1]) parsed.account = allArgs[acctIdx + 1];
500
+
501
+ const valIdx = allArgs.findIndex(a => a === '--validator' || a === '-v');
502
+ if (valIdx !== -1 && allArgs[valIdx + 1]) parsed.validator = allArgs[valIdx + 1];
503
+
504
+ const amtIdx = allArgs.findIndex(a => a === '--amount' || a === '-m');
505
+ if (amtIdx !== -1 && allArgs[amtIdx + 1]) {
506
+ const val = parseFloat(allArgs[amtIdx + 1]);
507
+ if (!isNaN(val) && val > 0) parsed.amount = val;
508
+ }
509
+
510
+ return parsed;
511
+ }
512
+
513
+ function showHelp() {
514
+ console.log(`
515
+ ${C.bright}${C.cyan}aether-cli delegations${C.reset} — View and manage stake delegations
516
+
517
+ ${C.bright}USAGE${C.reset}
518
+ aether delegations list [--address <addr>] [--json]
519
+ aether delegations info --account <stakeAcct> [--json]
520
+
521
+ ${C.bright}COMMANDS${C.reset}
522
+ list Show all stake delegations for a wallet
523
+ info Show detailed info about a specific delegation
524
+
525
+ ${C.bright}OPTIONS${C.reset}
526
+ --address <addr> Wallet address (default: configured default)
527
+ --account <addr> Stake account address
528
+ --rpc <url> RPC endpoint (default: $AETHER_RPC or localhost:8899)
529
+ --json Output JSON for scripting
530
+
531
+ ${C.bright}SDK METHODS USED${C.reset}
532
+ client.getStakeAccounts(address) → GET /v1/stake-accounts/<addr>
533
+ client.getValidators() → GET /v1/validators
534
+ client.getEpochInfo() → GET /v1/epoch
535
+
536
+ ${C.bright}EXAMPLES${C.reset}
537
+ aether delegations list
538
+ aether delegations list --address ATHxxx... --json
539
+ aether delegations info --account Stakexxx... --json
540
+
541
+ ${C.green}✓ Fully wired to @jellylegsai/aether-sdk${C.reset}
542
+ `);
543
+ }
544
+
545
+ // ---------------------------------------------------------------------------
546
+ // Main Entry Point
547
+ // ---------------------------------------------------------------------------
548
+
549
+ async function delegationsCommand() {
550
+ const args = parseArgs();
551
+
552
+ if (args.subcmd === '--help' || args.subcmd === '-h' || args.subcmd === 'help') {
553
+ showHelp();
554
+ return;
555
+ }
556
+
557
+ switch (args.subcmd) {
558
+ case 'list':
559
+ await delegationsList(args);
560
+ break;
561
+ case 'info':
562
+ await delegationsInfo(args);
563
+ break;
564
+ default:
565
+ console.log(`\n ${C.red}✗ Unknown subcommand: ${args.subcmd}${C.reset}`);
566
+ console.log(`\n ${C.dim}Available commands:${C.reset}`);
567
+ console.log(` ${C.cyan}list${C.reset} - Show all stake delegations`);
568
+ console.log(` ${C.cyan}info${C.reset} - Show delegation details\n`);
569
+ showHelp();
570
+ }
571
+ }
572
+
573
+ // Export for module use
574
+ module.exports = { delegationsCommand };
575
+
576
+ // Run if called directly
577
+ if (require.main === module) {
578
+ delegationsCommand().catch(err => {
579
+ console.error(`\n${C.red}✗ Delegations command failed:${C.reset}`, err.message, '\n');
580
+ process.exit(1);
581
+ });
582
+ }