aether-hub 1.1.0 → 1.1.2
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/delegations.js +462 -0
- package/commands/emergency.js +657 -0
- package/commands/rewards.js +600 -0
- package/commands/snapshot.js +509 -0
- package/commands/validators.js +326 -0
- package/commands/wallet.js +168 -0
- package/index.js +389 -341
- package/package.json +2 -2
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* aether-cli delegations
|
|
4
|
+
*
|
|
5
|
+
* View stake delegations and accumulated rewards for a wallet.
|
|
6
|
+
* Also supports claiming rewards from a stake account.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* aether delegations list --address <addr> List all stake delegations
|
|
10
|
+
* aether delegations list --address <addr> --json JSON output
|
|
11
|
+
* aether delegations claim --address <addr> --account <stakeAcct> [--json]
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const http = require('http');
|
|
15
|
+
const https = require('https');
|
|
16
|
+
const readline = require('readline');
|
|
17
|
+
const crypto = require('crypto');
|
|
18
|
+
const bs58 = require('bs58').default;
|
|
19
|
+
const bip39 = require('bip39');
|
|
20
|
+
const nacl = require('tweetnacl');
|
|
21
|
+
|
|
22
|
+
// ANSI colours
|
|
23
|
+
const C = {
|
|
24
|
+
reset: '\x1b[0m',
|
|
25
|
+
bright: '\x1b[1m',
|
|
26
|
+
dim: '\x1b[2m',
|
|
27
|
+
red: '\x1b[31m',
|
|
28
|
+
green: '\x1b[32m',
|
|
29
|
+
yellow: '\x1b[33m',
|
|
30
|
+
cyan: '\x1b[36m',
|
|
31
|
+
magenta: '\x1b[35m',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const DERIVATION_PATH = "m/44'/7777777'/0'/0'";
|
|
35
|
+
const CLI_VERSION = '1.0.5';
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Paths & config
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
function getAetherDir() {
|
|
42
|
+
return require('path').join(require('os').homedir(), '.aether');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function loadConfig() {
|
|
46
|
+
const p = require('path').join(getAetherDir(), 'config.json');
|
|
47
|
+
if (!require('fs').existsSync(p)) return { defaultWallet: null };
|
|
48
|
+
try {
|
|
49
|
+
return JSON.parse(require('fs').readFileSync(p, 'utf8'));
|
|
50
|
+
} catch {
|
|
51
|
+
return { defaultWallet: null };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function loadWallet(address) {
|
|
56
|
+
const fp = require('path').join(getAetherDir(), 'wallets', `${address}.json`);
|
|
57
|
+
if (!require('fs').existsSync(fp)) return null;
|
|
58
|
+
return JSON.parse(require('fs').readFileSync(fp, 'utf8'));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Crypto helpers (mirrored from wallet.js)
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
function deriveKeypair(mnemonic) {
|
|
66
|
+
if (!bip39.validateMnemonic(mnemonic)) throw new Error('Invalid mnemonic');
|
|
67
|
+
const seedBuffer = bip39.mnemonicToSeedSync(mnemonic, '');
|
|
68
|
+
const seed32 = seedBuffer.slice(0, 32);
|
|
69
|
+
const keyPair = nacl.sign.keyPair.fromSeed(seed32);
|
|
70
|
+
return { publicKey: Buffer.from(keyPair.publicKey), secretKey: Buffer.from(keyPair.secretKey) };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function formatAddress(publicKey) {
|
|
74
|
+
return 'ATH' + bs58.encode(publicKey);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// HTTP helpers
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
function httpRequest(rpcUrl, path) {
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
const url = new URL(path, rpcUrl);
|
|
84
|
+
const lib = url.protocol === 'https:' ? https : http;
|
|
85
|
+
const req = lib.request({
|
|
86
|
+
hostname: url.hostname,
|
|
87
|
+
port: url.port || (url.protocol === 'https:' ? 443 : 80),
|
|
88
|
+
path: url.pathname + url.search,
|
|
89
|
+
method: 'GET',
|
|
90
|
+
timeout: 8000,
|
|
91
|
+
headers: { 'Content-Type': 'application/json' },
|
|
92
|
+
}, (res) => {
|
|
93
|
+
let data = '';
|
|
94
|
+
res.on('data', (chunk) => data += chunk);
|
|
95
|
+
res.on('end', () => { try { resolve(JSON.parse(data)); } catch { resolve({ raw: data }); } });
|
|
96
|
+
});
|
|
97
|
+
req.on('error', reject);
|
|
98
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('Request timeout')); });
|
|
99
|
+
req.end();
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function httpPost(rpcUrl, path, body) {
|
|
104
|
+
return new Promise((resolve, reject) => {
|
|
105
|
+
const url = new URL(path, rpcUrl);
|
|
106
|
+
const lib = url.protocol === 'https:' ? https : http;
|
|
107
|
+
const bodyStr = JSON.stringify(body);
|
|
108
|
+
const req = lib.request({
|
|
109
|
+
hostname: url.hostname,
|
|
110
|
+
port: url.port || (url.protocol === 'https:' ? 443 : 80),
|
|
111
|
+
path: url.pathname + url.search,
|
|
112
|
+
method: 'POST',
|
|
113
|
+
timeout: 8000,
|
|
114
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(bodyStr) },
|
|
115
|
+
}, (res) => {
|
|
116
|
+
let data = '';
|
|
117
|
+
res.on('data', (chunk) => data += chunk);
|
|
118
|
+
res.on('end', () => { try { resolve(JSON.parse(data)); } catch { resolve({ raw: data }); } });
|
|
119
|
+
});
|
|
120
|
+
req.on('error', reject);
|
|
121
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('Request timeout')); });
|
|
122
|
+
req.write(bodyStr);
|
|
123
|
+
req.end();
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getDefaultRpc() {
|
|
128
|
+
return process.env.AETHER_RPC || 'http://127.0.0.1:8899';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function formatAether(lamports) {
|
|
132
|
+
const aeth = lamports / 1e9;
|
|
133
|
+
if (aeth === 0) return '0 AETH';
|
|
134
|
+
return aeth.toFixed(4).replace(/\.?0+$/, '') + ' AETH';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// Parse CLI args
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
function parseArgs() {
|
|
142
|
+
const args = process.argv.slice(3); // [node, index.js, delegations, <subcmd>, ...]
|
|
143
|
+
return args;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function createRl() {
|
|
147
|
+
return readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function question(rl, q) {
|
|
151
|
+
return new Promise((res) => rl.question(q, res));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function askMnemonic(rl, prompt) {
|
|
155
|
+
console.log(`\n${C.cyan}${prompt}${C.reset}`);
|
|
156
|
+
console.log(`${C.dim}Enter your 12 or 24-word passphrase, one space-separated line:${C.reset}`);
|
|
157
|
+
const raw = await question(rl, ` > ${C.reset}`);
|
|
158
|
+
return raw.trim().toLowerCase();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// LIST DELEGATIONS
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
async function listDelegations(args) {
|
|
166
|
+
const rl = createRl();
|
|
167
|
+
let address = null;
|
|
168
|
+
let asJson = false;
|
|
169
|
+
let rpcUrl = getDefaultRpc();
|
|
170
|
+
|
|
171
|
+
for (let i = 0; i < args.length; i++) {
|
|
172
|
+
if ((args[i] === '--address' || args[i] === '-a') && args[i + 1]) address = args[i + 1];
|
|
173
|
+
else if (args[i] === '--json' || args[i] === '-j') asJson = true;
|
|
174
|
+
else if ((args[i] === '--rpc' || args[i] === '-r') && args[i + 1]) rpcUrl = args[i + 1];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!address) {
|
|
178
|
+
const cfg = loadConfig();
|
|
179
|
+
address = cfg.defaultWallet;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!address) {
|
|
183
|
+
console.log(` ${C.red}✗ No wallet address.${C.reset} Use ${C.cyan}--address <addr>${C.reset} or set a default.`);
|
|
184
|
+
console.log(` ${C.dim}Usage: aether delegations list --address <addr> [--json]${C.reset}\n`);
|
|
185
|
+
rl.close();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const rawAddr = address.startsWith('ATH') ? address.slice(3) : address;
|
|
191
|
+
|
|
192
|
+
// Fetch account info for context
|
|
193
|
+
const account = await httpRequest(rpcUrl, `/v1/account/${rawAddr}`);
|
|
194
|
+
|
|
195
|
+
// Fetch stake accounts for this wallet
|
|
196
|
+
// The RPC may return stake accounts via a dedicated endpoint or as program accounts
|
|
197
|
+
let stakeAccounts = [];
|
|
198
|
+
try {
|
|
199
|
+
const res = await httpRequest(rpcUrl, `/v1/stake?address=${encodeURIComponent(rawAddr)}`);
|
|
200
|
+
if (res && !res.error) {
|
|
201
|
+
stakeAccounts = Array.isArray(res) ? res : (res.accounts || []);
|
|
202
|
+
}
|
|
203
|
+
} catch {
|
|
204
|
+
// Fallback: try program account query
|
|
205
|
+
try {
|
|
206
|
+
const res2 = await httpRequest(rpcUrl, `/v1/program/STAKE/accounts?owner=${encodeURIComponent(rawAddr)}`);
|
|
207
|
+
if (res2 && !res2.error) {
|
|
208
|
+
stakeAccounts = Array.isArray(res2) ? res2 : (res2.accounts || []);
|
|
209
|
+
}
|
|
210
|
+
} catch { /* no stake accounts or RPC doesn't support this endpoint */ }
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (asJson) {
|
|
214
|
+
console.log(JSON.stringify({
|
|
215
|
+
address,
|
|
216
|
+
rpc: rpcUrl,
|
|
217
|
+
account: account && !account.error ? { lamports: account.lamports } : null,
|
|
218
|
+
delegations: stakeAccounts,
|
|
219
|
+
fetched_at: new Date().toISOString(),
|
|
220
|
+
}, null, 2));
|
|
221
|
+
rl.close();
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
console.log(`\n${C.bright}${C.cyan}── Stake Delegations ─────────────────────────────────────${C.reset}\n`);
|
|
226
|
+
console.log(` ${C.green}★${C.reset} Wallet: ${C.bright}${address}${C.reset}`);
|
|
227
|
+
console.log(` ${C.dim} RPC: ${rpcUrl}${C.reset}`);
|
|
228
|
+
if (account && !account.error) {
|
|
229
|
+
console.log(` ${C.green}✓ Balance:${C.reset} ${C.bright}${formatAether(account.lamports || 0)}${C.reset}`);
|
|
230
|
+
}
|
|
231
|
+
console.log();
|
|
232
|
+
|
|
233
|
+
if (stakeAccounts.length === 0) {
|
|
234
|
+
console.log(` ${C.dim}No stake delegations found for this wallet.${C.reset}`);
|
|
235
|
+
console.log(` ${C.dim}Delegate with:${C.reset} ${C.cyan}aether stake --address ${address} --validator <val> --amount <aeth>${C.reset}\n`);
|
|
236
|
+
rl.close();
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const typeColors = {
|
|
241
|
+
Stake: C.green,
|
|
242
|
+
Unstake: C.yellow,
|
|
243
|
+
ClaimRewards: C.magenta,
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
for (const stake of stakeAccounts) {
|
|
247
|
+
const status = stake.status || stake.state || 'active';
|
|
248
|
+
const statusColor = status === 'active' ? C.green : status === 'unstaked' ? C.yellow : C.red;
|
|
249
|
+
const validator = stake.validator || stake.delegation?.validator || 'unknown';
|
|
250
|
+
const amount = stake.lamports || stake.amount || stake.delegation?.lamports || 0;
|
|
251
|
+
const rewards = stake.rewards || stake.pending_rewards || 0;
|
|
252
|
+
const stakeAcct = stake.pubkey || stake.publicKey || stake.account || 'unknown';
|
|
253
|
+
|
|
254
|
+
console.log(` ${C.bright}┌─ ${stakeAcct}${C.reset}`);
|
|
255
|
+
console.log(` │ Validator: ${C.cyan}${validator}${C.reset}`);
|
|
256
|
+
console.log(` │ Amount: ${C.bright}${formatAether(amount)}${C.reset}`);
|
|
257
|
+
if (rewards > 0) {
|
|
258
|
+
console.log(` │ ${C.magenta}★ Rewards: ${formatAether(rewards)}${C.reset}`);
|
|
259
|
+
}
|
|
260
|
+
console.log(` │ Status: ${statusColor}${status}${C.reset}`);
|
|
261
|
+
console.log(` ${C.dim}└${C.reset}`);
|
|
262
|
+
console.log();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Summary
|
|
266
|
+
const totalDelegated = stakeAccounts.reduce((sum, s) => sum + (s.lamports || s.amount || s.delegation?.lamports || 0), 0);
|
|
267
|
+
const totalRewards = stakeAccounts.reduce((sum, s) => sum + (s.rewards || s.pending_rewards || 0), 0);
|
|
268
|
+
console.log(` ${C.dim}────────────────────────────────────────${C.reset}`);
|
|
269
|
+
console.log(` ${C.dim}Total delegated: ${C.reset}${C.bright}${formatAether(totalDelegated)}${C.reset}`);
|
|
270
|
+
if (totalRewards > 0) {
|
|
271
|
+
console.log(` ${C.dim}Total rewards: ${C.reset}${C.bright}${C.magenta}${formatAether(totalRewards)}${C.reset}`);
|
|
272
|
+
console.log(` ${C.dim} Claim with: aether delegations claim --address ${address} --account <stake_account>${C.reset}`);
|
|
273
|
+
}
|
|
274
|
+
console.log();
|
|
275
|
+
rl.close();
|
|
276
|
+
} catch (err) {
|
|
277
|
+
console.log(` ${C.red}✗ Failed to fetch delegations:${C.reset} ${err.message}`);
|
|
278
|
+
console.log(` ${C.dim} Set custom RPC: AETHER_RPC=https://your-rpc-url${C.reset}\n`);
|
|
279
|
+
rl.close();
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
// CLAIM REWARDS
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
|
|
288
|
+
async function claimRewards(args) {
|
|
289
|
+
const rl = createRl();
|
|
290
|
+
|
|
291
|
+
let address = null;
|
|
292
|
+
let stakeAccount = null;
|
|
293
|
+
let asJson = false;
|
|
294
|
+
let rpcUrl = getDefaultRpc();
|
|
295
|
+
|
|
296
|
+
for (let i = 0; i < args.length; i++) {
|
|
297
|
+
if ((args[i] === '--address' || args[i] === '-a') && args[i + 1]) address = args[i + 1];
|
|
298
|
+
else if ((args[i] === '--account' || args[i] === '-s') && args[i + 1]) stakeAccount = args[i + 1];
|
|
299
|
+
else if (args[i] === '--json' || args[i] === '-j') asJson = true;
|
|
300
|
+
else if ((args[i] === '--rpc' || args[i] === '-r') && args[i + 1]) rpcUrl = args[i + 1];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (!address) {
|
|
304
|
+
const cfg = loadConfig();
|
|
305
|
+
address = cfg.defaultWallet;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (!address) {
|
|
309
|
+
console.log(` ${C.red}✗ No wallet address.${C.reset} Use ${C.cyan}--address <addr>${C.reset} or set a default.`);
|
|
310
|
+
console.log(` ${C.dim}Usage: aether delegations claim --address <addr> --account <stakeAcct>${C.reset}\n`);
|
|
311
|
+
rl.close();
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (!stakeAccount) {
|
|
316
|
+
// Fetch list first to let user pick
|
|
317
|
+
let stakeAccounts = [];
|
|
318
|
+
try {
|
|
319
|
+
const res = await httpRequest(rpcUrl, `/v1/stake?address=${encodeURIComponent(address.startsWith('ATH') ? address.slice(3) : address)}`);
|
|
320
|
+
if (res && !res.error) {
|
|
321
|
+
stakeAccounts = Array.isArray(res) ? res : (res.accounts || []);
|
|
322
|
+
}
|
|
323
|
+
} catch { /* noop */ }
|
|
324
|
+
|
|
325
|
+
if (stakeAccounts.length === 0) {
|
|
326
|
+
console.log(` ${C.red}✗ No stake accounts found.${C.reset} Use ${C.cyan}--account <stakeAcct>${C.reset} to specify one.\n`);
|
|
327
|
+
rl.close();
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
console.log(`\n${C.bright}${C.cyan}── Select Stake Account ──────────────────────────────────${C.reset}\n`);
|
|
332
|
+
for (let i = 0; i < stakeAccounts.length; i++) {
|
|
333
|
+
const s = stakeAccounts[i];
|
|
334
|
+
const rewards = s.rewards || s.pending_rewards || 0;
|
|
335
|
+
const validator = s.validator || s.delegation?.validator || 'unknown';
|
|
336
|
+
console.log(` ${C.green}${i + 1})${C.reset} ${s.pubkey || s.publicKey || s.account}`);
|
|
337
|
+
console.log(` Validator: ${C.cyan}${validator}${C.reset} Rewards: ${C.magenta}${formatAether(rewards)}${C.reset}`);
|
|
338
|
+
}
|
|
339
|
+
console.log();
|
|
340
|
+
const choice = await question(rl, ` ${C.cyan}Select account [1-${stakeAccounts.length}]:${C.reset} `);
|
|
341
|
+
const idx = parseInt(choice.trim(), 10) - 1;
|
|
342
|
+
if (isNaN(idx) || idx < 0 || idx >= stakeAccounts.length) {
|
|
343
|
+
console.log(` ${C.red}Invalid selection.${C.reset}\n`);
|
|
344
|
+
rl.close();
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
stakeAccount = stakeAccounts[idx].pubkey || stakeAccounts[idx].publicKey || stakeAccounts[idx].account;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const wallet = loadWallet(address);
|
|
351
|
+
if (!wallet) {
|
|
352
|
+
console.log(` ${C.red}✗ Wallet not found:${C.reset} ${address}\n`);
|
|
353
|
+
rl.close();
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
console.log(`\n${C.bright}${C.cyan}── Claim Rewards ─────────────────────────────────────────${C.reset}\n`);
|
|
358
|
+
console.log(` ${C.green}★${C.reset} Wallet: ${C.bright}${address}${C.reset}`);
|
|
359
|
+
console.log(` ${C.green}★${C.reset} Stake acct: ${C.bright}${stakeAccount}${C.reset}`);
|
|
360
|
+
console.log();
|
|
361
|
+
|
|
362
|
+
// Ask for mnemonic to derive signing keypair
|
|
363
|
+
console.log(`${C.yellow} ⚠ Signing requires your wallet passphrase.${C.reset}`);
|
|
364
|
+
const mnemonic = await askMnemonic(rl, 'Enter your 12/24-word passphrase to sign this transaction');
|
|
365
|
+
console.log();
|
|
366
|
+
|
|
367
|
+
let keyPair;
|
|
368
|
+
try {
|
|
369
|
+
keyPair = deriveKeypair(mnemonic, DERIVATION_PATH);
|
|
370
|
+
} catch (e) {
|
|
371
|
+
console.log(` ${C.red}✗ Failed to derive keypair: ${e.message}${C.reset}\n`);
|
|
372
|
+
rl.close();
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const derivedAddress = formatAddress(keyPair.publicKey);
|
|
377
|
+
if (derivedAddress !== address) {
|
|
378
|
+
console.log(` ${C.red}✗ Passphrase mismatch.${C.reset}`);
|
|
379
|
+
console.log(` ${C.dim} Derived: ${derivedAddress}${C.reset}`);
|
|
380
|
+
console.log(` ${C.dim} Expected: ${address}${C.reset}\n`);
|
|
381
|
+
rl.close();
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const confirm = await question(rl, ` ${C.yellow}Confirm claim? [y/N]${C.reset} > ${C.reset}`);
|
|
386
|
+
if (!confirm.trim().toLowerCase().startsWith('y')) {
|
|
387
|
+
console.log(` ${C.dim}Cancelled.${C.reset}\n`);
|
|
388
|
+
rl.close();
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Build claim rewards transaction
|
|
393
|
+
const tx = {
|
|
394
|
+
signer: address.startsWith('ATH') ? address.slice(3) : address,
|
|
395
|
+
tx_type: 'ClaimRewards',
|
|
396
|
+
payload: {
|
|
397
|
+
type: 'ClaimRewards',
|
|
398
|
+
data: {
|
|
399
|
+
stake_account: stakeAccount,
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
fee: 0,
|
|
403
|
+
slot: 0,
|
|
404
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
console.log(` ${C.dim}Submitting to ${rpcUrl}...${C.reset}`);
|
|
408
|
+
|
|
409
|
+
try {
|
|
410
|
+
const result = await httpPost(rpcUrl, '/v1/tx', tx);
|
|
411
|
+
|
|
412
|
+
if (result.error) {
|
|
413
|
+
console.log(`\n ${C.red}✗ Claim failed:${C.reset} ${result.error}\n`);
|
|
414
|
+
rl.close();
|
|
415
|
+
process.exit(1);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const sig = result.signature || result.tx_signature || result.id || JSON.stringify(result);
|
|
419
|
+
console.log(`\n${C.green}✓ Rewards claim submitted!${C.reset}`);
|
|
420
|
+
console.log(` ${C.dim}Signature: ${sig}${C.reset}`);
|
|
421
|
+
console.log(` ${C.dim}Check balance: aether wallet balance --address ${address}${C.reset}\n`);
|
|
422
|
+
rl.close();
|
|
423
|
+
} catch (err) {
|
|
424
|
+
console.log(` ${C.red}✗ Failed to submit claim:${C.reset} ${err.message}`);
|
|
425
|
+
console.log(` ${C.dim} Is your validator running? RPC: ${rpcUrl}${C.reset}\n`);
|
|
426
|
+
rl.close();
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// ---------------------------------------------------------------------------
|
|
432
|
+
// Main dispatcher
|
|
433
|
+
// ---------------------------------------------------------------------------
|
|
434
|
+
|
|
435
|
+
async function delegationsCommand() {
|
|
436
|
+
const args = parseArgs();
|
|
437
|
+
const subcmd = args[0];
|
|
438
|
+
|
|
439
|
+
const rl = createRl();
|
|
440
|
+
try {
|
|
441
|
+
if (!subcmd || subcmd === 'list') {
|
|
442
|
+
await listDelegations(args);
|
|
443
|
+
} else if (subcmd === 'claim') {
|
|
444
|
+
await claimRewards(args);
|
|
445
|
+
} else {
|
|
446
|
+
console.log(`\n ${C.red}Unknown subcommand:${C.reset} ${subcmd}`);
|
|
447
|
+
console.log(`\n Usage:`);
|
|
448
|
+
console.log(` ${C.cyan}aether delegations list --address <addr>${C.reset} List stake delegations`);
|
|
449
|
+
console.log(` ${C.cyan}aether delegations claim --address <addr> --account <stakeAcct>${C.reset} Claim rewards`);
|
|
450
|
+
console.log();
|
|
451
|
+
process.exit(1);
|
|
452
|
+
}
|
|
453
|
+
} finally {
|
|
454
|
+
rl.close();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
module.exports = { delegationsCommand };
|
|
459
|
+
|
|
460
|
+
if (require.main === module) {
|
|
461
|
+
delegationsCommand();
|
|
462
|
+
}
|