aether-hub 1.2.7 → 1.3.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/commands/account.js +280 -280
- package/commands/apy.js +480 -480
- package/commands/balance.js +276 -276
- package/commands/claim.js +43 -77
- package/commands/delegations.js +412 -462
- package/commands/emergency.js +607 -657
- package/commands/epoch.js +275 -275
- package/commands/fees.js +276 -276
- package/commands/info.js +495 -495
- package/commands/monitor.js +431 -431
- package/commands/multisig.js +685 -726
- package/commands/network.js +429 -429
- package/commands/ping.js +266 -266
- package/commands/rewards.js +479 -866
- package/commands/sdk-test.js +477 -477
- package/commands/sdk.js +537 -537
- package/commands/slot.js +155 -0
- package/commands/snapshot.js +509 -509
- package/commands/stake-positions.js +205 -220
- package/commands/stats.js +418 -418
- package/commands/status.js +326 -326
- package/commands/supply.js +3 -1
- package/commands/tps.js +238 -238
- package/commands/tx-history.js +52 -168
- package/commands/wallet.js +3 -0
- package/index.js +42 -43
- package/package.json +3 -1
- package/sdk/README.md +210 -0
- package/sdk/index.js +1013 -0
- package/sdk/package.json +33 -0
- package/sdk/rpc.js +108 -0
- package/sdk/test.js +85 -0
- package/theme.js +211 -0
package/commands/rewards.js
CHANGED
|
@@ -1,866 +1,479 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* aether-cli rewards
|
|
4
|
-
*
|
|
5
|
-
* View staking rewards earned from delegated stake accounts.
|
|
6
|
-
* Shows accumulated rewards, estimated APY, and claimable amounts.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* aether rewards list --address <addr>
|
|
11
|
-
* aether rewards
|
|
12
|
-
* aether rewards summary --address <addr> One-line summary of total rewards
|
|
13
|
-
* aether rewards
|
|
14
|
-
*
|
|
15
|
-
* Requires AETHER_RPC env var or local node running (default: http://127.0.0.1:8899)
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function
|
|
50
|
-
const
|
|
51
|
-
if (!
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
let
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
if (
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
console.log(
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
console.
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
async function rewardsClaim(args) {
|
|
481
|
-
const rpc = args.rpc || getDefaultRpc();
|
|
482
|
-
const isJson = args.json || false;
|
|
483
|
-
let address = args.address || null;
|
|
484
|
-
let stakeAccount = args.account || null;
|
|
485
|
-
|
|
486
|
-
const config = loadConfig();
|
|
487
|
-
const rl = createRl();
|
|
488
|
-
|
|
489
|
-
if (!address) {
|
|
490
|
-
const ans = await question(rl, `\n${C.cyan}Enter wallet address: ${C.reset}`);
|
|
491
|
-
address = ans.trim();
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
if (!stakeAccount) {
|
|
495
|
-
const stakeAccounts = await fetchWalletStakeAccounts(address);
|
|
496
|
-
if (stakeAccounts.length === 0) {
|
|
497
|
-
console.log(`\n${C.red}✗ No stake accounts found for this wallet.${C.reset}\n`);
|
|
498
|
-
rl.close();
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
|
-
if (stakeAccounts.length === 1) {
|
|
502
|
-
stakeAccount = stakeAccounts[0];
|
|
503
|
-
} else {
|
|
504
|
-
console.log(`\n${C.cyan}Select stake account:${C.reset}`);
|
|
505
|
-
stakeAccounts.forEach((sa, i) => {
|
|
506
|
-
console.log(` ${i + 1}) ${sa.substring(0, 20)}...`);
|
|
507
|
-
});
|
|
508
|
-
const ans = await question(rl, `${C.cyan}Enter number: ${C.reset}`);
|
|
509
|
-
const idx = parseInt(ans.trim()) - 1;
|
|
510
|
-
if (idx < 0 || idx >= stakeAccounts.length) {
|
|
511
|
-
console.log(`\n${C.red}Invalid selection.${C.reset}\n`);
|
|
512
|
-
rl.close();
|
|
513
|
-
return;
|
|
514
|
-
}
|
|
515
|
-
stakeAccount = stakeAccounts[idx];
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
rl.close();
|
|
520
|
-
|
|
521
|
-
// Load wallet for signing
|
|
522
|
-
const wallet = loadWallet(address);
|
|
523
|
-
if (!wallet) {
|
|
524
|
-
console.log(`\n${C.red}✗ Wallet not found locally: ${address}${C.reset}`);
|
|
525
|
-
console.log(` ${C.dim}Import it: aether wallet import${C.reset}\n`);
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
console.log(`\n${C.bright}${C.cyan}╔════════════════════════════════════════╗${C.reset}`);
|
|
530
|
-
console.log(`${C.bright}${C.cyan}║ Claim Staking Rewards ║${C.reset}`);
|
|
531
|
-
console.log(`${C.bright}${C.cyan}╚════════════════════════════════════════╝${C.reset}\n`);
|
|
532
|
-
console.log(` ${C.dim}Wallet:${C.reset} ${address.substring(0, 16)}...`);
|
|
533
|
-
console.log(` ${C.dim}Stake Account:${C.reset} ${stakeAccount.substring(0, 16)}...`);
|
|
534
|
-
|
|
535
|
-
// Fetch current rewards for this stake account
|
|
536
|
-
const rewardData = await fetchStakeRewards(rpc, stakeAccount);
|
|
537
|
-
if (rewardData.error) {
|
|
538
|
-
console.log(`\n${C.red}✗ Failed to fetch stake account: ${rewardData.error}${C.reset}\n`);
|
|
539
|
-
return;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
console.log(` ${C.dim}Delegated Stake:${C.reset} ${rewardData.delegatedStakeFormatted}`);
|
|
543
|
-
console.log(` ${C.dim}Est. Accumulated:${C.reset} ${rewardData.estimatedRewardsFormatted}`);
|
|
544
|
-
console.log(` ${C.dim}APY:${C.reset} ${(rewardData.apyBps / 100).toFixed(2)}%`);
|
|
545
|
-
|
|
546
|
-
const estimatedRewards = rewardData.estimatedRewards;
|
|
547
|
-
if (BigInt(estimatedRewards) === BigInt(0)) {
|
|
548
|
-
console.log(`\n${C.yellow}⚠ No rewards accumulated yet.${C.reset}\n`);
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
const confirm = await question(rl, `\n ${C.yellow}Claim ${rewardData.estimatedRewardsFormatted}? [y/N]${C.reset} > `);
|
|
553
|
-
if (confirm.trim().toLowerCase() !== 'y') {
|
|
554
|
-
console.log(`${C.dim}Cancelled.${C.reset}\n`);
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
// Derive keypair from mnemonic for signing
|
|
559
|
-
let keypair;
|
|
560
|
-
try {
|
|
561
|
-
const mnemonic = wallet.mnemonic;
|
|
562
|
-
keypair = deriveKeypair(mnemonic);
|
|
563
|
-
} catch (err) {
|
|
564
|
-
console.log(`\n${C.red}✗ Failed to derive keypair: ${err.message}${C.reset}\n`);
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// Build claim transaction
|
|
569
|
-
const tx = {
|
|
570
|
-
type: 'ClaimRewards',
|
|
571
|
-
from: address,
|
|
572
|
-
stake_account: stakeAccount,
|
|
573
|
-
lamports: estimatedRewards,
|
|
574
|
-
timestamp: Math.floor(Date.now() / 1000),
|
|
575
|
-
};
|
|
576
|
-
|
|
577
|
-
// Sign transaction
|
|
578
|
-
const txData = JSON.stringify(tx);
|
|
579
|
-
const txHash = crypto.createHash('sha256').update(txData).digest('hex');
|
|
580
|
-
const signature = nacl.hash(Buffer.from(txHash, 'hex'));
|
|
581
|
-
const signatureB58 = bs58.encode(signature.slice(0, 64));
|
|
582
|
-
|
|
583
|
-
tx.signature = signatureB58;
|
|
584
|
-
|
|
585
|
-
// Submit transaction
|
|
586
|
-
try {
|
|
587
|
-
const result = await httpPost(rpc, '/v1/tx', tx);
|
|
588
|
-
|
|
589
|
-
if (isJson) {
|
|
590
|
-
console.log(JSON.stringify({ success: true, tx: tx, result }, null, 2));
|
|
591
|
-
} else {
|
|
592
|
-
if (result.success || result.txid) {
|
|
593
|
-
console.log(`\n${C.green}✓ Rewards claimed successfully!${C.reset}`);
|
|
594
|
-
console.log(` ${C.dim}TX ID: ${result.txid || signatureB58.substring(0, 20)}...${C.reset}`);
|
|
595
|
-
console.log(` ${C.dim}Amount: ${rewardData.estimatedRewardsFormatted}${C.reset}`);
|
|
596
|
-
console.log(` ${C.dim}Check balance: aether wallet balance --address ${address}${C.reset}\n`);
|
|
597
|
-
} else {
|
|
598
|
-
console.log(`\n${C.red}✗ Claim failed: ${result.error || JSON.stringify(result)}${C.reset}\n`);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
} catch (err) {
|
|
602
|
-
if (isJson) {
|
|
603
|
-
console.log(JSON.stringify({ success: false, error: err.message }, null, 2));
|
|
604
|
-
} else {
|
|
605
|
-
console.log(`\n${C.red}✗ Failed to submit claim transaction: ${err.message}${C.reset}`);
|
|
606
|
-
console.log(` ${C.dim}The rewards are accumulated on-chain and can be claimed later.${C.reset}\n`);
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
// ---------------------------------------------------------------------------
|
|
612
|
-
// Rewards compound command — claim and auto-re-stake
|
|
613
|
-
// ---------------------------------------------------------------------------
|
|
614
|
-
|
|
615
|
-
async function rewardsCompound(args) {
|
|
616
|
-
const rpc = args.rpc || getDefaultRpc();
|
|
617
|
-
const isJson = args.json || false;
|
|
618
|
-
let address = args.address || null;
|
|
619
|
-
let stakeAccount = args.account || null;
|
|
620
|
-
|
|
621
|
-
const config = loadConfig();
|
|
622
|
-
const rl = createRl();
|
|
623
|
-
|
|
624
|
-
if (!address) {
|
|
625
|
-
const ans = await question(rl, `\n${C.cyan}Enter wallet address: ${C.reset}`);
|
|
626
|
-
address = ans.trim();
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
if (!address) {
|
|
630
|
-
console.log(`\n${C.red}✗ No address provided.${C.reset}\n`);
|
|
631
|
-
rl.close();
|
|
632
|
-
return;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
// Load wallet for signing
|
|
636
|
-
const wallet = loadWallet(address);
|
|
637
|
-
if (!wallet) {
|
|
638
|
-
console.log(`\n${C.red}✗ Wallet not found locally: ${address}${C.reset}`);
|
|
639
|
-
console.log(` ${C.dim}Import it: aether wallet import${C.reset}\n`);
|
|
640
|
-
rl.close();
|
|
641
|
-
return;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
// Fetch stake accounts
|
|
645
|
-
let stakeAccounts = await fetchWalletStakeAccounts(address);
|
|
646
|
-
if (stakeAccounts.length === 0) {
|
|
647
|
-
console.log(`\n${C.red}✗ No stake accounts found for this wallet.${C.reset}\n`);
|
|
648
|
-
rl.close();
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
// If --account specified, filter to that one
|
|
653
|
-
if (stakeAccount) {
|
|
654
|
-
stakeAccounts = stakeAccounts.filter(sa => sa === stakeAccount);
|
|
655
|
-
if (stakeAccounts.length === 0) {
|
|
656
|
-
console.log(`\n${C.red}✗ Stake account not found: ${stakeAccount}${C.reset}\n`);
|
|
657
|
-
rl.close();
|
|
658
|
-
return;
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
console.log(`\n${C.bright}${C.cyan}╔══════════════════════════════════════════════════════════════╗${C.reset}`);
|
|
663
|
-
console.log(`${C.bright}${C.cyan}║ Compound Staking Rewards ║${C.reset}`);
|
|
664
|
-
console.log(`${C.bright}${C.cyan}╚══════════════════════════════════════════════════════════════╝${C.reset}\n`);
|
|
665
|
-
console.log(` ${C.dim}Wallet:${C.reset} ${C.bright}${address}${C.reset}`);
|
|
666
|
-
console.log(` ${C.dim}RPC:${C.reset} ${rpc}`);
|
|
667
|
-
console.log(` ${C.dim}Stake accounts to process:${C.reset} ${stakeAccounts.length}\n`);
|
|
668
|
-
|
|
669
|
-
// Ask for mnemonic upfront
|
|
670
|
-
console.log(`${C.yellow} ⚠ Compound requires your wallet passphrase to sign transactions.${C.reset}`);
|
|
671
|
-
const mnemonic = await question(rl, ` ${C.cyan}Enter your 12/24-word mnemonic:${C.reset} `);
|
|
672
|
-
console.log();
|
|
673
|
-
|
|
674
|
-
let keypair;
|
|
675
|
-
try {
|
|
676
|
-
if (!bip39.validateMnemonic(mnemonic)) {
|
|
677
|
-
throw new Error('Invalid BIP39 mnemonic');
|
|
678
|
-
}
|
|
679
|
-
keypair = deriveKeypair(mnemonic);
|
|
680
|
-
} catch (err) {
|
|
681
|
-
console.log(` ${C.red}✗ Failed to derive keypair: ${err.message}${C.reset}\n`);
|
|
682
|
-
rl.close();
|
|
683
|
-
return;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
// Verify address matches
|
|
687
|
-
const derivedAddress = formatAddress(keypair.publicKey);
|
|
688
|
-
if (derivedAddress !== address) {
|
|
689
|
-
console.log(` ${C.red}✗ Passphrase mismatch.${C.reset}`);
|
|
690
|
-
console.log(` ${C.dim} Derived: ${derivedAddress}${C.reset}`);
|
|
691
|
-
console.log(` ${C.dim} Expected: ${address}${C.reset}`);
|
|
692
|
-
console.log(` ${C.dim}Check your passphrase and try again.${C.reset}\n`);
|
|
693
|
-
rl.close();
|
|
694
|
-
return;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
const compoundResults = [];
|
|
698
|
-
let totalCompounded = BigInt(0);
|
|
699
|
-
let successCount = 0;
|
|
700
|
-
|
|
701
|
-
for (const sa of stakeAccounts) {
|
|
702
|
-
console.log(` ${C.dim}Processing stake account:${C.reset} ${sa.substring(0, 20)}...`);
|
|
703
|
-
|
|
704
|
-
try {
|
|
705
|
-
// Fetch rewards for this stake account
|
|
706
|
-
const rewardData = await fetchStakeRewards(rpc, sa);
|
|
707
|
-
if (rewardData.error) {
|
|
708
|
-
console.log(` ${C.red}✗ Failed to fetch rewards: ${rewardData.error}${C.reset}`);
|
|
709
|
-
compoundResults.push({ stake_account: sa, status: 'error', error: rewardData.error });
|
|
710
|
-
continue;
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
const estimatedRewards = BigInt(rewardData.estimatedRewards);
|
|
714
|
-
if (estimatedRewards === BigInt(0)) {
|
|
715
|
-
console.log(` ${C.yellow}⚠ No rewards to compound${C.reset}`);
|
|
716
|
-
compoundResults.push({ stake_account: sa, status: 'no_rewards', rewards: '0' });
|
|
717
|
-
continue;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
console.log(` ${C.dim}Rewards to compound:${C.reset} ${rewardData.estimatedRewardsFormatted}`);
|
|
721
|
-
console.log(` ${C.dim}Validator:${C.reset} ${rewardData.validator || 'unknown'}`);
|
|
722
|
-
|
|
723
|
-
// Build compound transaction (ClaimRewards + Stake in one)
|
|
724
|
-
const tx = {
|
|
725
|
-
type: 'CompoundRewards',
|
|
726
|
-
from: address,
|
|
727
|
-
stake_account: sa,
|
|
728
|
-
lamports: estimatedRewards.toString(),
|
|
729
|
-
validator: rewardData.validator || null,
|
|
730
|
-
timestamp: Math.floor(Date.now() / 1000),
|
|
731
|
-
};
|
|
732
|
-
|
|
733
|
-
// Sign transaction
|
|
734
|
-
const txData = JSON.stringify(tx);
|
|
735
|
-
const txHash = crypto.createHash('sha256').update(txData).digest('hex');
|
|
736
|
-
const signature = nacl.hash(Buffer.from(txHash, 'hex'));
|
|
737
|
-
const signatureB58 = bs58.encode(signature.slice(0, 64));
|
|
738
|
-
tx.signature = signatureB58;
|
|
739
|
-
|
|
740
|
-
// Submit transaction
|
|
741
|
-
const result = await httpPost(rpc, '/v1/tx', tx);
|
|
742
|
-
|
|
743
|
-
if (result.success || result.txid || result.signature) {
|
|
744
|
-
console.log(` ${C.green}✓ Compounded successfully${C.reset}`);
|
|
745
|
-
console.log(` ${C.dim}TX: ${(result.txid || result.signature || signatureB58).substring(0, 20)}...${C.reset}`);
|
|
746
|
-
totalCompounded += estimatedRewards;
|
|
747
|
-
successCount++;
|
|
748
|
-
compoundResults.push({
|
|
749
|
-
stake_account: sa,
|
|
750
|
-
status: 'compounded',
|
|
751
|
-
rewards: estimatedRewards.toString(),
|
|
752
|
-
rewards_formatted: rewardData.estimatedRewardsFormatted,
|
|
753
|
-
tx: result.txid || result.signature || signatureB58,
|
|
754
|
-
});
|
|
755
|
-
} else {
|
|
756
|
-
console.log(` ${C.red}✗ Compound failed: ${result.error || JSON.stringify(result)}${C.reset}`);
|
|
757
|
-
compoundResults.push({ stake_account: sa, status: 'failed', error: result.error });
|
|
758
|
-
}
|
|
759
|
-
} catch (err) {
|
|
760
|
-
console.log(` ${C.red}✗ Error: ${err.message}${C.reset}`);
|
|
761
|
-
compoundResults.push({ stake_account: sa, status: 'error', error: err.message });
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
console.log();
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
rl.close();
|
|
768
|
-
|
|
769
|
-
// Summary
|
|
770
|
-
console.log(`${C.bright}${C.cyan}╔══════════════════════════════════════════════════════════════╗${C.reset}`);
|
|
771
|
-
console.log(`${C.bright}${C.cyan}║ Compound Summary ║${C.reset}`);
|
|
772
|
-
console.log(`${C.bright}${C.cyan}╚══════════════════════════════════════════════════════════════╝${C.reset}\n`);
|
|
773
|
-
console.log(` ${C.dim}Accounts processed:${C.reset} ${stakeAccounts.length}`);
|
|
774
|
-
console.log(` ${C.green}✓ Successful:${C.reset} ${successCount}`);
|
|
775
|
-
console.log(` ${C.dim}Total compounded:${C.reset} ${C.green}${formatAether(totalCompounded.toString())}${C.reset}\n`);
|
|
776
|
-
|
|
777
|
-
if (isJson) {
|
|
778
|
-
console.log(JSON.stringify({
|
|
779
|
-
address,
|
|
780
|
-
total_compounded_lamports: totalCompounded.toString(),
|
|
781
|
-
total_compounded_formatted: formatAether(totalCompounded.toString()),
|
|
782
|
-
accounts_processed: stakeAccounts.length,
|
|
783
|
-
successful: successCount,
|
|
784
|
-
results: compoundResults,
|
|
785
|
-
}, null, 2));
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
// ---------------------------------------------------------------------------
|
|
790
|
-
// Parse CLI args
|
|
791
|
-
// ---------------------------------------------------------------------------
|
|
792
|
-
|
|
793
|
-
function parseArgs() {
|
|
794
|
-
const args = process.argv.slice(3); // [node, index.js, rewards, <subcmd>, ...]
|
|
795
|
-
return args;
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
function createRl() {
|
|
799
|
-
return readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
function question(rl, q) {
|
|
803
|
-
return new Promise((res) => rl.question(q, res));
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
// ---------------------------------------------------------------------------
|
|
807
|
-
// Main entry point
|
|
808
|
-
// ---------------------------------------------------------------------------
|
|
809
|
-
|
|
810
|
-
async function main() {
|
|
811
|
-
const rawArgs = parseArgs();
|
|
812
|
-
const subcmd = rawArgs[0] || 'list';
|
|
813
|
-
|
|
814
|
-
// Parse common flags from all args
|
|
815
|
-
const allArgs = rawArgs.slice(1);
|
|
816
|
-
const rpcIndex = allArgs.findIndex(a => a === '--rpc');
|
|
817
|
-
const rpc = rpcIndex !== -1 ? allArgs[rpcIndex + 1] : getDefaultRpc();
|
|
818
|
-
|
|
819
|
-
const parsed = {
|
|
820
|
-
rpc,
|
|
821
|
-
json: allArgs.includes('--json'),
|
|
822
|
-
address: null,
|
|
823
|
-
account: null,
|
|
824
|
-
};
|
|
825
|
-
|
|
826
|
-
// Extract --address and --account flags
|
|
827
|
-
const addrIdx = allArgs.findIndex(a => a === '--address');
|
|
828
|
-
if (addrIdx !== -1 && allArgs[addrIdx + 1]) parsed.address = allArgs[addrIdx + 1];
|
|
829
|
-
|
|
830
|
-
const acctIdx = allArgs.findIndex(a => a === '--account');
|
|
831
|
-
if (acctIdx !== -1 && allArgs[acctIdx + 1]) parsed.account = allArgs[acctIdx + 1];
|
|
832
|
-
|
|
833
|
-
switch (subcmd) {
|
|
834
|
-
case 'list':
|
|
835
|
-
await rewardsList(parsed);
|
|
836
|
-
break;
|
|
837
|
-
case 'summary':
|
|
838
|
-
await rewardsSummary(parsed);
|
|
839
|
-
break;
|
|
840
|
-
case 'pending':
|
|
841
|
-
await rewardsPending(parsed);
|
|
842
|
-
break;
|
|
843
|
-
case 'claim':
|
|
844
|
-
await rewardsClaim(parsed);
|
|
845
|
-
break;
|
|
846
|
-
case 'compound':
|
|
847
|
-
await rewardsCompound(parsed);
|
|
848
|
-
break;
|
|
849
|
-
default:
|
|
850
|
-
console.log(`\n${C.cyan}Usage:${C.reset}`);
|
|
851
|
-
console.log(` aether rewards list --address <addr> List all staking rewards`);
|
|
852
|
-
console.log(` aether rewards summary --address <addr> One-line rewards summary`);
|
|
853
|
-
console.log(` aether rewards pending --address <addr> Show pending (unclaimed) rewards`);
|
|
854
|
-
console.log(` aether rewards claim --address <addr> [--account <stakeAcct>] Claim rewards`);
|
|
855
|
-
console.log(` aether rewards compound --address <addr> [--account <stakeAcct>] Claim and re-stake rewards`);
|
|
856
|
-
console.log();
|
|
857
|
-
console.log(` ${C.dim}--json Output as JSON`);
|
|
858
|
-
console.log(` --rpc <url> Use specific RPC endpoint${C.reset}\n`);
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
main().catch(err => {
|
|
863
|
-
console.error(`\n${C.red}Error running rewards command:${C.reset}`, err.message, '\n');
|
|
864
|
-
});
|
|
865
|
-
|
|
866
|
-
module.exports = { rewardsCommand: main };
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* aether-cli rewards
|
|
4
|
+
*
|
|
5
|
+
* View staking rewards earned from delegated stake accounts.
|
|
6
|
+
* Shows accumulated rewards, estimated APY, and claimable amounts.
|
|
7
|
+
* Uses @jellylegsai/aether-sdk for REAL blockchain RPC calls.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* aether rewards list --address <addr> List all rewards per stake account
|
|
11
|
+
* aether rewards list --address <addr> --json JSON output for scripting
|
|
12
|
+
* aether rewards summary --address <addr> One-line summary of total rewards
|
|
13
|
+
* aether rewards pending --address <addr> Show pending (unclaimed) rewards
|
|
14
|
+
*
|
|
15
|
+
* Requires AETHER_RPC env var or local node running (default: http://127.0.0.1:8899)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const os = require('os');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const readline = require('readline');
|
|
22
|
+
|
|
23
|
+
// Import SDK for real blockchain RPC calls
|
|
24
|
+
const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
|
|
25
|
+
const { AetherClient } = require(sdkPath);
|
|
26
|
+
|
|
27
|
+
// Import theme
|
|
28
|
+
const theme = require('../theme');
|
|
29
|
+
const { C, BANNERS, ICONS } = theme;
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Paths & config
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
function getAetherDir() {
|
|
36
|
+
return path.join(os.homedir(), '.aether');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function loadConfig() {
|
|
40
|
+
const p = path.join(getAetherDir(), 'config.json');
|
|
41
|
+
if (!fs.existsSync(p)) return { defaultWallet: null };
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
44
|
+
} catch {
|
|
45
|
+
return { defaultWallet: null };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function loadWallet(address) {
|
|
50
|
+
const fp = path.join(getAetherDir(), 'wallets', `${address}.json`);
|
|
51
|
+
if (!fs.existsSync(fp)) return null;
|
|
52
|
+
return JSON.parse(fs.readFileSync(fp, 'utf8'));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// SDK helpers
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
function getDefaultRpc() {
|
|
60
|
+
return process.env.AETHER_RPC || 'http://127.0.0.1:8899';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function createClient(rpcUrl) {
|
|
64
|
+
return new AetherClient({ rpcUrl });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function formatAether(lamports) {
|
|
68
|
+
const aeth = lamports / 1e9;
|
|
69
|
+
if (aeth === 0) return '0 AETH';
|
|
70
|
+
return aeth.toFixed(4).replace(/\.?0+$/, '') + ' AETH';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function formatAethFull(lamports) {
|
|
74
|
+
return (lamports / 1e9).toFixed(6) + ' AETH';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function shortAddress(addr) {
|
|
78
|
+
if (!addr || addr.length < 16) return addr || 'unknown';
|
|
79
|
+
return addr.slice(0, 8) + '...' + addr.slice(-8);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Rewards calculation using SDK
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Fetch stake account info and compute rewards using SDK.
|
|
88
|
+
* Uses real RPC calls to get stake positions and rewards data.
|
|
89
|
+
*/
|
|
90
|
+
async function fetchStakeRewards(rpc, stakeAddress) {
|
|
91
|
+
const client = createClient(rpc);
|
|
92
|
+
try {
|
|
93
|
+
// Fetch stake positions using SDK
|
|
94
|
+
const stakeData = await client.getStakePositions(stakeAddress);
|
|
95
|
+
|
|
96
|
+
// Get epoch info for APY calculation
|
|
97
|
+
const epochInfo = await client.getEpochInfo();
|
|
98
|
+
const currentEpoch = epochInfo.epoch || 0;
|
|
99
|
+
|
|
100
|
+
// Calculate rewards from stake data
|
|
101
|
+
let delegatedStake = BigInt(0);
|
|
102
|
+
let activationEpoch = 0;
|
|
103
|
+
let deactivationEpoch = null;
|
|
104
|
+
let pendingRewards = BigInt(0);
|
|
105
|
+
let validator = 'unknown';
|
|
106
|
+
|
|
107
|
+
if (Array.isArray(stakeData)) {
|
|
108
|
+
stakeData.forEach(stake => {
|
|
109
|
+
delegatedStake += BigInt(stake.lamports || stake.delegated_stake || 0);
|
|
110
|
+
pendingRewards += BigInt(stake.pending_rewards || stake.rewards || 0);
|
|
111
|
+
if (stake.validator) validator = stake.validator;
|
|
112
|
+
if (stake.activation_epoch) activationEpoch = stake.activation_epoch;
|
|
113
|
+
if (stake.deactivation_epoch) deactivationEpoch = stake.deactivation_epoch;
|
|
114
|
+
});
|
|
115
|
+
} else if (stakeData) {
|
|
116
|
+
delegatedStake = BigInt(stakeData.lamports || stakeData.delegated_stake || 0);
|
|
117
|
+
pendingRewards = BigInt(stakeData.pending_rewards || stakeData.rewards || 0);
|
|
118
|
+
validator = stakeData.validator || 'unknown';
|
|
119
|
+
activationEpoch = stakeData.activation_epoch || 0;
|
|
120
|
+
deactivationEpoch = stakeData.deactivation_epoch || null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Get rewards for APY calculation
|
|
124
|
+
const rewardsInfo = await client.getRewards(stakeAddress);
|
|
125
|
+
const rewardsPerEpoch = BigInt(rewardsInfo.rewards_per_epoch || '2000000000');
|
|
126
|
+
|
|
127
|
+
// Calculate active epochs
|
|
128
|
+
const activeFromEpoch = activationEpoch;
|
|
129
|
+
const activeToEpoch = deactivationEpoch || currentEpoch;
|
|
130
|
+
const activeEpochs = Math.max(0, activeToEpoch - activeFromEpoch);
|
|
131
|
+
|
|
132
|
+
// APY calculation
|
|
133
|
+
const totalNetworkStake = BigInt(epochInfo.total_staked || '1000000000000');
|
|
134
|
+
const rewardsRate = Number(rewardsPerEpoch * BigInt(365)) / Number(totalNetworkStake);
|
|
135
|
+
const apyBps = Math.round(rewardsRate * 10000);
|
|
136
|
+
|
|
137
|
+
// Estimated rewards
|
|
138
|
+
const stakeAeth = Number(delegatedStake) / 1e9;
|
|
139
|
+
const yearEpochs = 73; // ~73 epochs/year
|
|
140
|
+
const estimatedAnnualRewards = stakeAeth * (apyBps / 10000);
|
|
141
|
+
const estimatedRewards = (estimatedAnnualRewards / yearEpochs) * activeEpochs;
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
stakeAddress,
|
|
145
|
+
delegatedStake: delegatedStake.toString(),
|
|
146
|
+
delegatedStakeFormatted: formatAether(Number(delegatedStake)),
|
|
147
|
+
activationEpoch: activeFromEpoch,
|
|
148
|
+
deactivationEpoch,
|
|
149
|
+
isActive: deactivationEpoch === null,
|
|
150
|
+
activeEpochs,
|
|
151
|
+
estimatedRewards: Math.round(estimatedRewards * 1e9),
|
|
152
|
+
estimatedRewardsFormatted: formatAether(Math.round(estimatedRewards * 1e9)),
|
|
153
|
+
pendingRewards: pendingRewards.toString(),
|
|
154
|
+
apyBps,
|
|
155
|
+
validator,
|
|
156
|
+
};
|
|
157
|
+
} catch (err) {
|
|
158
|
+
return { stakeAddress, error: err.message };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Fetch all stake accounts for a wallet address using SDK.
|
|
164
|
+
*/
|
|
165
|
+
async function fetchWalletStakeAccounts(rpc, walletAddress) {
|
|
166
|
+
const client = createClient(rpc);
|
|
167
|
+
try {
|
|
168
|
+
const stakeAccounts = await client.getStakeAccounts(walletAddress);
|
|
169
|
+
if (Array.isArray(stakeAccounts)) {
|
|
170
|
+
return stakeAccounts.map(acc => acc.pubkey || acc.address || acc);
|
|
171
|
+
}
|
|
172
|
+
return [];
|
|
173
|
+
} catch {
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
// Rewards list command
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
async function rewardsList(args) {
|
|
183
|
+
const rpc = args.rpc || getDefaultRpc();
|
|
184
|
+
const isJson = args.json || false;
|
|
185
|
+
let address = args.address || null;
|
|
186
|
+
|
|
187
|
+
// Interactive address prompt if not provided
|
|
188
|
+
if (!address) {
|
|
189
|
+
const config = loadConfig();
|
|
190
|
+
const rl = createRl();
|
|
191
|
+
const answer = await question(rl, `\n${C.cyan}Enter wallet address (or press Enter for default): ${C.reset}`);
|
|
192
|
+
rl.close();
|
|
193
|
+
|
|
194
|
+
if (!answer.trim()) {
|
|
195
|
+
if (!config.defaultWallet) {
|
|
196
|
+
console.log(`\n${ICONS.error} ${C.red}No default wallet and no address provided.${C.reset}`);
|
|
197
|
+
console.log(` ${C.dim}Set a default wallet first: aether wallet default${C.reset}\n`);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
address = config.defaultWallet;
|
|
201
|
+
} else {
|
|
202
|
+
address = answer.trim();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Validate address format (ATH...)
|
|
207
|
+
if (!address.startsWith('ATH') || address.length < 30) {
|
|
208
|
+
const config = loadConfig();
|
|
209
|
+
if (config.defaultWallet) address = config.defaultWallet;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
console.log(`\n${BANNERS.rewards}`);
|
|
213
|
+
console.log(` ${C.dim}Address:${C.reset} ${address}\n`);
|
|
214
|
+
|
|
215
|
+
// Fetch stake accounts using SDK
|
|
216
|
+
const stakeAccounts = await fetchWalletStakeAccounts(rpc, address);
|
|
217
|
+
|
|
218
|
+
if (stakeAccounts.length === 0) {
|
|
219
|
+
console.log(` ${ICONS.warning} ${C.yellow}No stake accounts found for this wallet.${C.reset}`);
|
|
220
|
+
console.log(` ${C.dim}Stake AETH first: aether stake --address ${address} --validator <val> --amount <aeth>${C.reset}\n`);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Fetch rewards for each stake account using SDK
|
|
225
|
+
const rewardsResults = await Promise.all(
|
|
226
|
+
stakeAccounts.map(sa => fetchStakeRewards(rpc, sa))
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
let totalEstimatedRewards = BigInt(0);
|
|
230
|
+
let totalDelegatedStake = BigInt(0);
|
|
231
|
+
let activeCount = 0;
|
|
232
|
+
const rows = [];
|
|
233
|
+
|
|
234
|
+
for (const result of rewardsResults) {
|
|
235
|
+
if (result.error) {
|
|
236
|
+
rows.push({ status: 'error', ...result });
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
totalEstimatedRewards += BigInt(result.estimatedRewards);
|
|
241
|
+
totalDelegatedStake += BigInt(result.delegatedStake);
|
|
242
|
+
if (result.isActive) activeCount++;
|
|
243
|
+
|
|
244
|
+
rows.push(result);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (isJson) {
|
|
248
|
+
console.log(JSON.stringify({
|
|
249
|
+
address,
|
|
250
|
+
totalEstimatedRewards: totalEstimatedRewards.toString(),
|
|
251
|
+
totalEstimatedRewardsFormatted: formatAether(Number(totalEstimatedRewards)),
|
|
252
|
+
totalDelegatedStake: totalDelegatedStake.toString(),
|
|
253
|
+
totalDelegatedStakeFormatted: formatAether(Number(totalDelegatedStake)),
|
|
254
|
+
activeStakeAccounts: activeCount,
|
|
255
|
+
totalStakeAccounts: rows.length,
|
|
256
|
+
stakeAccounts: rows,
|
|
257
|
+
}, null, 2));
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ASCII table header
|
|
262
|
+
console.log(` ${C.dim}┌─────────────────────────────────────────────────────────────┐${C.reset}`);
|
|
263
|
+
console.log(` ${C.dim}│${C.reset} ${C.bright}Stake Account${C.reset} ${C.bright}Delegated${C.reset} ${C.bright}Est. Rewards${C.reset} ${C.bright}APY${C.reset} ${C.bright}Status${C.reset} ${C.dim}│${C.reset}`);
|
|
264
|
+
console.log(` ${C.dim}├─────────────────────────────────────────────────────────────┤${C.reset}`);
|
|
265
|
+
|
|
266
|
+
for (const r of rows) {
|
|
267
|
+
const shortAddr = r.stakeAddress ? r.stakeAddress.substring(0, 14) + '...' : 'unknown';
|
|
268
|
+
const delegated = r.delegatedStakeFormatted || '—';
|
|
269
|
+
const estRew = r.estimatedRewardsFormatted || '—';
|
|
270
|
+
const apy = r.apyBps ? `${(r.apyBps / 100).toFixed(2)}%` : '—';
|
|
271
|
+
const status = r.isActive
|
|
272
|
+
? `${C.green}● Active${C.reset}`
|
|
273
|
+
: r.deactivationEpoch
|
|
274
|
+
? `${C.yellow}○ Deactivated${C.reset}`
|
|
275
|
+
: `${C.red}✗ Error${C.reset}`;
|
|
276
|
+
|
|
277
|
+
console.log(
|
|
278
|
+
` ${C.dim}│${C.reset} ${shortAddr.padEnd(20)} ${delegated.padEnd(13)} ${estRew.padEnd(15)} ${apy.padEnd(7)} ${status} ${C.dim}│${C.reset}`
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
console.log(` ${C.dim}└─────────────────────────────────────────────────────────────┘${C.reset}`);
|
|
283
|
+
console.log();
|
|
284
|
+
console.log(` ${C.bright}Total Delegated:${C.reset} ${C.cyan}${formatAether(Number(totalDelegatedStake))}${C.reset}`);
|
|
285
|
+
console.log(` ${C.bright}Total Est. Rewards:${C.reset} ${C.green}${formatAether(Number(totalEstimatedRewards))}${C.reset}`);
|
|
286
|
+
console.log(` ${C.bright}Active Accounts:${C.reset} ${activeCount} of ${rows.length}`);
|
|
287
|
+
console.log();
|
|
288
|
+
console.log(` ${C.dim}Run "aether claim --address ${address}" to claim rewards.${C.reset}\n`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ---------------------------------------------------------------------------
|
|
292
|
+
// Rewards summary command (one-line)
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
|
|
295
|
+
async function rewardsSummary(args) {
|
|
296
|
+
const rpc = args.rpc || getDefaultRpc();
|
|
297
|
+
let address = args.address || null;
|
|
298
|
+
|
|
299
|
+
if (!address) {
|
|
300
|
+
const config = loadConfig();
|
|
301
|
+
if (!config.defaultWallet) {
|
|
302
|
+
console.log(`${C.red}✗ No default wallet and no address provided.${C.reset}`);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
address = config.defaultWallet;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const stakeAccounts = await fetchWalletStakeAccounts(rpc, address);
|
|
309
|
+
if (stakeAccounts.length === 0) {
|
|
310
|
+
console.log(`${C.yellow}⚠ No stake accounts for ${address.substring(0, 12)}...${C.reset}`);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const results = await Promise.all(stakeAccounts.map(sa => fetchStakeRewards(rpc, sa)));
|
|
315
|
+
let totalRewards = BigInt(0);
|
|
316
|
+
let totalStake = BigInt(0);
|
|
317
|
+
let activeCount = 0;
|
|
318
|
+
|
|
319
|
+
for (const r of results) {
|
|
320
|
+
if (!r.error) {
|
|
321
|
+
totalRewards += BigInt(r.estimatedRewards);
|
|
322
|
+
totalStake += BigInt(r.delegatedStake);
|
|
323
|
+
if (r.isActive) activeCount++;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
console.log(`${C.cyan}${address.substring(0, 12)}...${C.reset} │ Stake: ${C.cyan}${formatAether(Number(totalStake))}${C.reset} │ Est.Rewards: ${C.green}${formatAether(Number(totalRewards))}${C.reset} │ Active: ${activeCount}/${results.length}`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ---------------------------------------------------------------------------
|
|
331
|
+
// Rewards pending command
|
|
332
|
+
// ---------------------------------------------------------------------------
|
|
333
|
+
|
|
334
|
+
async function rewardsPending(args) {
|
|
335
|
+
const rpc = args.rpc || getDefaultRpc();
|
|
336
|
+
const isJson = args.json || false;
|
|
337
|
+
let address = args.address || null;
|
|
338
|
+
|
|
339
|
+
const config = loadConfig();
|
|
340
|
+
const rl = createRl();
|
|
341
|
+
|
|
342
|
+
if (!address) {
|
|
343
|
+
const ans = await question(rl, `\n${C.cyan}Enter wallet address: ${C.reset}`);
|
|
344
|
+
address = ans.trim();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (!address) {
|
|
348
|
+
console.log(`\n${ICONS.error} ${C.red}No address provided.${C.reset}\n`);
|
|
349
|
+
rl.close();
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
rl.close();
|
|
354
|
+
|
|
355
|
+
const stakeAccounts = await fetchWalletStakeAccounts(rpc, address);
|
|
356
|
+
if (stakeAccounts.length === 0) {
|
|
357
|
+
if (isJson) {
|
|
358
|
+
console.log(JSON.stringify({ address, pending: [], total_pending: '0' }, null, 2));
|
|
359
|
+
} else {
|
|
360
|
+
console.log(`\n${ICONS.error} ${C.red}No stake accounts found for ${address}${C.reset}\n`);
|
|
361
|
+
}
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const results = [];
|
|
366
|
+
let totalPending = BigInt(0);
|
|
367
|
+
|
|
368
|
+
for (const sa of stakeAccounts) {
|
|
369
|
+
const rd = await fetchStakeRewards(rpc, sa);
|
|
370
|
+
if (!rd.error) {
|
|
371
|
+
const pending = BigInt(rd.pendingRewards || 0);
|
|
372
|
+
totalPending += pending;
|
|
373
|
+
results.push({
|
|
374
|
+
stake_account: sa,
|
|
375
|
+
validator: rd.validator || 'unknown',
|
|
376
|
+
delegated_stake: rd.delegatedStakeFormatted || '0',
|
|
377
|
+
pending_rewards: formatAether(Number(pending)),
|
|
378
|
+
pending_lamports: pending.toString(),
|
|
379
|
+
apy_bps: rd.apyBps || 0,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (isJson) {
|
|
385
|
+
console.log(JSON.stringify({
|
|
386
|
+
address,
|
|
387
|
+
total_pending: totalPending.toString(),
|
|
388
|
+
total_pending_formatted: formatAether(Number(totalPending)),
|
|
389
|
+
accounts: results,
|
|
390
|
+
}, null, 2));
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
console.log(`\n${C.bright}${C.cyan}╔══════════════════════════════════════════════════════════════╗${C.reset}`);
|
|
395
|
+
console.log(`${C.bright}${C.cyan}║ Pending Staking Rewards ║${C.reset}`);
|
|
396
|
+
console.log(`${C.bright}${C.cyan}╚══════════════════════════════════════════════════════════════╝${C.reset}\n`);
|
|
397
|
+
console.log(` ${C.dim}Wallet:${C.reset} ${C.bright}${address}${C.reset}`);
|
|
398
|
+
console.log();
|
|
399
|
+
console.log(` ${C.yellow}Stake Account${C.reset.padEnd(48)} ${C.yellow}Pending${C.reset} ${C.yellow}APY${C.reset}`);
|
|
400
|
+
console.log(` ${C.dim}${'─'.repeat(72)}${C.reset}`);
|
|
401
|
+
|
|
402
|
+
for (const r of results) {
|
|
403
|
+
const shortSa = r.stake_account.substring(0, 12) + '...' + r.stake_account.slice(-6);
|
|
404
|
+
console.log(` ${C.cyan}${shortSa}${C.reset.padEnd(52)} ${C.green}${r.pending_rewards.padStart(12)}${C.reset} ${(r.apy_bps / 100).toFixed(2)}%`);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
console.log(` ${C.dim}${'─'.repeat(72)}${C.reset}`);
|
|
408
|
+
console.log(` ${C.bright}TOTAL PENDING${C.reset.padEnd(52)} ${C.green}${formatAethFull(Number(totalPending)).padStart(12)}${C.reset}`);
|
|
409
|
+
console.log();
|
|
410
|
+
console.log(` ${C.dim}Run ${C.cyan}aether claim --address ${address}${C.dim} to claim.${C.reset}\n`);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ---------------------------------------------------------------------------
|
|
414
|
+
// Parse CLI args
|
|
415
|
+
// ---------------------------------------------------------------------------
|
|
416
|
+
|
|
417
|
+
function parseArgs() {
|
|
418
|
+
const args = process.argv.slice(3); // [node, index.js, rewards, <subcmd>, ...]
|
|
419
|
+
return args;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function createRl() {
|
|
423
|
+
return readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function question(rl, q) {
|
|
427
|
+
return new Promise((res) => rl.question(q, res));
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// ---------------------------------------------------------------------------
|
|
431
|
+
// Main entry point
|
|
432
|
+
// ---------------------------------------------------------------------------
|
|
433
|
+
|
|
434
|
+
async function main() {
|
|
435
|
+
const rawArgs = parseArgs();
|
|
436
|
+
const subcmd = rawArgs[0] || 'list';
|
|
437
|
+
|
|
438
|
+
// Parse common flags from all args
|
|
439
|
+
const allArgs = rawArgs.slice(1);
|
|
440
|
+
const rpcIndex = allArgs.findIndex(a => a === '--rpc');
|
|
441
|
+
const rpc = rpcIndex !== -1 ? allArgs[rpcIndex + 1] : getDefaultRpc();
|
|
442
|
+
|
|
443
|
+
const parsed = {
|
|
444
|
+
rpc,
|
|
445
|
+
json: allArgs.includes('--json'),
|
|
446
|
+
address: null,
|
|
447
|
+
account: null,
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
// Extract --address flag
|
|
451
|
+
const addrIdx = allArgs.findIndex(a => a === '--address');
|
|
452
|
+
if (addrIdx !== -1 && allArgs[addrIdx + 1]) parsed.address = allArgs[addrIdx + 1];
|
|
453
|
+
|
|
454
|
+
switch (subcmd) {
|
|
455
|
+
case 'list':
|
|
456
|
+
await rewardsList(parsed);
|
|
457
|
+
break;
|
|
458
|
+
case 'summary':
|
|
459
|
+
await rewardsSummary(parsed);
|
|
460
|
+
break;
|
|
461
|
+
case 'pending':
|
|
462
|
+
await rewardsPending(parsed);
|
|
463
|
+
break;
|
|
464
|
+
default:
|
|
465
|
+
console.log(`\n${C.cyan}Usage:${C.reset}`);
|
|
466
|
+
console.log(` aether rewards list --address <addr> List all staking rewards`);
|
|
467
|
+
console.log(` aether rewards summary --address <addr> One-line rewards summary`);
|
|
468
|
+
console.log(` aether rewards pending --address <addr> Show pending (unclaimed) rewards`);
|
|
469
|
+
console.log();
|
|
470
|
+
console.log(` ${C.dim}--json Output as JSON`);
|
|
471
|
+
console.log(` --rpc <url> Use specific RPC endpoint${C.reset}\n`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
main().catch(err => {
|
|
476
|
+
console.error(`\n${C.red}Error running rewards command:${C.reset}`, err.message, '\n');
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
module.exports = { rewardsCommand: main };
|