@jellylegsai/aether-cli 1.9.0 → 1.9.1
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 +8 -0
- package/commands/tx.js +487 -0
- package/index.js +7 -5
- package/package.json +4 -4
- package/sdk/index.js +109 -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/index.js
CHANGED
|
@@ -11,6 +11,7 @@ const walletCommand = require('./wallet');
|
|
|
11
11
|
const balanceCommand = require('./balance');
|
|
12
12
|
const transferCommand = require('./transfer');
|
|
13
13
|
const txHistoryCommand = require('./tx-history');
|
|
14
|
+
const txCommand = require('./tx');
|
|
14
15
|
const multisigCommand = require('./multisig');
|
|
15
16
|
const claimCommand = require('./claim');
|
|
16
17
|
const unstakeCommand = require('./unstake');
|
|
@@ -36,6 +37,9 @@ const priceCommand = require('./price');
|
|
|
36
37
|
const emergencyCommand = require('./emergency');
|
|
37
38
|
const snapshotCommand = require('./snapshot');
|
|
38
39
|
const nftCommand = require('./nft');
|
|
40
|
+
const pingCommand = require('./ping');
|
|
41
|
+
const slotCommand = require('./slot');
|
|
42
|
+
const stakeInfoCommand = require('./stake-info');
|
|
39
43
|
|
|
40
44
|
module.exports = {
|
|
41
45
|
doctorCommand,
|
|
@@ -50,11 +54,13 @@ module.exports = {
|
|
|
50
54
|
balanceCommand,
|
|
51
55
|
transferCommand,
|
|
52
56
|
txHistoryCommand,
|
|
57
|
+
txCommand,
|
|
53
58
|
multisigCommand,
|
|
54
59
|
claimCommand,
|
|
55
60
|
unstakeCommand,
|
|
56
61
|
stakeCommand,
|
|
57
62
|
stakePositionsCommand,
|
|
63
|
+
stakeInfoCommand,
|
|
58
64
|
networkCommand,
|
|
59
65
|
monitorLoop,
|
|
60
66
|
logsCommand,
|
|
@@ -75,4 +81,6 @@ module.exports = {
|
|
|
75
81
|
emergencyCommand,
|
|
76
82
|
snapshotCommand,
|
|
77
83
|
nftCommand,
|
|
84
|
+
pingCommand,
|
|
85
|
+
slotCommand,
|
|
78
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
|
|
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
|
-
|
|
255
|
-
description: '
|
|
255
|
+
tx: {
|
|
256
|
+
description: 'Look up a transaction by signature — aether tx <signature> [--json] [--wait] [--logs]',
|
|
256
257
|
handler: () => {
|
|
257
|
-
|
|
258
|
+
const { txCommand } = require('./commands/tx');
|
|
259
|
+
txCommand();
|
|
258
260
|
},
|
|
259
261
|
},
|
|
260
262
|
blockhash: {
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jellylegsai/aether-cli",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.1",
|
|
4
4
|
"description": "Aether CLI - Validator Onboarding, Staking, and Blockchain Operations",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"aether-cli": "
|
|
8
|
-
"aether": "
|
|
7
|
+
"aether-cli": "index.js",
|
|
8
|
+
"aether": "index.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"test": "node test.js && cd sdk && node test.js",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"license": "MIT",
|
|
64
64
|
"repository": {
|
|
65
65
|
"type": "git",
|
|
66
|
-
"url": "https://github.com/jelly-legs-ai/Jelly-legs-unsteady-workshop"
|
|
66
|
+
"url": "git+https://github.com/jelly-legs-ai/Jelly-legs-unsteady-workshop.git"
|
|
67
67
|
},
|
|
68
68
|
"engines": {
|
|
69
69
|
"node": ">=14.0.0"
|
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
|
|
package/aether-cli-1.0.0.tgz
DELETED
|
Binary file
|
package/aether-cli-1.8.0.tgz
DELETED
|
Binary file
|
package/aether-hub-1.0.5.tgz
DELETED
|
Binary file
|
package/aether-hub-1.1.8.tgz
DELETED
|
Binary file
|
package/aether-hub-1.2.1.tgz
DELETED
|
Binary file
|