@jellylegsai/aether-cli 1.9.0 → 1.9.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/index.js CHANGED
@@ -1,78 +1,86 @@
1
- // Export all command modules
2
- const doctorCommand = require('./doctor');
3
- const initCommand = require('./init');
4
- const validatorStartCommand = require('./validator-start');
5
- const validatorStatusCommand = require('./validator-status');
6
- const validatorInfoCommand = require('./validator-info');
7
- const validatorRegisterCommand = require('./validator-register');
8
- const delegationsCommand = require('./delegations');
9
- const rewardsCommand = require('./rewards');
10
- const walletCommand = require('./wallet');
11
- const balanceCommand = require('./balance');
12
- const transferCommand = require('./transfer');
13
- const txHistoryCommand = require('./tx-history');
14
- const multisigCommand = require('./multisig');
15
- const claimCommand = require('./claim');
16
- const unstakeCommand = require('./unstake');
17
- const stakeCommand = require('./stake');
18
- const stakePositionsCommand = require('./stake-positions');
19
- const networkCommand = require('./network');
20
- const monitorLoop = require('./monitor');
21
- const logsCommand = require('./logs');
22
- const sdkCommand = require('./sdk');
23
- const sdkTestCommand = require('./sdk-test');
24
- const configCommand = require('./config');
25
- const epochCommand = require('./epoch');
26
- const supplyCommand = require('./supply');
27
- const statusCommand = require('./status');
28
- const validatorsListCommand = require('./validators');
29
- const blockhashCommand = require('./blockhash');
30
- const tpsCommand = require('./tps');
31
- const feesCommand = require('./fees');
32
- const apyCommand = require('./apy');
33
- const broadcastCommand = require('./broadcast');
34
- const accountCommand = require('./account');
35
- const priceCommand = require('./price');
36
- const emergencyCommand = require('./emergency');
37
- const snapshotCommand = require('./snapshot');
38
- const nftCommand = require('./nft');
39
-
40
- module.exports = {
41
- doctorCommand,
42
- initCommand,
43
- validatorStartCommand,
44
- validatorStatusCommand,
45
- validatorInfoCommand,
46
- validatorRegisterCommand,
47
- delegationsCommand,
48
- rewardsCommand,
49
- walletCommand,
50
- balanceCommand,
51
- transferCommand,
52
- txHistoryCommand,
53
- multisigCommand,
54
- claimCommand,
55
- unstakeCommand,
56
- stakeCommand,
57
- stakePositionsCommand,
58
- networkCommand,
59
- monitorLoop,
60
- logsCommand,
61
- sdkCommand,
62
- sdkTestCommand,
63
- configCommand,
64
- epochCommand,
65
- supplyCommand,
66
- statusCommand,
67
- validatorsListCommand,
68
- blockhashCommand,
69
- tpsCommand,
70
- feesCommand,
71
- apyCommand,
72
- broadcastCommand,
73
- accountCommand,
74
- priceCommand,
75
- emergencyCommand,
76
- snapshotCommand,
77
- nftCommand,
78
- };
1
+ // Export all command modules
2
+ const doctorCommand = require('./doctor');
3
+ const initCommand = require('./init');
4
+ const validatorStartCommand = require('./validator-start');
5
+ const validatorStatusCommand = require('./validator-status');
6
+ const validatorInfoCommand = require('./validator-info');
7
+ const validatorRegisterCommand = require('./validator-register');
8
+ const delegationsCommand = require('./delegations');
9
+ const rewardsCommand = require('./rewards');
10
+ const walletCommand = require('./wallet');
11
+ const balanceCommand = require('./balance');
12
+ const transferCommand = require('./transfer');
13
+ const txHistoryCommand = require('./tx-history');
14
+ const txCommand = require('./tx');
15
+ const multisigCommand = require('./multisig');
16
+ const claimCommand = require('./claim');
17
+ const unstakeCommand = require('./unstake');
18
+ const stakeCommand = require('./stake');
19
+ const stakePositionsCommand = require('./stake-positions');
20
+ const networkCommand = require('./network');
21
+ const monitorLoop = require('./monitor');
22
+ const logsCommand = require('./logs');
23
+ const sdkCommand = require('./sdk');
24
+ const sdkTestCommand = require('./sdk-test');
25
+ const configCommand = require('./config');
26
+ const epochCommand = require('./epoch');
27
+ const supplyCommand = require('./supply');
28
+ const statusCommand = require('./status');
29
+ const validatorsListCommand = require('./validators');
30
+ const blockhashCommand = require('./blockhash');
31
+ const tpsCommand = require('./tps');
32
+ const feesCommand = require('./fees');
33
+ const apyCommand = require('./apy');
34
+ const broadcastCommand = require('./broadcast');
35
+ const accountCommand = require('./account');
36
+ const priceCommand = require('./price');
37
+ const emergencyCommand = require('./emergency');
38
+ const snapshotCommand = require('./snapshot');
39
+ const nftCommand = require('./nft');
40
+ const pingCommand = require('./ping');
41
+ const slotCommand = require('./slot');
42
+ const stakeInfoCommand = require('./stake-info');
43
+
44
+ module.exports = {
45
+ doctorCommand,
46
+ initCommand,
47
+ validatorStartCommand,
48
+ validatorStatusCommand,
49
+ validatorInfoCommand,
50
+ validatorRegisterCommand,
51
+ delegationsCommand,
52
+ rewardsCommand,
53
+ walletCommand,
54
+ balanceCommand,
55
+ transferCommand,
56
+ txHistoryCommand,
57
+ txCommand,
58
+ multisigCommand,
59
+ claimCommand,
60
+ unstakeCommand,
61
+ stakeCommand,
62
+ stakePositionsCommand,
63
+ stakeInfoCommand,
64
+ networkCommand,
65
+ monitorLoop,
66
+ logsCommand,
67
+ sdkCommand,
68
+ sdkTestCommand,
69
+ configCommand,
70
+ epochCommand,
71
+ supplyCommand,
72
+ statusCommand,
73
+ validatorsListCommand,
74
+ blockhashCommand,
75
+ tpsCommand,
76
+ feesCommand,
77
+ apyCommand,
78
+ broadcastCommand,
79
+ accountCommand,
80
+ priceCommand,
81
+ emergencyCommand,
82
+ snapshotCommand,
83
+ nftCommand,
84
+ pingCommand,
85
+ slotCommand,
86
+ };
package/commands/tx.js ADDED
@@ -0,0 +1,487 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * aether-cli tx
4
+ *
5
+ * Look up a specific transaction by its signature and show confirmation status,
6
+ * raw JSON data, fee paid, slot, block time, and logs.
7
+ * This is the go-to command after submitting any transaction to check if it landed.
8
+ *
9
+ * FULLY WIRED TO SDK — Uses @jellylegsai/aether-sdk for real HTTP RPC calls.
10
+ * No stubs, no mocks. Every call hits the actual blockchain RPC endpoint.
11
+ *
12
+ * Usage:
13
+ * aether tx <signature> Show human-readable transaction details
14
+ * aether tx <signature> --json Raw JSON output for scripting
15
+ * aether tx <signature> --rpc <url> Query a specific RPC endpoint
16
+ * aether tx <signature> --wait Poll until confirmed (max 60s)
17
+ * aether tx <signature> --wait 120 Poll with custom timeout (seconds)
18
+ * aether tx <signature> --logs Show parsed instruction logs if available
19
+ *
20
+ * SDK Methods Used:
21
+ * - client.getTransaction(sig) → GET /v1/transaction/<signature>
22
+ * - client.getSlot() → GET /v1/slot
23
+ * - client.getBlockHeight() → GET /v1/blockheight
24
+ *
25
+ * Examples:
26
+ * aether tx 5abc123...def # Look up a transaction
27
+ * aether tx 5abc123...def --json # JSON for scripting
28
+ * aether tx 5abc123...def --wait # Wait up to 60s for confirmation
29
+ * aether tx 5abc123...def --logs # Show transaction logs
30
+ *
31
+ * Note: Transactions are final once confirmed. Use --wait when submitting new
32
+ * transactions to get immediate confirmation without a separate poll step.
33
+ */
34
+
35
+ const path = require('path');
36
+
37
+ // Import SDK — REAL HTTP RPC calls to the blockchain
38
+ const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
39
+ const aether = require(sdkPath);
40
+
41
+ // ANSI colours
42
+ const C = {
43
+ reset: '\x1b[0m',
44
+ bright: '\x1b[1m',
45
+ dim: '\x1b[2m',
46
+ red: '\x1b[31m',
47
+ green: '\x1b[32m',
48
+ yellow: '\x1b[33m',
49
+ cyan: '\x1b[36m',
50
+ magenta: '\x1b[35m',
51
+ };
52
+
53
+ const CLI_VERSION = '1.0.0';
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // SDK Client Setup
57
+ // ---------------------------------------------------------------------------
58
+
59
+ function getDefaultRpc() {
60
+ return process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
61
+ }
62
+
63
+ function createClient(rpcUrl) {
64
+ return new aether.AetherClient({ rpcUrl });
65
+ }
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // Argument Parsing
69
+ // ---------------------------------------------------------------------------
70
+
71
+ function parseArgs() {
72
+ const args = process.argv.slice(2);
73
+ const opts = {
74
+ signature: null,
75
+ rpc: getDefaultRpc(),
76
+ asJson: false,
77
+ showLogs: false,
78
+ wait: false,
79
+ waitTimeoutS: 60,
80
+ };
81
+
82
+ for (let i = 0; i < args.length; i++) {
83
+ const arg = args[i];
84
+ if (!arg.startsWith('-') && !opts.signature) {
85
+ // First non-flag is the signature
86
+ opts.signature = arg;
87
+ } else if (arg === '--rpc' || arg === '-r') {
88
+ opts.rpc = args[++i];
89
+ } else if (arg === '--json' || arg === '-j') {
90
+ opts.asJson = true;
91
+ } else if (arg === '--logs' || arg === '-l') {
92
+ opts.showLogs = true;
93
+ } else if (arg === '--wait' || arg === '-w') {
94
+ opts.wait = true;
95
+ // Check for custom timeout: --wait 30
96
+ const next = args[i + 1];
97
+ if (next && /^\d+$/.test(next)) {
98
+ opts.waitTimeoutS = parseInt(next, 10);
99
+ i++;
100
+ }
101
+ } else if (arg === '--help' || arg === '-h') {
102
+ opts.help = true;
103
+ }
104
+ }
105
+
106
+ return opts;
107
+ }
108
+
109
+ function showHelp() {
110
+ console.log(`
111
+ ${C.bright}${C.cyan}aether-cli tx${C.reset} — Look up a transaction by signature
112
+
113
+ ${C.bright}USAGE${C.reset}
114
+ aether tx <signature> [options]
115
+
116
+ ${C.bright}ARGUMENTS${C.reset}
117
+ <signature> Transaction signature (base58, required)
118
+
119
+ ${C.bright}OPTIONS${C.reset}
120
+ --rpc <url> RPC endpoint (default: AETHER_RPC or localhost:8899)
121
+ --json, -j Output raw JSON for scripting
122
+ --logs, -l Show parsed transaction logs
123
+ --wait, -w [s] Poll until confirmed (default: 60s, max: 300s)
124
+ --help, -h Show this help
125
+
126
+ ${C.bright}SDK METHODS${C.reset}
127
+ client.getTransaction(sig) → GET /v1/transaction/<signature>
128
+ client.getSlot() → GET /v1/slot
129
+ client.getBlockHeight() → GET /v1/blockheight
130
+
131
+ ${C.bright}DESCRIPTION${C.reset}
132
+ Queries the Aether blockchain for a specific transaction by its
133
+ base58-encoded signature. Returns:
134
+ • Confirmation status (confirmed / pending / not found)
135
+ • Slot number (when the transaction landed)
136
+ • Block time (unix timestamp)
137
+ • Fee paid
138
+ • Transaction type and data
139
+ • Error message if failed
140
+ • Program logs if available
141
+
142
+ This is the primary command to verify a submitted transaction
143
+ has been accepted by the network.
144
+
145
+ ${C.bright}EXAMPLES${C.reset}
146
+ aether tx 5abc123def... # Human-readable lookup
147
+ aether tx 5abc123def... --json # JSON output
148
+ aether tx 5abc123def... --wait # Poll until confirmed (max 60s)
149
+ aether tx 5abc123def... --logs # Show program logs
150
+ aether tx 5abc123def... --wait 120 # Poll with 120s timeout
151
+ aether tx 5abc123def... --rpc https://my-node:8899
152
+
153
+ ${C.green}✓ Fully wired to @jellylegsai/aether-sdk${C.reset}
154
+ `);
155
+ }
156
+
157
+ // ---------------------------------------------------------------------------
158
+ // Helpers
159
+ // ---------------------------------------------------------------------------
160
+
161
+ function shortSig(sig) {
162
+ if (!sig || sig.length < 16) return sig || 'unknown';
163
+ return sig.slice(0, 8) + '...' + sig.slice(-8);
164
+ }
165
+
166
+ function formatTime(unixTs) {
167
+ if (!unixTs) return '—';
168
+ try {
169
+ return new Date(unixTs * 1000).toISOString().replace('T', ' ').substring(0, 19) + ' UTC';
170
+ } catch {
171
+ return String(unixTs);
172
+ }
173
+ }
174
+
175
+ function formatAether(lamports) {
176
+ if (lamports === undefined || lamports === null) return 'N/A';
177
+ const aeth = Number(lamports) / 1e9;
178
+ if (aeth === 0) return '0 AETH';
179
+ return aeth.toFixed(6).replace(/\.?0+$/, '') + ' AETH';
180
+ }
181
+
182
+ function statusColorAndLabel(tx) {
183
+ if (!tx) return { color: C.red, label: 'NOT FOUND' };
184
+ if (tx.confirmed !== false && (tx.slot !== undefined || tx.blockTime !== undefined)) {
185
+ return { color: C.green, label: 'CONFIRMED' };
186
+ }
187
+ if (tx.pending || tx.confirmations === null) {
188
+ return { color: C.yellow, label: 'PENDING' };
189
+ }
190
+ if (tx.error || tx.err) {
191
+ return { color: C.red, label: 'FAILED' };
192
+ }
193
+ return { color: C.yellow, label: 'UNKNOWN' };
194
+ }
195
+
196
+ // ---------------------------------------------------------------------------
197
+ // Transaction Fetch — REAL RPC call via SDK
198
+ // ---------------------------------------------------------------------------
199
+
200
+ async function fetchTransaction(rpcUrl, signature) {
201
+ const client = createClient(rpcUrl);
202
+
203
+ // Real RPC call: GET /v1/transaction/<signature>
204
+ const tx = await client.getTransaction(signature);
205
+
206
+ // Also get current slot for context
207
+ let currentSlot = null;
208
+ try {
209
+ currentSlot = await client.getSlot();
210
+ } catch {
211
+ // Non-critical — don't fail the whole lookup
212
+ }
213
+
214
+ return { tx, currentSlot };
215
+ }
216
+
217
+ // ---------------------------------------------------------------------------
218
+ // Wait for Confirmation — polls getTransaction until confirmed or timeout
219
+ // ---------------------------------------------------------------------------
220
+
221
+ async function waitForConfirmation(rpcUrl, signature, timeoutS = 60) {
222
+ const client = createClient(rpcUrl);
223
+ const start = Date.now();
224
+ const deadline = start + timeoutS * 1000;
225
+ const pollIntervalMs = 2000;
226
+
227
+ process.stdout.write(` ${C.dim}Polling every ${pollIntervalMs / 1000}s (max ${timeoutS}s)...${C.reset}\n`);
228
+
229
+ while (Date.now() < deadline) {
230
+ try {
231
+ const tx = await client.getTransaction(signature);
232
+
233
+ if (tx && (tx.blockTime !== undefined || tx.slot !== undefined)) {
234
+ const elapsed = ((Date.now() - start) / 1000).toFixed(1);
235
+ return { confirmed: true, tx, waitedS: parseFloat(elapsed) };
236
+ }
237
+ } catch {
238
+ // Not found yet — keep polling
239
+ }
240
+
241
+ // Progress indicator
242
+ const elapsed = ((Date.now() - start) / 1000).toFixed(0);
243
+ process.stdout.write(`\r ${C.dim}[${elapsed}s] waiting for confirmation... ${C.reset}`);
244
+
245
+ await new Promise(r => setTimeout(r, pollIntervalMs));
246
+ }
247
+
248
+ process.stdout.write('\r' + ' '.repeat(70) + '\r');
249
+ return { confirmed: false, tx: null, waitedS: timeoutS };
250
+ }
251
+
252
+ // ---------------------------------------------------------------------------
253
+ // Output Formatters
254
+ // ---------------------------------------------------------------------------
255
+
256
+ function printTxHuman(tx, signature, rpcUrl, currentSlot) {
257
+ const status = statusColorAndLabel(tx);
258
+
259
+ console.log(`\n${C.bright}${C.cyan}╔════════════════════════════════════════════════════════════════════╗${C.reset}`);
260
+ console.log(`${C.bright}${C.cyan}║${C.reset} ${C.bright}AETHER TRANSACTION${C.reset} ${C.bright}║${C.reset}`);
261
+ console.log(`${C.bright}${C.cyan}╚════════════════════════════════════════════════════════════════════╝${C.reset}`);
262
+
263
+ // Status banner
264
+ const statusBg = status.color === C.green ? C.green : status.color === C.red ? C.red : C.yellow;
265
+ console.log(`\n ${C.bright}┌────────────────────────────────────────────────────────────────────┐${C.reset}`);
266
+ console.log(` ${C.bright}│${C.reset} ${C.bright}Status:${C.reset} ${status.color}${C.bright}${status.label.padEnd(52)}${C.reset} ${C.bright}│${C.reset}`);
267
+ console.log(` ${C.bright}└────────────────────────────────────────────────────────────────────┘${C.reset}`);
268
+
269
+ // Signature
270
+ console.log(`\n ${C.bright}Signature:${C.reset} ${C.magenta}${signature}${C.reset}`);
271
+
272
+ // Transaction details table
273
+ console.log(`\n ${C.bright}┌─────────────────────┬──────────────────────────────────────────────┐${C.reset}`);
274
+
275
+ if (tx) {
276
+ // Slot
277
+ const slotStr = tx.slot !== undefined ? tx.slot.toLocaleString() : '—';
278
+ console.log(` ${C.bright}│${C.reset} ${C.cyan}Slot${C.reset} ${C.bright}│${C.reset} ${slotStr.padEnd(44)} ${C.bright}│${C.reset}`);
279
+
280
+ // Block time
281
+ const timeStr = formatTime(tx.blockTime);
282
+ console.log(` ${C.bright}│${C.reset} ${C.cyan}Block Time${C.reset} ${C.bright}│${C.reset} ${timeStr.padEnd(44)} ${C.bright}│${C.reset}`);
283
+
284
+ // Fee
285
+ const feeStr = tx.fee !== undefined ? formatAether(tx.fee) : '—';
286
+ console.log(` ${C.bright}│${C.reset} ${C.cyan}Fee${C.reset} ${C.bright}│${C.reset} ${feeStr.padEnd(44)} ${C.bright}│${C.reset}`);
287
+
288
+ // Transaction type
289
+ const typeStr = tx.type || tx.tx_type || tx.txType || '—';
290
+ console.log(` ${C.bright}│${C.reset} ${C.cyan}Type${C.reset} ${C.bright}│${C.reset} ${typeStr.padEnd(44)} ${C.bright}│${C.reset}`);
291
+
292
+ // Signer
293
+ const signerStr = tx.signer || tx.from || tx.pubkey || '—';
294
+ if (signerStr !== '—') {
295
+ console.log(` ${C.bright}│${C.reset} ${C.cyan}Signer${C.reset} ${C.bright}│${C.reset} ${signerStr.padEnd(44)} ${C.bright}│${C.reset}`);
296
+ }
297
+
298
+ // Status details
299
+ if (tx.confirmations !== undefined && tx.confirmations !== null) {
300
+ console.log(` ${C.bright}│${C.reset} ${C.cyan}Confirmations${C.reset} ${C.bright}│${C.reset} ${String(tx.confirmations).padEnd(44)} ${C.bright}│${C.reset}`);
301
+ }
302
+
303
+ // Error
304
+ if (tx.error || tx.err) {
305
+ const errStr = (tx.error || tx.err).toString().substring(0, 44);
306
+ console.log(` ${C.bright}│${C.reset} ${C.red}Error${C.reset} ${C.bright}│${C.reset} ${errStr.padEnd(44)} ${C.bright}│${C.reset}`);
307
+ }
308
+
309
+ // Memo
310
+ if (tx.memo) {
311
+ const memoStr = tx.memo.toString().substring(0, 44);
312
+ console.log(` ${C.bright}│${C.reset} ${C.cyan}Memo${C.reset} ${C.bright}│${C.reset} ${memoStr.padEnd(44)} ${C.bright}│${C.reset}`);
313
+ }
314
+
315
+ // Network context
316
+ if (currentSlot !== null) {
317
+ const slotsAgo = currentSlot !== null && tx.slot !== undefined ? currentSlot - tx.slot : null;
318
+ const agoStr = slotsAgo !== null ? `${slotsAgo} slots ago` : '—';
319
+ console.log(` ${C.bright}│${C.reset} ${C.cyan}Current Slot${C.reset} ${C.bright}│${C.reset} ${currentSlot.toLocaleString().padEnd(44)} ${C.bright}│${C.reset}`);
320
+ console.log(` ${C.bright}│${C.reset} ${C.cyan}Confirmed${C.reset} ${C.bright}│${C.reset} ${agoStr.padEnd(44)} ${C.bright}│${C.reset}`);
321
+ }
322
+
323
+ } else {
324
+ console.log(` ${C.bright}│${C.reset} ${C.yellow}Transaction not found on chain${C.reset} ${C.bright}│${C.reset}`);
325
+ }
326
+
327
+ console.log(` ${C.bright}└─────────────────────┴──────────────────────────────────────────────┘${C.reset}`);
328
+
329
+ // Logs section
330
+ if (tx && tx.logs && tx.logs.length > 0) {
331
+ console.log(`\n ${C.bright}── Program Logs ─────────────────────────────────────────────${C.reset}\n`);
332
+ tx.logs.forEach((log, i) => {
333
+ const prefix = log.includes('Error') || log.includes('failed') ? C.red : C.dim;
334
+ console.log(` ${prefix}${log}${C.reset}`);
335
+ });
336
+ }
337
+
338
+ // RPC info
339
+ console.log(`\n ${C.dim}RPC: ${rpcUrl}${C.reset}`);
340
+ console.log(` ${C.dim}SDK: getTransaction() → GET /v1/transaction/${shortSig(signature)}${C.reset}\n`);
341
+ }
342
+
343
+ function printTxJson(tx, signature, rpcUrl) {
344
+ const status = statusColorAndLabel(tx);
345
+
346
+ const output = {
347
+ signature,
348
+ rpc: rpcUrl,
349
+ status: status.label,
350
+ confirmed: tx !== null && (tx.blockTime !== undefined || tx.slot !== undefined),
351
+ slot: tx?.slot ?? null,
352
+ block_time: tx?.blockTime ?? null,
353
+ block_time_human: formatTime(tx?.blockTime),
354
+ fee: tx?.fee ?? null,
355
+ type: tx?.type ?? tx?.tx_type ?? tx?.txType ?? null,
356
+ signer: tx?.signer ?? tx?.from ?? tx?.pubkey ?? null,
357
+ error: tx?.error ?? tx?.err ?? null,
358
+ logs: tx?.logs ?? null,
359
+ memo: tx?.memo ?? null,
360
+ confirmations: tx?.confirmations ?? null,
361
+ raw: tx ?? null,
362
+ cli_version: CLI_VERSION,
363
+ timestamp: new Date().toISOString(),
364
+ sdk_method: 'client.getTransaction()',
365
+ rpc_endpoint: `GET /v1/transaction/${shortSig(signature)}`,
366
+ };
367
+
368
+ console.log(JSON.stringify(output, null, 2));
369
+ }
370
+
371
+ // ---------------------------------------------------------------------------
372
+ // Main Command
373
+ // ---------------------------------------------------------------------------
374
+
375
+ async function txCommand() {
376
+ const opts = parseArgs();
377
+
378
+ if (opts.help || !opts.signature) {
379
+ showHelp();
380
+ return;
381
+ }
382
+
383
+ const { signature, rpc, asJson, showLogs, wait, waitTimeoutS } = opts;
384
+
385
+ if (!asJson) {
386
+ console.log(`\n${C.bright}${C.cyan}── Transaction Lookup ───────────────────────────────────────${C.reset}`);
387
+ console.log(` ${C.dim}Signature: ${C.magenta}${shortSig(signature)}${C.reset}`);
388
+ console.log(` ${C.dim}RPC: ${rpc}${C.reset}`);
389
+
390
+ if (wait) {
391
+ console.log(` ${C.dim}Mode: polling until confirmed (timeout: ${waitTimeoutS}s)${C.reset}`);
392
+ } else {
393
+ console.log(` ${C.dim}Mode: one-shot lookup${C.reset}`);
394
+ }
395
+ console.log();
396
+ }
397
+
398
+ if (wait) {
399
+ // Wait for confirmation mode
400
+ const effectiveTimeout = Math.min(waitTimeoutS, 300);
401
+ const result = await waitForConfirmation(rpc, signature, effectiveTimeout);
402
+
403
+ if (result.confirmed) {
404
+ const tx = result.tx;
405
+
406
+ if (asJson) {
407
+ printTxJson(tx, signature, rpc);
408
+ } else {
409
+ const currentSlot = null; // Already waited, no need to fetch again
410
+ printTxHuman(tx, signature, rpc, currentSlot);
411
+ console.log(` ${C.green}✓ Confirmed after ${result.waitedS}s${C.reset}\n`);
412
+ }
413
+ } else {
414
+ if (asJson) {
415
+ console.log(JSON.stringify({
416
+ signature,
417
+ rpc,
418
+ status: 'TIMEOUT',
419
+ confirmed: false,
420
+ waited_s: result.waitedS,
421
+ message: `Transaction not confirmed after ${result.waitedS}s`,
422
+ cli_version: CLI_VERSION,
423
+ timestamp: new Date().toISOString(),
424
+ }, null, 2));
425
+ } else {
426
+ console.log(`\n ${C.yellow}⚠ Timed out — transaction not confirmed after ${result.waitedS}s${C.reset}`);
427
+ console.log(` ${C.dim}It may still be pending. Check again with:${C.reset}`);
428
+ console.log(` ${C.cyan}aether tx ${signature}${C.reset}\n`);
429
+ }
430
+ process.exit(1);
431
+ }
432
+ } else {
433
+ // One-shot lookup
434
+ let tx;
435
+ let currentSlot;
436
+
437
+ if (!asJson) {
438
+ process.stdout.write(` ${C.dim}Fetching from ${rpc}...${C.reset}\n`);
439
+ }
440
+
441
+ try {
442
+ const result = await fetchTransaction(rpc, signature);
443
+ tx = result.tx;
444
+ currentSlot = result.currentSlot;
445
+ } catch (err) {
446
+ if (asJson) {
447
+ console.log(JSON.stringify({
448
+ signature,
449
+ rpc,
450
+ error: err.message,
451
+ status: 'ERROR',
452
+ confirmed: false,
453
+ cli_version: CLI_VERSION,
454
+ timestamp: new Date().toISOString(),
455
+ }, null, 2));
456
+ } else {
457
+ console.log(`\n ${C.red}✗ Failed to fetch transaction:${C.reset} ${err.message}`);
458
+ console.log(` ${C.dim}RPC: ${rpc}${C.reset}`);
459
+ console.log(` ${C.dim}The RPC endpoint may be down or the signature may be invalid.${C.reset}\n`);
460
+ }
461
+ process.exit(1);
462
+ }
463
+
464
+ if (asJson) {
465
+ printTxJson(tx, signature, rpc);
466
+ } else {
467
+ // Suppress logs in human output unless requested
468
+ if (!showLogs && tx) {
469
+ tx = { ...tx, logs: undefined };
470
+ }
471
+ printTxHuman(tx, signature, rpc, currentSlot);
472
+ }
473
+ }
474
+ }
475
+
476
+ // ---------------------------------------------------------------------------
477
+ // Exports
478
+ // ---------------------------------------------------------------------------
479
+
480
+ module.exports = { txCommand };
481
+
482
+ if (require.main === module) {
483
+ txCommand().catch(err => {
484
+ console.error(`\n${C.red}✗ Tx command failed:${C.reset} ${err.message}\n`);
485
+ process.exit(1);
486
+ });
487
+ }
package/index.js CHANGED
@@ -42,6 +42,7 @@ const { configCommand } = require('./commands/config');
42
42
  const { stakeCommand } = require('./commands/stake');
43
43
  const { nftCommand } = require('./commands/nft');
44
44
  const { installCommand } = require('./commands/install');
45
+ const { pingCommand } = require('./commands/ping');
45
46
  const readline = require('readline');
46
47
 
47
48
  // CLI version
@@ -245,16 +246,17 @@ const COMMANDS = {
245
246
  transferCommand();
246
247
  },
247
248
  },
248
- tx: {
249
- description: 'Transaction history — aether tx history --address <addr> [--limit 20] [--json]',
249
+ 'tx-history': {
250
+ description: 'Transaction history for an address — aether tx-history --address <addr> [--limit 20] [--json]',
250
251
  handler: () => {
251
252
  txHistoryCommand();
252
253
  },
253
254
  },
254
- 'tx-history': {
255
- description: 'Transaction history for an address — aether tx-history --address <addr> [--limit 20] [--json]',
255
+ tx: {
256
+ description: 'Look up a transaction by signature — aether tx <signature> [--json] [--wait] [--logs]',
256
257
  handler: () => {
257
- txHistoryCommand();
258
+ const { txCommand } = require('./commands/tx');
259
+ txCommand();
258
260
  },
259
261
  },
260
262
  blockhash: {
@@ -449,6 +451,13 @@ const COMMANDS = {
449
451
  slotCommand();
450
452
  },
451
453
  },
454
+ multisig: {
455
+ description: 'Multi-signature wallet management — create, list, info, send, add-signer — aether multisig <subcommand>',
456
+ handler: () => {
457
+ const { multisigCommand } = require('./commands/multisig');
458
+ multisigCommand();
459
+ },
460
+ },
452
461
  claim: {
453
462
  description: 'Claim accumulated staking rewards — aether claim --address <addr> [--json] [--dry-run]',
454
463
  handler: () => {
package/package.json CHANGED
@@ -1,76 +1,76 @@
1
- {
2
- "name": "@jellylegsai/aether-cli",
3
- "version": "1.9.0",
4
- "description": "Aether CLI - Validator Onboarding, Staking, and Blockchain Operations",
5
- "main": "index.js",
6
- "bin": {
7
- "aether-cli": "./index.js",
8
- "aether": "./index.js"
9
- },
10
- "scripts": {
11
- "test": "node test.js && cd sdk && node test.js",
12
- "lint": "echo 'No linting configured'",
13
- "doctor": "node index.js doctor",
14
- "init": "node index.js init",
15
- "monitor": "node index.js monitor",
16
- "logs": "node index.js logs",
17
- "sdk": "node index.js sdk",
18
- "sdk-test": "node index.js sdk-test",
19
- "wallet": "node index.js wallet",
20
- "stake": "node index.js stake",
21
- "stake-positions": "node index.js stake-positions",
22
- "unstake": "node index.js unstake",
23
- "claim": "node index.js claim",
24
- "delegations": "node index.js delegations",
25
- "rewards": "node index.js rewards",
26
- "validators": "node index.js validators",
27
- "validator": "node index.js validator",
28
- "validator-register": "node index.js validator-register",
29
- "validator-start": "node index.js validator-start",
30
- "validator-status": "node index.js validator-status",
31
- "validator-info": "node index.js validator-info",
32
- "multisig": "node index.js multisig",
33
- "transfer": "node index.js transfer",
34
- "balance": "node index.js balance",
35
- "tx-history": "node index.js tx-history",
36
- "tx": "node index.js tx-history",
37
- "network": "node index.js network",
38
- "epoch": "node index.js epoch",
39
- "supply": "node index.js supply",
40
- "status": "node index.js status",
41
- "blockhash": "node index.js blockhash",
42
- "tps": "node index.js tps",
43
- "fees": "node index.js fees",
44
- "apy": "node index.js apy",
45
- "account": "node index.js account",
46
- "price": "node index.js price",
47
- "emergency": "node index.js emergency",
48
- "snapshot": "node index.js snapshot",
49
- "nft": "node index.js nft",
50
- "prepare-publish": "npm pack && echo 'Package ready for publishing'",
51
- "version-bump": "node -e \"const fs=require('fs');let p=JSON.parse(fs.readFileSync('package.json'));let v=p.version.split('.');v[2]=parseInt(v[2])+1;p.version=v.join('.');fs.writeFileSync('package.json',JSON.stringify(p,null,2));console.log('Version bumped to',p.version);\""
52
- },
53
- "keywords": [
54
- "aether",
55
- "blockchain",
56
- "validator",
57
- "staking",
58
- "cryptocurrency",
59
- "multi-signature",
60
- "cli"
61
- ],
62
- "author": "Jelly-legs AI Team",
63
- "license": "MIT",
64
- "repository": {
65
- "type": "git",
66
- "url": "https://github.com/jelly-legs-ai/Jelly-legs-unsteady-workshop"
67
- },
68
- "engines": {
69
- "node": ">=14.0.0"
70
- },
71
- "dependencies": {
72
- "bip39": "^3.0.4",
73
- "tweetnacl": "^1.0.3",
74
- "bs58": "^5.0.0"
75
- }
76
- }
1
+ {
2
+ "name": "@jellylegsai/aether-cli",
3
+ "version": "1.9.2",
4
+ "description": "Aether CLI - Validator Onboarding, Staking, and Blockchain Operations",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "aether-cli": "index.js",
8
+ "aether": "index.js"
9
+ },
10
+ "scripts": {
11
+ "test": "node test.js && cd sdk && node test.js",
12
+ "lint": "echo 'No linting configured'",
13
+ "doctor": "node index.js doctor",
14
+ "init": "node index.js init",
15
+ "monitor": "node index.js monitor",
16
+ "logs": "node index.js logs",
17
+ "sdk": "node index.js sdk",
18
+ "sdk-test": "node index.js sdk-test",
19
+ "wallet": "node index.js wallet",
20
+ "stake": "node index.js stake",
21
+ "stake-positions": "node index.js stake-positions",
22
+ "unstake": "node index.js unstake",
23
+ "claim": "node index.js claim",
24
+ "delegations": "node index.js delegations",
25
+ "rewards": "node index.js rewards",
26
+ "validators": "node index.js validators",
27
+ "validator": "node index.js validator",
28
+ "validator-register": "node index.js validator-register",
29
+ "validator-start": "node index.js validator-start",
30
+ "validator-status": "node index.js validator-status",
31
+ "validator-info": "node index.js validator-info",
32
+ "multisig": "node index.js multisig",
33
+ "transfer": "node index.js transfer",
34
+ "balance": "node index.js balance",
35
+ "tx-history": "node index.js tx-history",
36
+ "tx": "node index.js tx-history",
37
+ "network": "node index.js network",
38
+ "epoch": "node index.js epoch",
39
+ "supply": "node index.js supply",
40
+ "status": "node index.js status",
41
+ "blockhash": "node index.js blockhash",
42
+ "tps": "node index.js tps",
43
+ "fees": "node index.js fees",
44
+ "apy": "node index.js apy",
45
+ "account": "node index.js account",
46
+ "price": "node index.js price",
47
+ "emergency": "node index.js emergency",
48
+ "snapshot": "node index.js snapshot",
49
+ "nft": "node index.js nft",
50
+ "prepare-publish": "npm pack && echo 'Package ready for publishing'",
51
+ "version-bump": "node -e \"const fs=require('fs');let p=JSON.parse(fs.readFileSync('package.json'));let v=p.version.split('.');v[2]=parseInt(v[2])+1;p.version=v.join('.');fs.writeFileSync('package.json',JSON.stringify(p,null,2));console.log('Version bumped to',p.version);\""
52
+ },
53
+ "keywords": [
54
+ "aether",
55
+ "blockchain",
56
+ "validator",
57
+ "staking",
58
+ "cryptocurrency",
59
+ "multi-signature",
60
+ "cli"
61
+ ],
62
+ "author": "Jelly-legs AI Team",
63
+ "license": "MIT",
64
+ "repository": {
65
+ "type": "git",
66
+ "url": "git+https://github.com/jelly-legs-ai/Jelly-legs-unsteady-workshop.git"
67
+ },
68
+ "engines": {
69
+ "node": ">=14.0.0"
70
+ },
71
+ "dependencies": {
72
+ "bip39": "^3.0.4",
73
+ "tweetnacl": "^1.0.3",
74
+ "bs58": "^5.0.0"
75
+ }
76
+ }
package/sdk/index.js CHANGED
@@ -1220,6 +1220,64 @@ class AetherClient {
1220
1220
  return receipt;
1221
1221
  }
1222
1222
 
1223
+ // ============================================================
1224
+ // NFT Query Methods - Real blockchain calls for NFT data
1225
+ // ============================================================
1226
+
1227
+ /**
1228
+ * Get NFT details by ID
1229
+ * RPC: GET /v1/nft/<id>
1230
+ *
1231
+ * @param {string} nftId - NFT ID
1232
+ * @returns {Promise<Object>} NFT details: { id, creator, metadata_url, royalties, supply, max_supply, created_at, update_authority }
1233
+ */
1234
+ async getNFT(nftId) {
1235
+ if (!nftId) throw new AetherSDKError('NFT ID is required', 'VALIDATION_ERROR');
1236
+ return this._executeWithRetry(
1237
+ async () => {
1238
+ const result = await this._httpGet(`/v1/nft/${nftId}`);
1239
+ return result;
1240
+ },
1241
+ 'getNFT'
1242
+ );
1243
+ }
1244
+
1245
+ /**
1246
+ * Get NFT holdings for an address
1247
+ * RPC: GET /v1/nft-holdings/<address>
1248
+ *
1249
+ * @param {string} address - Account address
1250
+ * @returns {Promise<Array>} List of NFT holdings with { id, amount, metadata_url }
1251
+ */
1252
+ async getNFTHoldings(address) {
1253
+ if (!address) throw new AetherSDKError('Address is required', 'VALIDATION_ERROR');
1254
+ return this._executeWithRetry(
1255
+ async () => {
1256
+ const result = await this._httpGet(`/v1/nft-holdings/${address}`);
1257
+ return result.holdings ?? result.nfts ?? result ?? [];
1258
+ },
1259
+ 'getNFTHoldings'
1260
+ );
1261
+ }
1262
+
1263
+ /**
1264
+ * Get all NFTs created by an address
1265
+ * RPC: GET /v1/nft-created/<address>
1266
+ *
1267
+ * @param {string} address - Creator address
1268
+ * @returns {Promise<Array>} List of created NFTs
1269
+ */
1270
+ async getNFTsByCreator(address) {
1271
+ if (!address) throw new AetherSDKError('Address is required', 'VALIDATION_ERROR');
1272
+ return this._executeWithRetry(
1273
+ async () => {
1274
+ const result = await this._httpGet(`/v1/nft-created/${address}`);
1275
+ return result.nfts ?? result.created ?? result ?? [];
1276
+ },
1277
+ 'getNFTsByCreator'
1278
+ );
1279
+ }
1280
+
1223
1281
  // ============================================================
1224
1282
  // Utilities
1225
1283
  // ============================================================
@@ -1575,6 +1633,52 @@ async function ping(rpcUrl) {
1575
1633
  }
1576
1634
  }
1577
1635
 
1636
+ // ============================================================
1637
+ // NFT Convenience Functions (for quick one-off calls)
1638
+ // ============================================================
1639
+
1640
+ /**
1641
+ * Get NFT details by ID (uses default RPC)
1642
+ * @param {string} nftId - NFT ID
1643
+ * @returns {Promise<Object>} NFT details
1644
+ */
1645
+ async function getNFT(nftId) {
1646
+ const client = new AetherClient();
1647
+ try {
1648
+ return await client.getNFT(nftId);
1649
+ } finally {
1650
+ client.destroy();
1651
+ }
1652
+ }
1653
+
1654
+ /**
1655
+ * Get NFT holdings for an address (uses default RPC)
1656
+ * @param {string} address - Account address
1657
+ * @returns {Promise<Array>} List of NFT holdings
1658
+ */
1659
+ async function getNFTHoldings(address) {
1660
+ const client = new AetherClient();
1661
+ try {
1662
+ return await client.getNFTHoldings(address);
1663
+ } finally {
1664
+ client.destroy();
1665
+ }
1666
+ }
1667
+
1668
+ /**
1669
+ * Get NFTs created by an address (uses default RPC)
1670
+ * @param {string} address - Creator address
1671
+ * @returns {Promise<Array>} List of created NFTs
1672
+ */
1673
+ async function getNFTsByCreator(address) {
1674
+ const client = new AetherClient();
1675
+ try {
1676
+ return await client.getNFTsByCreator(address);
1677
+ } finally {
1678
+ client.destroy();
1679
+ }
1680
+ }
1681
+
1578
1682
  // ============================================================
1579
1683
  // Exports
1580
1684
  // ============================================================
@@ -1615,6 +1719,11 @@ module.exports = {
1615
1719
  getPeers,
1616
1720
  getHealth,
1617
1721
 
1722
+ // NFT queries
1723
+ getNFT,
1724
+ getNFTHoldings,
1725
+ getNFTsByCreator,
1726
+
1618
1727
  // Transactions
1619
1728
  sendTransaction,
1620
1729
 
Binary file
Binary file
Binary file
Binary file
Binary file