@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,582 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* aether-cli delegations
|
|
4
|
+
*
|
|
5
|
+
* View and manage stake delegations for a wallet.
|
|
6
|
+
* Shows active delegations, validator info, and delegation history.
|
|
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 delegations list --address <addr> List all delegations
|
|
13
|
+
* aether delegations info --account <stakeAcct> Show detailed delegation info
|
|
14
|
+
* aether delegations create --validator <addr> --amount <aeth> Create new delegation
|
|
15
|
+
* aether delegations deactivate --account <stakeAcct> [--amount <aeth>] Begin unstake
|
|
16
|
+
* aether delegations withdraw --account <stakeAcct> Withdraw after cooldown
|
|
17
|
+
*
|
|
18
|
+
* SDK Methods Used:
|
|
19
|
+
* - client.getStakePositions(address) → GET /v1/stake/<addr>
|
|
20
|
+
* - client.getStakeAccounts(address) → GET /v1/stake-accounts/<addr>
|
|
21
|
+
* - client.getValidators() → GET /v1/validators
|
|
22
|
+
* - client.getEpochInfo() → GET /v1/epoch
|
|
23
|
+
* - client.getSlot() → GET /v1/slot
|
|
24
|
+
* - client.sendTransaction(tx) → POST /v1/transaction
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const fs = require('fs');
|
|
28
|
+
const path = require('path');
|
|
29
|
+
const os = require('os');
|
|
30
|
+
const readline = require('readline');
|
|
31
|
+
const nacl = require('tweetnacl');
|
|
32
|
+
const bs58 = require('bs58').default;
|
|
33
|
+
const bip39 = require('bip39');
|
|
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 CLI_VERSION = '1.0.0';
|
|
52
|
+
const DERIVATION_PATH = "m/44'/7777777'/0'/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
|
+
// Config & Paths
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
function getAetherDir() {
|
|
71
|
+
return path.join(os.homedir(), '.aether');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function getConfigPath() {
|
|
75
|
+
return path.join(getAetherDir(), 'config.json');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function loadConfig() {
|
|
79
|
+
if (!fs.existsSync(getConfigPath())) {
|
|
80
|
+
return { defaultWallet: null };
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
return JSON.parse(fs.readFileSync(getConfigPath(), 'utf8'));
|
|
84
|
+
} catch {
|
|
85
|
+
return { defaultWallet: null };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function loadWallet(address) {
|
|
90
|
+
const fp = path.join(getAetherDir(), 'wallets', `${address}.json`);
|
|
91
|
+
if (!fs.existsSync(fp)) return null;
|
|
92
|
+
try {
|
|
93
|
+
return JSON.parse(fs.readFileSync(fp, 'utf8'));
|
|
94
|
+
} catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Crypto Helpers
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
function deriveKeypair(mnemonic) {
|
|
104
|
+
if (!bip39.validateMnemonic(mnemonic)) {
|
|
105
|
+
throw new Error('Invalid mnemonic phrase');
|
|
106
|
+
}
|
|
107
|
+
const seedBuffer = bip39.mnemonicToSeedSync(mnemonic, '');
|
|
108
|
+
const seed32 = seedBuffer.slice(0, 32);
|
|
109
|
+
const keyPair = nacl.sign.keyPair.fromSeed(seed32);
|
|
110
|
+
return {
|
|
111
|
+
publicKey: Buffer.from(keyPair.publicKey),
|
|
112
|
+
secretKey: Buffer.from(keyPair.secretKey),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function formatAddress(publicKey) {
|
|
117
|
+
return 'ATH' + bs58.encode(publicKey);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function signTransaction(tx, secretKey) {
|
|
121
|
+
const txBytes = Buffer.from(JSON.stringify(tx));
|
|
122
|
+
const sig = nacl.sign.detached(txBytes, secretKey);
|
|
123
|
+
return bs58.encode(sig);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// Format Helpers
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
function formatAether(lamports) {
|
|
131
|
+
if (!lamports || lamports === '0') return '0 AETH';
|
|
132
|
+
const aeth = Number(lamports) / 1e9;
|
|
133
|
+
return aeth.toFixed(4).replace(/\.?0+$/, '') + ' AETH';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function shortAddress(addr) {
|
|
137
|
+
if (!addr || addr.length < 20) return addr || 'unknown';
|
|
138
|
+
return addr.slice(0, 8) + '...' + addr.slice(-8);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// SDK Data Fetchers (REAL RPC CALLS)
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Fetch stake accounts for a wallet using SDK
|
|
147
|
+
* REAL RPC: GET /v1/stake-accounts/<address>
|
|
148
|
+
*/
|
|
149
|
+
async function fetchStakeAccounts(rpcUrl, walletAddress) {
|
|
150
|
+
const client = createClient(rpcUrl);
|
|
151
|
+
const rawAddr = walletAddress.startsWith('ATH') ? walletAddress.slice(3) : walletAddress;
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const stakeAccounts = await client.getStakeAccounts(rawAddr);
|
|
155
|
+
if (!Array.isArray(stakeAccounts)) return [];
|
|
156
|
+
|
|
157
|
+
return stakeAccounts.map(s => ({
|
|
158
|
+
address: s.pubkey || s.publicKey || s.account || s.address,
|
|
159
|
+
validator: s.validator || s.delegate || s.vote_account,
|
|
160
|
+
lamports: s.lamports || s.stake_lamports || 0,
|
|
161
|
+
status: s.status || s.state || 'unknown',
|
|
162
|
+
activationEpoch: s.activation_epoch || s.activationEpoch,
|
|
163
|
+
deactivationEpoch: s.deactivation_epoch || s.deactivationEpoch,
|
|
164
|
+
rentExemptReserve: s.rent_exempt_reserve || 2282880,
|
|
165
|
+
})).filter(s => s.address);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Fetch validators list using SDK
|
|
173
|
+
* REAL RPC: GET /v1/validators
|
|
174
|
+
*/
|
|
175
|
+
async function fetchValidators(rpcUrl) {
|
|
176
|
+
const client = createClient(rpcUrl);
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const validators = await client.getValidators();
|
|
180
|
+
if (!Array.isArray(validators)) return [];
|
|
181
|
+
|
|
182
|
+
return validators.map(v => ({
|
|
183
|
+
address: v.vote_account || v.pubkey || v.address || v.identity,
|
|
184
|
+
identity: v.identity || v.node_pubkey,
|
|
185
|
+
stake: v.stake_lamports || v.activated_stake || 0,
|
|
186
|
+
commission: v.commission || v.commission_bps || 0,
|
|
187
|
+
apy: v.apy || v.return_rate || 0,
|
|
188
|
+
name: v.name || v.moniker || 'Unknown',
|
|
189
|
+
active: v.active !== false && v.delinquent !== true,
|
|
190
|
+
})).filter(v => v.address);
|
|
191
|
+
} catch (err) {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Fetch current epoch info using SDK
|
|
198
|
+
* REAL RPC: GET /v1/epoch
|
|
199
|
+
*/
|
|
200
|
+
async function fetchEpochInfo(rpcUrl) {
|
|
201
|
+
const client = createClient(rpcUrl);
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
const epoch = await client.getEpochInfo();
|
|
205
|
+
return {
|
|
206
|
+
epoch: epoch.epoch || 0,
|
|
207
|
+
slotIndex: epoch.slot_index || epoch.slotIndex || 0,
|
|
208
|
+
slotsInEpoch: epoch.slots_in_epoch || epoch.slotsInEpoch || 432000,
|
|
209
|
+
absoluteSlot: epoch.absolute_slot || epoch.absoluteSlot || 0,
|
|
210
|
+
blockHeight: epoch.block_height || epoch.blockHeight || 0,
|
|
211
|
+
};
|
|
212
|
+
} catch (err) {
|
|
213
|
+
return { epoch: 0, slotIndex: 0, slotsInEpoch: 432000, absoluteSlot: 0, blockHeight: 0 };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
// Delegations List Command - SDK WIRED
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
|
|
221
|
+
async function delegationsList(args) {
|
|
222
|
+
const rpc = args.rpc || getDefaultRpc();
|
|
223
|
+
const isJson = args.json || false;
|
|
224
|
+
let address = args.address || null;
|
|
225
|
+
|
|
226
|
+
// Resolve address
|
|
227
|
+
if (!address) {
|
|
228
|
+
const config = loadConfig();
|
|
229
|
+
address = config.defaultWallet;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!address) {
|
|
233
|
+
if (isJson) {
|
|
234
|
+
console.log(JSON.stringify({ error: 'No address provided and no default wallet' }, null, 2));
|
|
235
|
+
} else {
|
|
236
|
+
console.log(`\n ${C.red}✗ No wallet address specified.${C.reset}`);
|
|
237
|
+
console.log(` ${C.dim}Usage: aether delegations list --address <addr>${C.reset}\n`);
|
|
238
|
+
}
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// SDK calls
|
|
243
|
+
if (!isJson) {
|
|
244
|
+
console.log(`\n${C.bright}${C.cyan}╔═══════════════════════════════════════════════════════════════════╗${C.reset}`);
|
|
245
|
+
console.log(`${C.bright}${C.cyan}║ STAKE DELEGATIONS — ${shortAddress(address).padEnd(30)}║${C.reset}`);
|
|
246
|
+
console.log(`${C.bright}${C.cyan}╚═══════════════════════════════════════════════════════════════════╝${C.reset}\n`);
|
|
247
|
+
console.log(` ${C.dim}RPC: ${rpc}${C.reset}\n`);
|
|
248
|
+
console.log(` ${C.dim}Fetching stake accounts via SDK...${C.reset}\n`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const [stakeAccounts, epochInfo, validators] = await Promise.all([
|
|
252
|
+
fetchStakeAccounts(rpc, address),
|
|
253
|
+
fetchEpochInfo(rpc),
|
|
254
|
+
fetchValidators(rpc),
|
|
255
|
+
]);
|
|
256
|
+
|
|
257
|
+
if (stakeAccounts.length === 0) {
|
|
258
|
+
if (isJson) {
|
|
259
|
+
console.log(JSON.stringify({
|
|
260
|
+
address,
|
|
261
|
+
rpc,
|
|
262
|
+
stake_accounts: [],
|
|
263
|
+
total_delegated: '0',
|
|
264
|
+
cli_version: CLI_VERSION,
|
|
265
|
+
timestamp: new Date().toISOString(),
|
|
266
|
+
}, null, 2));
|
|
267
|
+
} else {
|
|
268
|
+
console.log(` ${C.yellow}⚠ No stake delegations found.${C.reset}`);
|
|
269
|
+
console.log(` ${C.dim}Create a delegation:${C.reset}`);
|
|
270
|
+
console.log(` ${C.cyan}aether stake --validator <addr> --amount <aeth>${C.reset}\n`);
|
|
271
|
+
}
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Calculate totals and enhance with validator info
|
|
276
|
+
let totalDelegated = BigInt(0);
|
|
277
|
+
let totalActive = BigInt(0);
|
|
278
|
+
let totalDeactivating = BigInt(0);
|
|
279
|
+
let activeCount = 0;
|
|
280
|
+
let deactivatingCount = 0;
|
|
281
|
+
let inactiveCount = 0;
|
|
282
|
+
|
|
283
|
+
const enhancedAccounts = stakeAccounts.map(s => {
|
|
284
|
+
const validator = validators.find(v => v.address === s.validator || v.identity === s.validator);
|
|
285
|
+
totalDelegated += BigInt(s.lamports);
|
|
286
|
+
|
|
287
|
+
let status = s.status;
|
|
288
|
+
if (s.deactivationEpoch && s.deactivationEpoch <= epochInfo.epoch) {
|
|
289
|
+
status = 'deactivated';
|
|
290
|
+
inactiveCount++;
|
|
291
|
+
} else if (s.deactivationEpoch) {
|
|
292
|
+
status = 'deactivating';
|
|
293
|
+
totalDeactivating += BigInt(s.lamports);
|
|
294
|
+
deactivatingCount++;
|
|
295
|
+
} else {
|
|
296
|
+
totalActive += BigInt(s.lamports);
|
|
297
|
+
activeCount++;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
...s,
|
|
302
|
+
status,
|
|
303
|
+
validatorName: validator?.name || 'Unknown',
|
|
304
|
+
validatorCommission: validator?.commission || 0,
|
|
305
|
+
validatorApy: validator?.apy || 0,
|
|
306
|
+
};
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
if (isJson) {
|
|
310
|
+
console.log(JSON.stringify({
|
|
311
|
+
address,
|
|
312
|
+
rpc,
|
|
313
|
+
epoch: epochInfo.epoch,
|
|
314
|
+
stake_accounts: enhancedAccounts.map(s => ({
|
|
315
|
+
stake_account: s.address,
|
|
316
|
+
validator: s.validator,
|
|
317
|
+
validator_name: s.validatorName,
|
|
318
|
+
delegated_lamports: s.lamports,
|
|
319
|
+
delegated_aeth: formatAether(s.lamports),
|
|
320
|
+
status: s.status,
|
|
321
|
+
activation_epoch: s.activationEpoch,
|
|
322
|
+
deactivation_epoch: s.deactivationEpoch,
|
|
323
|
+
commission: s.validatorCommission,
|
|
324
|
+
estimated_apy: s.validatorApy,
|
|
325
|
+
})),
|
|
326
|
+
summary: {
|
|
327
|
+
total_delegated_lamports: totalDelegated.toString(),
|
|
328
|
+
total_delegated_aeth: formatAether(totalDelegated),
|
|
329
|
+
total_active_lamports: totalActive.toString(),
|
|
330
|
+
total_active_aeth: formatAether(totalActive),
|
|
331
|
+
total_deactivating_lamports: totalDeactivating.toString(),
|
|
332
|
+
total_deactivating_aeth: formatAether(totalDeactivating),
|
|
333
|
+
active_accounts: activeCount,
|
|
334
|
+
deactivating_accounts: deactivatingCount,
|
|
335
|
+
inactive_accounts: inactiveCount,
|
|
336
|
+
},
|
|
337
|
+
cli_version: CLI_VERSION,
|
|
338
|
+
timestamp: new Date().toISOString(),
|
|
339
|
+
}, null, 2));
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Table header
|
|
344
|
+
console.log(` ${C.dim}┌─────────────────────────────────────────────────────────────────────────────┐${C.reset}`);
|
|
345
|
+
console.log(` ${C.dim}│${C.reset} ${C.bright}# Stake Account Validator Delegated Status${C.reset} ${C.dim}│${C.reset}`);
|
|
346
|
+
console.log(` ${C.dim}├─────────────────────────────────────────────────────────────────────────────┤${C.reset}`);
|
|
347
|
+
|
|
348
|
+
enhancedAccounts.forEach((s, i) => {
|
|
349
|
+
const num = (i + 1).toString().padStart(2);
|
|
350
|
+
const shortAcct = shortAddress(s.address).padEnd(18);
|
|
351
|
+
const shortVal = shortAddress(s.validator).padEnd(20);
|
|
352
|
+
const delegated = formatAether(s.lamports).padStart(12);
|
|
353
|
+
|
|
354
|
+
let statusColor, statusText;
|
|
355
|
+
switch (s.status) {
|
|
356
|
+
case 'active':
|
|
357
|
+
statusColor = C.green;
|
|
358
|
+
statusText = 'ACTIVE';
|
|
359
|
+
break;
|
|
360
|
+
case 'activating':
|
|
361
|
+
statusColor = C.cyan;
|
|
362
|
+
statusText = 'ACTIVATING';
|
|
363
|
+
break;
|
|
364
|
+
case 'deactivating':
|
|
365
|
+
statusColor = C.yellow;
|
|
366
|
+
statusText = 'DEACTIVATING';
|
|
367
|
+
break;
|
|
368
|
+
case 'deactivated':
|
|
369
|
+
statusColor = C.dim;
|
|
370
|
+
statusText = 'INACTIVE';
|
|
371
|
+
break;
|
|
372
|
+
default:
|
|
373
|
+
statusColor = C.reset;
|
|
374
|
+
statusText = s.status.toUpperCase();
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
console.log(` ${C.dim}│${C.reset} ${num} ${shortAcct} ${shortVal} ${delegated} ${statusColor}${statusText.padEnd(12)}${C.reset} ${C.dim}│${C.reset}`);
|
|
378
|
+
|
|
379
|
+
// Validator info line
|
|
380
|
+
const valName = s.validatorName !== 'Unknown' ? `(${s.validatorName})` : '';
|
|
381
|
+
const valInfo = valName ? ` ${C.dim}${valName}${C.reset}` : '';
|
|
382
|
+
if (valInfo) {
|
|
383
|
+
console.log(` ${C.dim}│${C.reset} ${valInfo.padEnd(75)} ${C.dim}│${C.reset}`);
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
console.log(` ${C.dim}└─────────────────────────────────────────────────────────────────────────────┘${C.reset}`);
|
|
388
|
+
console.log();
|
|
389
|
+
|
|
390
|
+
// Summary
|
|
391
|
+
console.log(` ${C.bright}Summary:${C.reset}`);
|
|
392
|
+
console.log(` ${C.dim} Total Delegated:${C.reset} ${C.bright}${formatAether(totalDelegated)}${C.reset}`);
|
|
393
|
+
console.log(` ${C.green} ● Active:${C.reset} ${formatAether(totalActive)} (${activeCount} accounts)`);
|
|
394
|
+
if (deactivatingCount > 0) {
|
|
395
|
+
console.log(` ${C.yellow} ○ Deactivating:${C.reset} ${formatAether(totalDeactivating)} (${deactivatingCount} accounts)`);
|
|
396
|
+
}
|
|
397
|
+
if (inactiveCount > 0) {
|
|
398
|
+
console.log(` ${C.dim} ● Inactive:${C.reset} ${inactiveCount} accounts ready to withdraw`);
|
|
399
|
+
}
|
|
400
|
+
console.log();
|
|
401
|
+
console.log(` ${C.dim}SDK: getStakeAccounts(), getValidators(), getEpochInfo()${C.reset}`);
|
|
402
|
+
console.log(` ${C.dim}Current Epoch: ${epochInfo.epoch} (slot ${epochInfo.slotIndex}/${epochInfo.slotsInEpoch})${C.reset}`);
|
|
403
|
+
console.log();
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// ---------------------------------------------------------------------------
|
|
407
|
+
// Delegations Info Command - SDK WIRED
|
|
408
|
+
// ---------------------------------------------------------------------------
|
|
409
|
+
|
|
410
|
+
async function delegationsInfo(args) {
|
|
411
|
+
const rpc = args.rpc || getDefaultRpc();
|
|
412
|
+
const isJson = args.json || false;
|
|
413
|
+
const stakeAccount = args.account;
|
|
414
|
+
|
|
415
|
+
if (!stakeAccount) {
|
|
416
|
+
if (isJson) {
|
|
417
|
+
console.log(JSON.stringify({ error: 'No stake account provided (--account <addr>)' }, null, 2));
|
|
418
|
+
} else {
|
|
419
|
+
console.log(`\n ${C.red}✗ Stake account required.${C.reset}`);
|
|
420
|
+
console.log(` ${C.dim}Usage: aether delegations info --account <stakeAcct>${C.reset}\n`);
|
|
421
|
+
}
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (!isJson) {
|
|
426
|
+
console.log(`\n${C.bright}${C.cyan}── Delegation Details ────────────────────────────────────────────────${C.reset}\n`);
|
|
427
|
+
console.log(` ${C.dim}Fetching details via SDK...${C.reset}\n`);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// SDK calls
|
|
431
|
+
const client = createClient(rpc);
|
|
432
|
+
const [epochInfo, validators] = await Promise.all([
|
|
433
|
+
fetchEpochInfo(rpc),
|
|
434
|
+
fetchValidators(rpc),
|
|
435
|
+
]);
|
|
436
|
+
|
|
437
|
+
// Try to fetch the specific stake account info
|
|
438
|
+
// Note: getStakePositions returns delegations for a wallet, not a specific stake account
|
|
439
|
+
// We'll search in validators list for now
|
|
440
|
+
const stakeInfo = validators.find(v => v.address === stakeAccount);
|
|
441
|
+
|
|
442
|
+
if (!stakeInfo) {
|
|
443
|
+
if (isJson) {
|
|
444
|
+
console.log(JSON.stringify({ stake_account: stakeAccount, found: false }, null, 2));
|
|
445
|
+
} else {
|
|
446
|
+
console.log(` ${C.yellow}⚠ Stake account details not found.${C.reset}`);
|
|
447
|
+
console.log(` ${C.dim}The account may not be delegated or may not exist.${C.reset}\n`);
|
|
448
|
+
}
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (isJson) {
|
|
453
|
+
console.log(JSON.stringify({
|
|
454
|
+
stake_account: stakeAccount,
|
|
455
|
+
validator: stakeInfo,
|
|
456
|
+
epoch: epochInfo,
|
|
457
|
+
cli_version: CLI_VERSION,
|
|
458
|
+
}, null, 2));
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
console.log(` ${C.green}★${C.reset} Stake Account: ${C.bright}${stakeAccount}${C.reset}`);
|
|
463
|
+
console.log(` ${C.green}★${C.reset} Validator: ${C.bright}${stakeInfo.name || 'Unknown'}${C.reset}`);
|
|
464
|
+
console.log(` ${C.green}★${C.reset} Address: ${C.bright}${stakeInfo.address}${C.reset}`);
|
|
465
|
+
console.log(` ${C.green}★${C.reset} Commission: ${C.bright}${stakeInfo.commission / 100}%${C.reset}`);
|
|
466
|
+
console.log(` ${C.green}★${C.reset} Total Stake: ${C.bright}${formatAether(stakeInfo.stake)}${C.reset}`);
|
|
467
|
+
console.log();
|
|
468
|
+
console.log(` ${C.dim}Current Epoch: ${epochInfo.epoch}${C.reset}`);
|
|
469
|
+
console.log(` ${C.dim}Epoch Progress: ${((epochInfo.slotIndex / epochInfo.slotsInEpoch) * 100).toFixed(1)}%${C.reset}`);
|
|
470
|
+
console.log();
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// ---------------------------------------------------------------------------
|
|
474
|
+
// CLI Args Parser
|
|
475
|
+
// ---------------------------------------------------------------------------
|
|
476
|
+
|
|
477
|
+
function parseArgs() {
|
|
478
|
+
const rawArgs = process.argv.slice(3);
|
|
479
|
+
const subcmd = rawArgs[0] || 'list';
|
|
480
|
+
const allArgs = rawArgs.slice(1);
|
|
481
|
+
|
|
482
|
+
const rpcIndex = allArgs.findIndex(a => a === '--rpc' || a === '-r');
|
|
483
|
+
const rpc = rpcIndex !== -1 && allArgs[rpcIndex + 1] ? allArgs[rpcIndex + 1] : getDefaultRpc();
|
|
484
|
+
|
|
485
|
+
const parsed = {
|
|
486
|
+
subcmd,
|
|
487
|
+
rpc,
|
|
488
|
+
json: allArgs.includes('--json') || allArgs.includes('-j'),
|
|
489
|
+
address: null,
|
|
490
|
+
account: null,
|
|
491
|
+
validator: null,
|
|
492
|
+
amount: null,
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
const addrIdx = allArgs.findIndex(a => a === '--address' || a === '-a');
|
|
496
|
+
if (addrIdx !== -1 && allArgs[addrIdx + 1]) parsed.address = allArgs[addrIdx + 1];
|
|
497
|
+
|
|
498
|
+
const acctIdx = allArgs.findIndex(a => a === '--account' || a === '-s');
|
|
499
|
+
if (acctIdx !== -1 && allArgs[acctIdx + 1]) parsed.account = allArgs[acctIdx + 1];
|
|
500
|
+
|
|
501
|
+
const valIdx = allArgs.findIndex(a => a === '--validator' || a === '-v');
|
|
502
|
+
if (valIdx !== -1 && allArgs[valIdx + 1]) parsed.validator = allArgs[valIdx + 1];
|
|
503
|
+
|
|
504
|
+
const amtIdx = allArgs.findIndex(a => a === '--amount' || a === '-m');
|
|
505
|
+
if (amtIdx !== -1 && allArgs[amtIdx + 1]) {
|
|
506
|
+
const val = parseFloat(allArgs[amtIdx + 1]);
|
|
507
|
+
if (!isNaN(val) && val > 0) parsed.amount = val;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return parsed;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function showHelp() {
|
|
514
|
+
console.log(`
|
|
515
|
+
${C.bright}${C.cyan}aether-cli delegations${C.reset} — View and manage stake delegations
|
|
516
|
+
|
|
517
|
+
${C.bright}USAGE${C.reset}
|
|
518
|
+
aether delegations list [--address <addr>] [--json]
|
|
519
|
+
aether delegations info --account <stakeAcct> [--json]
|
|
520
|
+
|
|
521
|
+
${C.bright}COMMANDS${C.reset}
|
|
522
|
+
list Show all stake delegations for a wallet
|
|
523
|
+
info Show detailed info about a specific delegation
|
|
524
|
+
|
|
525
|
+
${C.bright}OPTIONS${C.reset}
|
|
526
|
+
--address <addr> Wallet address (default: configured default)
|
|
527
|
+
--account <addr> Stake account address
|
|
528
|
+
--rpc <url> RPC endpoint (default: $AETHER_RPC or localhost:8899)
|
|
529
|
+
--json Output JSON for scripting
|
|
530
|
+
|
|
531
|
+
${C.bright}SDK METHODS USED${C.reset}
|
|
532
|
+
client.getStakeAccounts(address) → GET /v1/stake-accounts/<addr>
|
|
533
|
+
client.getValidators() → GET /v1/validators
|
|
534
|
+
client.getEpochInfo() → GET /v1/epoch
|
|
535
|
+
|
|
536
|
+
${C.bright}EXAMPLES${C.reset}
|
|
537
|
+
aether delegations list
|
|
538
|
+
aether delegations list --address ATHxxx... --json
|
|
539
|
+
aether delegations info --account Stakexxx... --json
|
|
540
|
+
|
|
541
|
+
${C.green}✓ Fully wired to @jellylegsai/aether-sdk${C.reset}
|
|
542
|
+
`);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// ---------------------------------------------------------------------------
|
|
546
|
+
// Main Entry Point
|
|
547
|
+
// ---------------------------------------------------------------------------
|
|
548
|
+
|
|
549
|
+
async function delegationsCommand() {
|
|
550
|
+
const args = parseArgs();
|
|
551
|
+
|
|
552
|
+
if (args.subcmd === '--help' || args.subcmd === '-h' || args.subcmd === 'help') {
|
|
553
|
+
showHelp();
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
switch (args.subcmd) {
|
|
558
|
+
case 'list':
|
|
559
|
+
await delegationsList(args);
|
|
560
|
+
break;
|
|
561
|
+
case 'info':
|
|
562
|
+
await delegationsInfo(args);
|
|
563
|
+
break;
|
|
564
|
+
default:
|
|
565
|
+
console.log(`\n ${C.red}✗ Unknown subcommand: ${args.subcmd}${C.reset}`);
|
|
566
|
+
console.log(`\n ${C.dim}Available commands:${C.reset}`);
|
|
567
|
+
console.log(` ${C.cyan}list${C.reset} - Show all stake delegations`);
|
|
568
|
+
console.log(` ${C.cyan}info${C.reset} - Show delegation details\n`);
|
|
569
|
+
showHelp();
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Export for module use
|
|
574
|
+
module.exports = { delegationsCommand };
|
|
575
|
+
|
|
576
|
+
// Run if called directly
|
|
577
|
+
if (require.main === module) {
|
|
578
|
+
delegationsCommand().catch(err => {
|
|
579
|
+
console.error(`\n${C.red}✗ Delegations command failed:${C.reset}`, err.message, '\n');
|
|
580
|
+
process.exit(1);
|
|
581
|
+
});
|
|
582
|
+
}
|