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