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