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