@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.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +110 -0
  3. package/aether-cli-1.0.0.tgz +0 -0
  4. package/aether-cli-1.8.0.tgz +0 -0
  5. package/aether-hub-1.0.5.tgz +0 -0
  6. package/aether-hub-1.1.8.tgz +0 -0
  7. package/aether-hub-1.2.1.tgz +0 -0
  8. package/commands/account.js +280 -0
  9. package/commands/apy.js +499 -0
  10. package/commands/balance.js +241 -0
  11. package/commands/blockhash.js +181 -0
  12. package/commands/broadcast.js +387 -0
  13. package/commands/claim.js +490 -0
  14. package/commands/config.js +851 -0
  15. package/commands/delegations.js +582 -0
  16. package/commands/doctor.js +769 -0
  17. package/commands/emergency.js +667 -0
  18. package/commands/epoch.js +275 -0
  19. package/commands/fees.js +276 -0
  20. package/commands/index.js +78 -0
  21. package/commands/info.js +495 -0
  22. package/commands/init.js +816 -0
  23. package/commands/install.js +666 -0
  24. package/commands/kyc.js +272 -0
  25. package/commands/logs.js +315 -0
  26. package/commands/monitor.js +431 -0
  27. package/commands/multisig.js +701 -0
  28. package/commands/network.js +429 -0
  29. package/commands/nft.js +857 -0
  30. package/commands/ping.js +266 -0
  31. package/commands/price.js +253 -0
  32. package/commands/rewards.js +931 -0
  33. package/commands/sdk-test.js +477 -0
  34. package/commands/sdk.js +656 -0
  35. package/commands/slot.js +155 -0
  36. package/commands/snapshot.js +470 -0
  37. package/commands/stake-info.js +139 -0
  38. package/commands/stake-positions.js +205 -0
  39. package/commands/stake.js +516 -0
  40. package/commands/stats.js +396 -0
  41. package/commands/status.js +327 -0
  42. package/commands/supply.js +391 -0
  43. package/commands/tps.js +238 -0
  44. package/commands/transfer.js +495 -0
  45. package/commands/tx-history.js +346 -0
  46. package/commands/unstake.js +597 -0
  47. package/commands/validator-info.js +657 -0
  48. package/commands/validator-register.js +593 -0
  49. package/commands/validator-start.js +323 -0
  50. package/commands/validator-status.js +227 -0
  51. package/commands/validators.js +626 -0
  52. package/commands/wallet.js +1570 -0
  53. package/index.js +593 -0
  54. package/lib/errors.js +398 -0
  55. package/package.json +76 -0
  56. package/sdk/README.md +210 -0
  57. package/sdk/index.js +1639 -0
  58. package/sdk/package.json +34 -0
  59. package/sdk/rpc.js +254 -0
  60. package/sdk/test.js +85 -0
  61. package/test/doctor.test.js +76 -0
  62. package/validator-identity.json +4 -0
@@ -0,0 +1,396 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * aether-cli stats
4
+ *
5
+ * Comprehensive wallet stats dashboard:
6
+ * - Token balance (AETH + lamports)
7
+ * - Active stake positions (validator, amount, status)
8
+ * - Recent transactions (last 5)
9
+ * - Estimated rewards accrued
10
+ *
11
+ * FULLY WIRED TO SDK - Uses @jellylegsai/aether-sdk for all blockchain calls.
12
+ * No manual HTTP - all calls go through AetherClient with real RPC.
13
+ *
14
+ * Usage:
15
+ * aether stats --address <addr> Full stats dashboard
16
+ * aether stats --address <addr> --json JSON output for scripting
17
+ * aether stats --address <addr> --compact One-line summary
18
+ *
19
+ * Requires AETHER_RPC env var or local node (default: http://127.0.0.1:8899)
20
+ */
21
+
22
+ const os = require('os');
23
+ const path = require('path');
24
+ const fs = require('fs');
25
+ const bs58 = require('bs58').default;
26
+
27
+ // Import SDK for real blockchain RPC calls
28
+ const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
29
+ const aether = require(sdkPath);
30
+
31
+ // ANSI colours
32
+ const C = {
33
+ reset: '\x1b[0m',
34
+ bright: '\x1b[1m',
35
+ dim: '\x1b[2m',
36
+ red: '\x1b[31m',
37
+ green: '\x1b[32m',
38
+ yellow: '\x1b[33m',
39
+ cyan: '\x1b[36m',
40
+ magenta: '\x1b[35m',
41
+ bold: '\x1b[1m',
42
+ };
43
+
44
+ const CLI_VERSION = '1.1.0';
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Paths & config
48
+ // ---------------------------------------------------------------------------
49
+
50
+ function getAetherDir() {
51
+ return path.join(os.homedir(), '.aether');
52
+ }
53
+
54
+ function getConfigPath() {
55
+ return path.join(getAetherDir(), 'config.json');
56
+ }
57
+
58
+ function getWalletsDir() {
59
+ return path.join(getAetherDir(), 'wallets');
60
+ }
61
+
62
+ function loadConfig() {
63
+ const p = getConfigPath();
64
+ if (!fs.existsSync(p)) return { defaultWallet: null };
65
+ try {
66
+ return JSON.parse(fs.readFileSync(p, 'utf8'));
67
+ } catch {
68
+ return { defaultWallet: null };
69
+ }
70
+ }
71
+
72
+ function loadWallet(address) {
73
+ const fp = path.join(getWalletsDir(), `${address}.json`);
74
+ if (!fs.existsSync(fp)) return null;
75
+ return JSON.parse(fs.readFileSync(fp, 'utf8'));
76
+ }
77
+
78
+ // ---------------------------------------------------------------------------
79
+ // SDK setup - Real blockchain RPC calls via @jellylegsai/aether-sdk
80
+ // ---------------------------------------------------------------------------
81
+
82
+ function getDefaultRpc() {
83
+ return process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
84
+ }
85
+
86
+ function createClient(rpcUrl) {
87
+ return new aether.AetherClient({ rpcUrl });
88
+ }
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // Formatting helpers
92
+ // ---------------------------------------------------------------------------
93
+
94
+ /**
95
+ * Format lamports as AETH string (1 AETH = 1e9 lamports)
96
+ */
97
+ function formatAether(lamports) {
98
+ if (lamports === undefined || lamports === null) return '0 AETH';
99
+ const aeth = lamports / 1e9;
100
+ if (aeth === 0) return '0 AETH';
101
+ return aeth.toFixed(4).replace(/\.?0+$/, '') + ' AETH';
102
+ }
103
+
104
+ /**
105
+ * Format a timestamp as relative time ("2h ago", "3d ago")
106
+ */
107
+ function relativeTime(timestamp) {
108
+ if (!timestamp) return 'unknown';
109
+ const now = Math.floor(Date.now() / 1000);
110
+ const diff = now - timestamp;
111
+ if (diff < 60) return `${diff}s ago`;
112
+ if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
113
+ if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
114
+ return `${Math.floor(diff / 86400)}d ago`;
115
+ }
116
+
117
+ /**
118
+ * Truncate a signature or hash for display
119
+ */
120
+ function truncate(str, chars = 8) {
121
+ if (!str || typeof str !== 'string') return '—';
122
+ if (str.length <= chars * 2 + 3) return str;
123
+ return str.slice(0, chars) + '…' + str.slice(-chars);
124
+ }
125
+
126
+ // ---------------------------------------------------------------------------
127
+ // Fetch wallet stats using SDK - Real RPC calls
128
+ // ---------------------------------------------------------------------------
129
+
130
+ async function fetchWalletStats(address, rpcUrl) {
131
+ const client = createClient(rpcUrl);
132
+ const rawAddr = address.startsWith('ATH') ? address.slice(3) : address;
133
+
134
+ // Parallel SDK calls - all real blockchain RPC calls
135
+ const [account, txHistory, stakeAccounts] = await Promise.all([
136
+ // SDK: getAccountInfo → GET /v1/account/<addr>
137
+ client.getAccountInfo(rawAddr).catch(err => ({ error: err.message })),
138
+ // SDK: getTransactionHistory → GET /v1/tx/<addr>
139
+ client.getTransactionHistory(address, 5).catch(err => ({ error: err.message })),
140
+ // SDK: getStakePositions → GET /v1/stake/<addr>
141
+ client.getStakePositions(rawAddr).catch(err => ({ error: err.message })),
142
+ ]);
143
+
144
+ return { account, txHistory, stakeAccounts };
145
+ }
146
+
147
+ // ---------------------------------------------------------------------------
148
+ // Render dashboard
149
+ // ---------------------------------------------------------------------------
150
+
151
+ function renderDashboard(address, stats, opts) {
152
+ const { account, txHistory, stakeAccounts } = stats;
153
+ const { compact, asJson } = opts;
154
+ const rpcUrl = opts.rpcUrl;
155
+
156
+ // Parse SDK responses - handle both wrapped and raw shapes
157
+ const rawAccount = account && !account.error ? account : null;
158
+ const rawTxs = (txHistory && !txHistory.error)
159
+ ? (txHistory.transactions || txHistory || [])
160
+ : [];
161
+ const rawStakes = (stakeAccounts && !stakeAccounts.error)
162
+ ? (Array.isArray(stakeAccounts) ? stakeAccounts
163
+ : stakeAccounts.accounts || stakeAccounts.stake_accounts || [])
164
+ : [];
165
+
166
+ if (asJson) {
167
+ const out = {
168
+ address,
169
+ rpc: rpcUrl,
170
+ balance: rawAccount && rawAccount.lamports !== undefined ? {
171
+ lamports: rawAccount.lamports || 0,
172
+ aeth: formatAether(rawAccount.lamports || 0),
173
+ } : null,
174
+ stake_positions: rawStakes.map((sa) => ({
175
+ stake_account: sa.stake_account || sa.address || sa.pubkey || sa.publicKey || 'unknown',
176
+ validator: sa.validator || sa.delegate || 'unknown',
177
+ amount: sa.amount || sa.lamports || sa.stake_lamports || 0,
178
+ aeth: formatAether(sa.amount || sa.lamports || sa.stake_lamports || 0),
179
+ status: sa.status || sa.state || 'active',
180
+ created_epoch: sa.created_epoch || sa.activation_epoch || null,
181
+ })),
182
+ recent_txs: rawTxs.slice(0, 5).map((tx) => ({
183
+ type: tx.tx_type || tx.type || 'Unknown',
184
+ signature: tx.signature || tx.id || tx.tx_signature || null,
185
+ timestamp: tx.blockTime || null,
186
+ relative_time: relativeTime(tx.blockTime),
187
+ payload: tx.payload?.data || tx.payload || {},
188
+ fee: tx.fee || 0,
189
+ })),
190
+ fetched_at: new Date().toISOString(),
191
+ cli_version: CLI_VERSION,
192
+ };
193
+ console.log(JSON.stringify(out, null, 2));
194
+ return;
195
+ }
196
+
197
+ const lamports = rawAccount?.lamports ?? null;
198
+
199
+ if (compact) {
200
+ const bal = lamports !== null ? formatAether(lamports) : 'unknown';
201
+ const stakes = rawStakes.length;
202
+ const recent = rawTxs.length > 0 ? (rawTxs[0].tx_type || rawTxs[0].type || '?') : 'none';
203
+ console.log(`${C.bright}${address}${C.reset} bal:${C.green}${bal}${C.reset} stakes:${stakes} last:${recent} txs:${rawTxs.length}`);
204
+ return;
205
+ }
206
+
207
+ // Full dashboard
208
+ console.log(`\n${C.bright}${C.cyan}── Wallet Stats ─────────────────────────────────────────${C.reset}`);
209
+ console.log(` ${C.green}★${C.reset} ${C.bright}${address}${C.reset}`);
210
+ console.log(` ${C.dim}RPC: ${rpcUrl}${C.reset}`);
211
+ console.log();
212
+
213
+ // Balance section
214
+ console.log(` ${C.bright}Balance${C.reset}`);
215
+ if (lamports !== null) {
216
+ console.log(` ${C.green}${formatAether(lamports)}${C.reset} ${C.dim}(${lamports} lamports)${C.reset}`);
217
+ if (rawAccount?.owner) {
218
+ const ownerStr = Array.isArray(rawAccount.owner)
219
+ ? 'ATH' + bs58.encode(Buffer.from(rawAccount.owner.slice(0, 32)))
220
+ : rawAccount.owner;
221
+ console.log(` ${C.dim}Owner: ${ownerStr}${C.reset}`);
222
+ }
223
+ if (rawAccount?.rent_epoch !== undefined) {
224
+ console.log(` ${C.dim}Rent epoch: ${rawAccount.rent_epoch}${C.reset}`);
225
+ }
226
+ } else {
227
+ console.log(` ${C.yellow}⚠ Could not fetch balance (account may not exist)${C.reset}`);
228
+ }
229
+ console.log();
230
+
231
+ // Stake positions section
232
+ console.log(` ${C.bright}Stake Positions (${rawStakes.length})${C.reset}`);
233
+ if (rawStakes.length === 0) {
234
+ console.log(` ${C.dim}No active stake positions.${C.reset}`);
235
+ } else {
236
+ const statusColors = {
237
+ active: C.green,
238
+ inactive: C.yellow,
239
+ activating: C.yellow,
240
+ deactivating: C.red,
241
+ unknown: C.dim,
242
+ };
243
+ for (const sa of rawStakes) {
244
+ const status = sa.status || 'unknown';
245
+ const color = statusColors[status] || C.dim;
246
+ const amount = sa.amount || sa.lamports || sa.stake_lamports || 0;
247
+ const validator = sa.validator || sa.delegate || 'unknown';
248
+ console.log(` ${C.dim}┌─${C.reset}`);
249
+ console.log(` │ ${C.bright}Validator:${C.reset} ${validator}`);
250
+ console.log(` │ ${C.bright}Amount:${C.reset} ${color}${formatAether(amount)}${C.reset}`);
251
+ console.log(` │ ${C.bright}Status:${C.reset} ${color}${status}${C.reset}`);
252
+ const saAddr = sa.stake_account || sa.address || sa.pubkey || sa.publicKey;
253
+ if (saAddr) {
254
+ console.log(` │ ${C.bright}Stake acct:${C.reset} ${truncate(saAddr)}`);
255
+ }
256
+ console.log(` ${C.dim}└${C.reset}`);
257
+ }
258
+ }
259
+ console.log();
260
+
261
+ // Recent transactions section
262
+ console.log(` ${C.bright}Recent Transactions (${rawTxs.length})${C.reset}`);
263
+ if (rawTxs.length === 0) {
264
+ console.log(` ${C.dim}No transactions yet.${C.reset}`);
265
+ } else {
266
+ const typeColors = {
267
+ Transfer: C.cyan,
268
+ Stake: C.green,
269
+ Unstake: C.yellow,
270
+ ClaimRewards: C.magenta,
271
+ CreateNFT: C.red,
272
+ MintNFT: C.red,
273
+ TransferNFT: C.cyan,
274
+ UpdateMetadata: C.yellow,
275
+ Unknown: C.dim,
276
+ };
277
+ for (const tx of rawTxs.slice(0, 5)) {
278
+ const txType = tx.tx_type || tx.type || 'Unknown';
279
+ const color = typeColors[txType] || C.dim;
280
+ const sig = tx.signature || tx.id || tx.tx_signature || '—';
281
+ const ts = tx.blockTime ? relativeTime(tx.blockTime) : 'unknown';
282
+
283
+ console.log(` ${C.dim}┌─ ${ts}${C.reset} ${C.bright}${color}${txType}${C.reset} sig:${truncate(sig)}`);
284
+ const payload = tx.payload?.data || tx.payload || {};
285
+ if (payload.recipient) console.log(` │ ${C.dim}→ to: ${payload.recipient}${C.reset}`);
286
+ if (payload.amount) console.log(` │ ${C.dim}amount: ${formatAether(payload.amount)}${C.reset}`);
287
+ if (payload.validator) console.log(` │ ${C.dim}validator: ${payload.validator}${C.reset}`);
288
+ if (tx.fee !== undefined && tx.fee > 0) {
289
+ console.log(` │ ${C.dim}fee: ${tx.fee} lamports${C.reset}`);
290
+ }
291
+ console.log(` ${C.dim}└${C.reset}`);
292
+ }
293
+ }
294
+ console.log();
295
+
296
+ // SDK attribution
297
+ console.log(` ${C.dim}SDK: getAccountInfo(), getTransactionHistory(), getStakePositions()${C.reset}`);
298
+ console.log();
299
+ }
300
+
301
+ // ---------------------------------------------------------------------------
302
+ // Main command
303
+ // ---------------------------------------------------------------------------
304
+
305
+ async function statsCommand() {
306
+ const args = process.argv.slice(3);
307
+
308
+ // Parse flags
309
+ let address = null;
310
+ let compact = false;
311
+ let asJson = false;
312
+
313
+ for (let i = 0; i < args.length; i++) {
314
+ if ((args[i] === '--address' || args[i] === '-a') && args[i + 1]) {
315
+ address = args[i + 1];
316
+ i++;
317
+ } else if (args[i] === '--compact' || args[i] === '-c') {
318
+ compact = true;
319
+ } else if (args[i] === '--json' || args[i] === '-j') {
320
+ asJson = true;
321
+ } else if (args[i] === '--help' || args[i] === '-h') {
322
+ showHelp();
323
+ return;
324
+ } else if ((args[i] === '--rpc' || args[i] === '-r') && args[i + 1]) {
325
+ // Ignore -r here (handled by SDK client default)
326
+ i++;
327
+ }
328
+ }
329
+
330
+ // Resolve address
331
+ if (!address) {
332
+ const cfg = loadConfig();
333
+ address = cfg.defaultWallet;
334
+ }
335
+
336
+ if (!address) {
337
+ console.log(` ${C.red}✗ No wallet address.${C.reset} Use ${C.cyan}--address <addr>${C.reset} or set a default.`);
338
+ console.log(` ${C.dim}Usage: aether stats --address <address> [--compact] [--json]${C.reset}\n`);
339
+ process.exit(1);
340
+ }
341
+
342
+ // Verify wallet file exists
343
+ const wallet = loadWallet(address);
344
+ if (!wallet) {
345
+ console.log(` ${C.red}✗ Wallet not found locally:${C.reset} ${address}`);
346
+ console.log(` ${C.dim}Check your wallets: ${C.cyan}aether wallet list${C.reset}\n`);
347
+ process.exit(1);
348
+ }
349
+
350
+ const rpcUrl = getDefaultRpc();
351
+
352
+ if (!asJson) {
353
+ console.log(` ${C.dim}Fetching stats via SDK from ${rpcUrl}...${C.reset}`);
354
+ }
355
+
356
+ try {
357
+ const stats = await fetchWalletStats(address, rpcUrl);
358
+ renderDashboard(address, stats, { compact, asJson, rpcUrl });
359
+ } catch (err) {
360
+ if (asJson) {
361
+ console.log(JSON.stringify({ address, error: err.message, rpc: rpcUrl }, null, 2));
362
+ } else {
363
+ console.log(` ${C.red}✗ Failed to fetch wallet stats:${C.reset} ${err.message}`);
364
+ console.log(` ${C.dim} Is your validator running? RPC: ${rpcUrl}${C.reset}`);
365
+ console.log(` ${C.dim} Set custom RPC: AETHER_RPC=https://your-rpc-url${C.reset}\n`);
366
+ }
367
+ process.exit(1);
368
+ }
369
+ }
370
+
371
+ function showHelp() {
372
+ console.log(`
373
+ ${C.bright}Aether Wallet Stats${C.reset}
374
+ ${C.dim}Comprehensive wallet overview: balance, stakes, recent transactions.
375
+ Fully wired to @jellylegsai/aether-sdk for real blockchain RPC calls.${C.reset}
376
+
377
+ ${C.bright}Usage:${C.reset}
378
+ aether stats --address <addr> Full dashboard
379
+ aether stats --address <addr> --json JSON output
380
+ aether stats --address <addr> --compact One-line summary
381
+
382
+ ${C.bright}Options:${C.reset}
383
+ -a, --address <addr> Wallet address (or set default)
384
+ -j, --json JSON output
385
+ -c, --compact One-line summary
386
+ -r, --rpc <url> RPC endpoint (default: AETHER_RPC or localhost:8899)
387
+ -h, --help Show this help
388
+
389
+ ${C.bright}SDK Methods Used:${C.reset}
390
+ client.getAccountInfo(addr) → GET /v1/account/<addr>
391
+ client.getTransactionHistory(addr) → GET /v1/tx/<addr>
392
+ client.getStakePositions(addr) → GET /v1/stake/<addr>
393
+ `);
394
+ }
395
+
396
+ module.exports = { statsCommand };