@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,490 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * aether-cli claim
4
+ *
5
+ * Claim accumulated staking rewards for a wallet.
6
+ * Fetches pending rewards from the chain and submits a claim transaction.
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 claim --address <addr> [--json] [--rpc <url>]
13
+ * aether claim --address <addr> --dry-run
14
+ *
15
+ * SDK wired to:
16
+ * - client.getStakePositions(address) → GET /v1/stake/<addr>
17
+ * - client.getRewards(address) → GET /v1/rewards/<addr>
18
+ * - client.getSlot() → GET /v1/slot
19
+ * - client.sendTransaction(tx) → POST /v1/transaction
20
+ */
21
+
22
+ const path = require('path');
23
+ const readline = require('readline');
24
+ const crypto = require('crypto');
25
+ const bs58 = require('bs58').default;
26
+ const bip39 = require('bip39');
27
+ const nacl = require('tweetnacl');
28
+
29
+ // Import SDK for ALL blockchain RPC calls
30
+ const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
31
+ const aether = require(sdkPath);
32
+
33
+ // ANSI colours
34
+ const C = {
35
+ reset: '\x1b[0m',
36
+ bright: '\x1b[1m',
37
+ dim: '\x1b[2m',
38
+ red: '\x1b[31m',
39
+ green: '\x1b[32m',
40
+ yellow: '\x1b[33m',
41
+ cyan: '\x1b[36m',
42
+ magenta: '\x1b[35m',
43
+ };
44
+
45
+ const DERIVATION_PATH = "m/44'/7777777'/0'/0'";
46
+ const CLI_VERSION = '1.1.0';
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // SDK Client Setup
50
+ // ---------------------------------------------------------------------------
51
+
52
+ function getDefaultRpc() {
53
+ return process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
54
+ }
55
+
56
+ function createClient(rpcUrl) {
57
+ return new aether.AetherClient({ rpcUrl });
58
+ }
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // Paths & config
62
+ // ---------------------------------------------------------------------------
63
+
64
+ function getAetherDir() {
65
+ return path.join(require('os').homedir(), '.aether');
66
+ }
67
+
68
+ function loadConfig() {
69
+ const fs = require('fs');
70
+ const p = path.join(getAetherDir(), 'config.json');
71
+ if (!fs.existsSync(p)) return { defaultWallet: null };
72
+ try {
73
+ return JSON.parse(fs.readFileSync(p, 'utf8'));
74
+ } catch {
75
+ return { defaultWallet: null };
76
+ }
77
+ }
78
+
79
+ function loadWallet(address) {
80
+ const fs = require('fs');
81
+ const fp = path.join(getAetherDir(), 'wallets', `${address}.json`);
82
+ if (!fs.existsSync(fp)) return null;
83
+ return JSON.parse(fs.readFileSync(fp, 'utf8'));
84
+ }
85
+
86
+ // ---------------------------------------------------------------------------
87
+ // Crypto helpers
88
+ // ---------------------------------------------------------------------------
89
+
90
+ function deriveKeypair(mnemonic) {
91
+ if (!bip39.validateMnemonic(mnemonic)) throw new Error('Invalid mnemonic');
92
+ const seedBuffer = bip39.mnemonicToSeedSync(mnemonic, '');
93
+ const seed32 = seedBuffer.slice(0, 32);
94
+ const keyPair = nacl.sign.keyPair.fromSeed(seed32);
95
+ return {
96
+ publicKey: Buffer.from(keyPair.publicKey),
97
+ secretKey: Buffer.from(keyPair.secretKey)
98
+ };
99
+ }
100
+
101
+ function formatAddress(publicKey) {
102
+ return 'ATH' + bs58.encode(publicKey);
103
+ }
104
+
105
+ function signTransaction(tx, secretKey) {
106
+ const txBytes = Buffer.from(JSON.stringify(tx));
107
+ const sig = nacl.sign.detached(txBytes, secretKey);
108
+ return bs58.encode(sig);
109
+ }
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // Format helpers
113
+ // ---------------------------------------------------------------------------
114
+
115
+ function formatAether(lamports) {
116
+ if (!lamports || lamports === '0') return '0 AETH';
117
+ const aeth = Number(lamports) / 1e9;
118
+ return aeth.toFixed(4).replace(/\.?0+$/, '') + ' AETH';
119
+ }
120
+
121
+ function formatFlux(lamports) {
122
+ if (!lamports) return '0 FLUX';
123
+ const flux = Number(lamports) / 1e6;
124
+ return flux.toFixed(2) + ' FLUX';
125
+ }
126
+
127
+ function shortPubkey(pubkey) {
128
+ if (!pubkey || pubkey.length < 16) return pubkey || 'unknown';
129
+ return pubkey.slice(0, 8) + '...' + pubkey.slice(-8);
130
+ }
131
+
132
+ // ---------------------------------------------------------------------------
133
+ // SDK Reward Fetching (REAL RPC CALLS)
134
+ // ---------------------------------------------------------------------------
135
+
136
+ async function fetchStakeRewards(rpcUrl, stakeAddress) {
137
+ const client = createClient(rpcUrl);
138
+
139
+ try {
140
+ // Parallel SDK calls
141
+ const [stakePositions, rewards] = await Promise.all([
142
+ client.getStakePositions(stakeAddress).catch(() => []),
143
+ client.getRewards(stakeAddress).catch(() => ({ total: 0, pending: 0 })),
144
+ ]);
145
+
146
+ const stakeData = stakePositions.find(s =>
147
+ (s.pubkey || s.publicKey || s.account) === stakeAddress
148
+ ) || stakePositions[0] || {};
149
+
150
+ const delegatedStake = BigInt(stakeData.lamports || stakeData.stake_lamports || 0);
151
+ const validator = stakeData.validator || stakeData.delegate || rewards.validator || 'unknown';
152
+ const totalRewards = BigInt(rewards.total || rewards.pending_rewards || rewards.amount || 0);
153
+ const pendingRewards = BigInt(rewards.pending || rewards.pending_rewards || 0);
154
+
155
+ return {
156
+ stakeAddress,
157
+ delegatedStake: delegatedStake.toString(),
158
+ delegatedStakeFormatted: formatAether(delegatedStake),
159
+ totalRewards: totalRewards.toString(),
160
+ pendingRewards: pendingRewards.toString(),
161
+ totalRewardsFormatted: formatAether(totalRewards),
162
+ pendingRewardsFormatted: formatFlux(pendingRewards),
163
+ validator,
164
+ };
165
+ } catch (err) {
166
+ return { stakeAddress, error: err.message };
167
+ }
168
+ }
169
+
170
+ async function fetchWalletStakeAccounts(rpcUrl, walletAddress) {
171
+ const client = createClient(rpcUrl);
172
+
173
+ try {
174
+ const rawAddr = walletAddress.startsWith('ATH') ? walletAddress.slice(3) : walletAddress;
175
+ const stakePositions = await client.getStakePositions(rawAddr);
176
+
177
+ if (!Array.isArray(stakePositions)) return [];
178
+
179
+ return stakePositions.map(s => ({
180
+ address: s.pubkey || s.publicKey || s.account,
181
+ validator: s.validator || s.delegate,
182
+ lamports: s.lamports || s.stake_lamports || 0,
183
+ pendingRewards: s.pending_rewards || s.rewards || 0,
184
+ })).filter(s => s.address);
185
+ } catch (err) {
186
+ return [];
187
+ }
188
+ }
189
+
190
+ // ---------------------------------------------------------------------------
191
+ // Argument parsing
192
+ // ---------------------------------------------------------------------------
193
+
194
+ function parseArgs() {
195
+ const args = process.argv.slice(2);
196
+ const result = { address: null, json: false, dryRun: false, rpc: getDefaultRpc() };
197
+
198
+ for (let i = 0; i < args.length; i++) {
199
+ if ((args[i] === '--address' || args[i] === '-a') && args[i + 1]) {
200
+ result.address = args[i + 1];
201
+ i++;
202
+ } else if (args[i] === '--json' || args[i] === '--json-output') {
203
+ result.json = true;
204
+ } else if (args[i] === '--rpc' && args[i + 1]) {
205
+ result.rpc = args[i + 1];
206
+ i++;
207
+ } else if (args[i] === '--help' || args[i] === '-h') {
208
+ result.help = true;
209
+ } else if (args[i] === '--dry-run') {
210
+ result.dryRun = true;
211
+ }
212
+ }
213
+
214
+ return result;
215
+ }
216
+
217
+ function createRl() {
218
+ return readline.createInterface({ input: process.stdin, output: process.stdout });
219
+ }
220
+
221
+ function question(rl, q) {
222
+ return new Promise((res) => rl.question(q, res));
223
+ }
224
+
225
+ async function askMnemonic(rl, promptText) {
226
+ console.log(`\n${C.cyan}${promptText}${C.reset}`);
227
+ console.log(`${C.dim}Enter your 12 or 24-word passphrase, one space-separated line:${C.reset}`);
228
+ const raw = await question(rl, ` > ${C.reset}`);
229
+ return raw.trim().toLowerCase();
230
+ }
231
+
232
+ // ---------------------------------------------------------------------------
233
+ // Main Command - FULLY WIRED TO SDK
234
+ // ---------------------------------------------------------------------------
235
+
236
+ async function claimCommand() {
237
+ const opts = parseArgs();
238
+ const rl = createRl();
239
+
240
+ if (opts.help) {
241
+ console.log(`
242
+ ${C.bright}${C.cyan}claim${C.reset} — Claim accumulated staking rewards for a wallet
243
+
244
+ ${C.bright}USAGE${C.reset}
245
+ aether claim --address <addr> [--json] [--rpc <url>] [--dry-run]
246
+
247
+ ${C.bright}OPTIONS${C.reset}
248
+ --address <addr> Wallet address (ATH...)
249
+ --json Output raw JSON
250
+ --rpc <url> RPC endpoint (default: AETHER_RPC or localhost:8899)
251
+ --dry-run Preview claim without submitting transaction
252
+ --help Show this help
253
+
254
+ ${C.bright}SDK METHODS USED${C.reset}
255
+ client.getStakePositions(address) → GET /v1/stake/<addr>
256
+ client.getRewards(address) → GET /v1/rewards/<addr>
257
+ client.getSlot() → GET /v1/slot
258
+ client.sendTransaction(tx) → POST /v1/transaction
259
+
260
+ ${C.bright}EXAMPLES${C.reset}
261
+ aether claim --address ATH3abc...
262
+ aether claim --address ATH3abc... --dry-run
263
+ aether claim --address ATH3abc... --json
264
+ `);
265
+ rl.close();
266
+ return;
267
+ }
268
+
269
+ if (!opts.address) {
270
+ // Try default wallet
271
+ const config = loadConfig();
272
+ if (config.defaultWallet) {
273
+ opts.address = config.defaultWallet;
274
+ } else {
275
+ console.log(` ${C.red}✗ Missing --address${C.reset}\n`);
276
+ console.log(` Usage: aether claim --address <addr> [--json] [--dry-run]\n`);
277
+ rl.close();
278
+ return;
279
+ }
280
+ }
281
+
282
+ const rpcUrl = opts.rpc;
283
+ const address = opts.address;
284
+ const rawAddr = address.startsWith('ATH') ? address.slice(3) : address;
285
+
286
+ if (!opts.json) {
287
+ console.log(`\n${C.bright}${C.cyan}── Claim Staking Rewards ────────────────────────────────${C.reset}\n`);
288
+ console.log(` ${C.dim}Wallet:${C.reset} ${address}`);
289
+ console.log(` ${C.dim}RPC: ${C.reset} ${rpcUrl}`);
290
+ if (opts.dryRun) console.log(` ${C.yellow}(dry-run mode - no transaction will be submitted)${C.reset}`);
291
+ console.log();
292
+ }
293
+
294
+ try {
295
+ // SDK call: Fetch stake positions via client.getStakePositions (REAL RPC)
296
+ const client = createClient(rpcUrl);
297
+
298
+ if (!opts.json) {
299
+ console.log(` ${C.dim}Fetching stake positions via SDK...${C.reset}`);
300
+ }
301
+
302
+ const stakeAccounts = await fetchWalletStakeAccounts(rpcUrl, address);
303
+
304
+ if (!stakeAccounts || stakeAccounts.length === 0) {
305
+ if (opts.json) {
306
+ console.log(JSON.stringify({
307
+ address,
308
+ error: 'No active stake positions found',
309
+ suggestion: 'Stake AETH first with: aether stake --validator <addr> --amount <aeth>',
310
+ }, null, 2));
311
+ } else {
312
+ console.log(` ${C.yellow}⚠ No active stake positions found.${C.reset}`);
313
+ console.log(` ${C.dim} Stake AETH with: ${C.cyan}aether stake --validator <addr> --amount <aeth>${C.reset}\n`);
314
+ }
315
+ rl.close();
316
+ return;
317
+ }
318
+
319
+ // Calculate total pending rewards using SDK
320
+ let totalPendingRewards = BigInt(0);
321
+ const rewardBreakdown = [];
322
+
323
+ for (const acc of stakeAccounts) {
324
+ // SDK call: getRewards for each stake account
325
+ const rewardData = await fetchStakeRewards(rpcUrl, acc.address);
326
+
327
+ if (!rewardData.error) {
328
+ const pendingRewards = BigInt(rewardData.pendingRewards || 0);
329
+ totalPendingRewards += pendingRewards;
330
+
331
+ rewardBreakdown.push({
332
+ stakeAcct: acc.address,
333
+ validator: acc.validator || rewardData.validator || 'unknown',
334
+ stakeLamports: acc.lamports || 0,
335
+ pendingRewards: pendingRewards.toString(),
336
+ pendingFormatted: rewardData.pendingRewardsFormatted,
337
+ });
338
+ }
339
+ }
340
+
341
+ if (!opts.json) {
342
+ console.log(` ${C.bright}Stake Positions (${stakeAccounts.length})${C.reset}\n`);
343
+
344
+ for (const pos of rewardBreakdown) {
345
+ const shortVal = shortPubkey(pos.validator);
346
+ const shortAcct = shortPubkey(pos.stakeAcct);
347
+ console.log(` ${C.dim}├─ ${C.reset}${shortAcct} → ${C.cyan}${shortVal}${C.reset}`);
348
+ console.log(` │ ${C.dim}Staked:${C.reset} ${formatAether(pos.stakeLamports)}`);
349
+ console.log(` │ ${C.green}Pending:${C.reset} ${pos.pendingFormatted}\n`);
350
+ }
351
+
352
+ console.log(` ${C.dim}────────────────────────────────────────${C.reset}`);
353
+ console.log(` ${C.bright}Total Pending Rewards:${C.reset} ${C.green}${formatFlux(totalPendingRewards.toString())}${C.reset}\n`);
354
+ }
355
+
356
+ // Dry run mode - don't submit
357
+ if (opts.dryRun) {
358
+ if (opts.json) {
359
+ console.log(JSON.stringify({
360
+ wallet_address: address,
361
+ dry_run: true,
362
+ stake_count: stakeAccounts.length,
363
+ total_pending_flux: totalPendingRewards.toString(),
364
+ total_pending_aeth: (Number(totalPendingRewards) / 1e9).toFixed(9),
365
+ breakdown: rewardBreakdown,
366
+ sdk_version: CLI_VERSION,
367
+ }, null, 2));
368
+ } else {
369
+ console.log(` ${C.yellow}⚠ Dry run - not submitting claim transaction${C.reset}\n`);
370
+ }
371
+ rl.close();
372
+ return;
373
+ }
374
+
375
+ // Load wallet for signing
376
+ const wallet = loadWallet(address);
377
+ if (!wallet) {
378
+ console.log(` ${C.red}✗ Wallet not found locally: ${address}${C.reset}`);
379
+ console.log(` ${C.dim}Import it: aether wallet import${C.reset}\n`);
380
+ rl.close();
381
+ return;
382
+ }
383
+
384
+ // Step: Submit claim transaction
385
+ if (!opts.json) {
386
+ console.log(` ${C.dim}Preparing claim transaction...${C.reset}`);
387
+ }
388
+
389
+ // Ask for mnemonic
390
+ const mnemonic = await askMnemonic(rl, 'Enter your wallet passphrase to sign the claim');
391
+ console.log();
392
+
393
+ let keyPair;
394
+ try {
395
+ keyPair = deriveKeypair(mnemonic);
396
+ } catch (e) {
397
+ console.log(` ${C.red}✗ Failed to derive keypair: ${e.message}${C.reset}\n`);
398
+ rl.close();
399
+ return;
400
+ }
401
+
402
+ // Verify derived address matches
403
+ const derivedAddress = formatAddress(keyPair.publicKey);
404
+ if (derivedAddress !== address) {
405
+ console.log(` ${C.red}✗ Passphrase mismatch.${C.reset}`);
406
+ console.log(` ${C.dim} Derived: ${derivedAddress}${C.reset}`);
407
+ console.log(` ${C.dim} Expected: ${address}${C.reset}`);
408
+ console.log(` ${C.dim}Check your passphrase and try again.${C.reset}\n`);
409
+ rl.close();
410
+ return;
411
+ }
412
+
413
+ // SDK call: get current slot
414
+ const currentSlot = await client.getSlot().catch(() => 0);
415
+
416
+ // Build claim transaction for SDK
417
+ const tx = {
418
+ signer: rawAddr,
419
+ tx_type: 'ClaimRewards',
420
+ payload: {
421
+ type: 'ClaimRewards',
422
+ data: {
423
+ stake_accounts: rewardBreakdown.map(r => r.stakeAcct),
424
+ lamports: totalPendingRewards.toString(),
425
+ },
426
+ },
427
+ fee: 5000,
428
+ slot: currentSlot,
429
+ timestamp: Math.floor(Date.now() / 1000),
430
+ };
431
+
432
+ // Sign transaction
433
+ tx.signature = signTransaction(tx, keyPair.secretKey);
434
+
435
+ if (!opts.json) {
436
+ console.log(` ${C.dim}Submitting claim via SDK to ${rpcUrl}...${C.reset}`);
437
+ }
438
+
439
+ // SDK call: sendTransaction (REAL RPC POST /v1/transaction)
440
+ const result = await client.sendTransaction(tx);
441
+
442
+ if (opts.json) {
443
+ console.log(JSON.stringify({
444
+ wallet_address: address,
445
+ success: !result.error,
446
+ total_claimed_flux: totalPendingRewards.toString(),
447
+ total_claimed_aeth: (Number(totalPendingRewards) / 1e9).toFixed(9),
448
+ tx_signature: result.signature || result.txid || null,
449
+ block_height: result.block_height || result.slot || currentSlot,
450
+ slot: result.slot || currentSlot,
451
+ claimed_at: new Date().toISOString(),
452
+ sdk_version: CLI_VERSION,
453
+ }, null, 2));
454
+ } else {
455
+ if (result.error) {
456
+ console.log(` ${C.red}✗ Claim failed:${C.reset} ${result.error}\n`);
457
+ rl.close();
458
+ process.exit(1);
459
+ }
460
+
461
+ console.log(` ${C.green}✓ Rewards claimed!${C.reset}`);
462
+ console.log(` ${C.dim} Amount:${C.reset} ${C.green}${formatFlux(result.claimed || totalPendingRewards.toString())}${C.reset}`);
463
+ if (result.signature || result.txid) {
464
+ console.log(` ${C.dim} Tx:${C.reset} ${shortPubkey(result.signature || result.txid)}`);
465
+ }
466
+ console.log(` ${C.dim} Slot:${C.reset} ${result.slot || currentSlot}`);
467
+ console.log(` ${C.dim} SDK: sendTransaction()${C.reset}\n`);
468
+ }
469
+
470
+ rl.close();
471
+
472
+ } catch (err) {
473
+ if (opts.json) {
474
+ console.log(JSON.stringify({ address, error: err.message, sdk_version: CLI_VERSION }, null, 2));
475
+ } else {
476
+ console.log(` ${C.red}✗ Failed to claim rewards:${C.reset} ${err.message}\n`);
477
+ console.log(` ${C.dim} Set custom RPC: AETHER_RPC=https://your-rpc-url${C.reset}\n`);
478
+ }
479
+ rl.close();
480
+ process.exit(1);
481
+ }
482
+ }
483
+
484
+ // Export for module use
485
+ module.exports = { claimCommand };
486
+
487
+ // Run if called directly
488
+ if (require.main === module) {
489
+ claimCommand();
490
+ }