aether-hub 1.2.6 → 1.2.7
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/account.js +10 -34
- package/commands/balance.js +276 -0
- package/commands/blockhash.js +181 -0
- package/commands/broadcast.js +323 -323
- package/commands/claim.js +292 -0
- package/commands/emergency.js +657 -657
- package/commands/epoch.js +12 -94
- package/commands/fees.js +276 -0
- package/commands/info.js +38 -79
- package/commands/network.js +34 -108
- package/commands/ping.js +11 -65
- package/commands/price.js +253 -253
- package/commands/sdk-test.js +477 -0
- package/commands/stake-info.js +139 -0
- package/commands/status.js +113 -157
- package/commands/supply.js +34 -82
- package/commands/tps.js +238 -0
- package/commands/transfer.js +495 -0
- package/commands/tx-history.js +462 -508
- package/commands/validator-info.js +10 -4
- package/commands/validator-start.js +1 -1
- package/commands/validator-status.js +32 -73
- package/commands/validators.js +36 -75
- package/commands/wallet.js +5 -29
- package/index.js +54 -6
- package/package.json +1 -3
package/commands/tx-history.js
CHANGED
|
@@ -1,508 +1,462 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* aether-cli tx-history
|
|
4
|
-
*
|
|
5
|
-
* Fetch and display transaction history for an Aether wallet address.
|
|
6
|
-
* Shows recent transactions with type, amount, timestamp, fee, and status.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* aether tx history --address <addr> [--limit <n>] [--json] [--rpc <url>]
|
|
10
|
-
* aether history --address <addr> [--limit <n>] [--json]
|
|
11
|
-
*
|
|
12
|
-
* Examples:
|
|
13
|
-
* aether tx history --address ATHxxx
|
|
14
|
-
* aether tx history --address ATHxxx --limit 50 --json
|
|
15
|
-
* aether history --address ATHxxx --rpc https://rpc.aether.io
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
//
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
console.log(
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
displayTxTable([]);
|
|
464
|
-
} else {
|
|
465
|
-
displayJson([], { address: opts.address, rpc: rpcUrl, limit });
|
|
466
|
-
}
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// Step 2: Fetch each transaction in parallel (up to 10 at a time)
|
|
471
|
-
const txResults = [];
|
|
472
|
-
const BATCH = 10;
|
|
473
|
-
|
|
474
|
-
for (let i = 0; i < signatures.length; i += BATCH) {
|
|
475
|
-
const batch = signatures.slice(i, i + BATCH);
|
|
476
|
-
const batchPromises = batch.map(sig => fetchTx(rpcUrl, sig.signature).catch(err => ({ error: err.message })));
|
|
477
|
-
const batchResults = await Promise.all(batchPromises);
|
|
478
|
-
txResults.push(...batchResults);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// Step 3: Parse and normalize
|
|
482
|
-
const txs = txResults
|
|
483
|
-
.map((res, idx) => {
|
|
484
|
-
if (res.error) return null;
|
|
485
|
-
try {
|
|
486
|
-
return parseTransaction(res.result || {}, signatures[idx] || {});
|
|
487
|
-
} catch {
|
|
488
|
-
return null;
|
|
489
|
-
}
|
|
490
|
-
})
|
|
491
|
-
.filter(Boolean);
|
|
492
|
-
|
|
493
|
-
if (opts.json) {
|
|
494
|
-
displayJson(txs, { address: opts.address, rpc: rpcUrl, limit });
|
|
495
|
-
} else {
|
|
496
|
-
displayTxTable(txs);
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
} catch (err) {
|
|
500
|
-
console.log(`\n ${C.red}✗ Failed to fetch transaction history:${C.reset} ${err.message}\n`);
|
|
501
|
-
if (err.stack && !opts.json) {
|
|
502
|
-
console.log(` ${C.dim}${err.stack.split('\n').slice(0, 3).join('\n ')}${C.reset}\n`);
|
|
503
|
-
}
|
|
504
|
-
process.exit(1);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
main();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* aether-cli tx-history
|
|
4
|
+
*
|
|
5
|
+
* Fetch and display transaction history for an Aether wallet address.
|
|
6
|
+
* Shows recent transactions with type, amount, timestamp, fee, and status.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* aether tx history --address <addr> [--limit <n>] [--json] [--rpc <url>]
|
|
10
|
+
* aether history --address <addr> [--limit <n>] [--json]
|
|
11
|
+
*
|
|
12
|
+
* Examples:
|
|
13
|
+
* aether tx history --address ATHxxx
|
|
14
|
+
* aether tx history --address ATHxxx --limit 50 --json
|
|
15
|
+
* aether history --address ATHxxx --rpc https://rpc.aether.io
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
// Import SDK for real blockchain RPC calls
|
|
21
|
+
const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
|
|
22
|
+
const aether = require(sdkPath);
|
|
23
|
+
|
|
24
|
+
// ANSI colours
|
|
25
|
+
const C = {
|
|
26
|
+
reset: '\x1b[0m',
|
|
27
|
+
bright: '\x1b[1m',
|
|
28
|
+
dim: '\x1b[2m',
|
|
29
|
+
red: '\x1b[31m',
|
|
30
|
+
green: '\x1b[32m',
|
|
31
|
+
yellow: '\x1b[33m',
|
|
32
|
+
cyan: '\x1b[36m',
|
|
33
|
+
magenta: '\x1b[35m',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const CLI_VERSION = '1.0.0';
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// SDK helpers - Real blockchain RPC calls via Aether SDK
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
function getDefaultRpc() {
|
|
43
|
+
return process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function createClient(rpcUrl) {
|
|
47
|
+
return new aether.AetherClient({ rpcUrl });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Argument parsing
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
function parseArgs() {
|
|
55
|
+
const args = process.argv.slice(2);
|
|
56
|
+
const result = { address: null, limit: 20, json: false, rpc: null };
|
|
57
|
+
|
|
58
|
+
for (let i = 0; i < args.length; i++) {
|
|
59
|
+
const arg = args[i];
|
|
60
|
+
if ((arg === '--address' || arg === '-a') && args[i + 1] && !args[i + 1].startsWith('-')) {
|
|
61
|
+
result.address = args[i + 1];
|
|
62
|
+
i++;
|
|
63
|
+
} else if ((arg === '--limit' || arg === '-l') && args[i + 1] && !args[i + 1].startsWith('-')) {
|
|
64
|
+
result.limit = parseInt(args[i + 1], 10);
|
|
65
|
+
i++;
|
|
66
|
+
} else if (arg === '--json' || arg === '--json-output') {
|
|
67
|
+
result.json = true;
|
|
68
|
+
} else if (arg === '--rpc' && args[i + 1] && !args[i + 1].startsWith('-')) {
|
|
69
|
+
result.rpc = args[i + 1];
|
|
70
|
+
i++;
|
|
71
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
72
|
+
result.help = true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Formatting helpers
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
function formatAether(lamports) {
|
|
84
|
+
if (lamports == null) return '—';
|
|
85
|
+
const aeth = lamports / 1e9;
|
|
86
|
+
if (aeth === 0) return '0 AETH';
|
|
87
|
+
return aeth.toFixed(4).replace(/\.?0+$/, '') + ' AETH';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function formatTimestamp(slot, blockTime) {
|
|
91
|
+
if (!blockTime) return '—';
|
|
92
|
+
try {
|
|
93
|
+
const d = new Date(blockTime * 1000);
|
|
94
|
+
return d.toISOString().replace('T', ' ').slice(0, 19);
|
|
95
|
+
} catch {
|
|
96
|
+
return String(blockTime);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function shortAddress(addr) {
|
|
101
|
+
if (!addr) return '—';
|
|
102
|
+
if (addr.length <= 16) return addr;
|
|
103
|
+
return addr.slice(0, 8) + '…' + addr.slice(-6);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function formatTxType(type) {
|
|
107
|
+
const t = (type || 'unknown').toLowerCase();
|
|
108
|
+
if (t.includes('transfer') || t.includes('send') || t.includes('payment')) return { label: 'TRANSFER', color: C.cyan };
|
|
109
|
+
if (t.includes('stake') || t.includes('delegate')) return { label: 'STAKE', color: C.green };
|
|
110
|
+
if (t.includes('unstake') || t.includes('deactivate')) return { label: 'UNSTAKE', color: C.yellow };
|
|
111
|
+
if (t.includes('reward') || t.includes('mint')) return { label: 'REWARD', color: C.magenta };
|
|
112
|
+
if (t.includes('vote')) return { label: 'VOTE', color: C.bright };
|
|
113
|
+
if (t.includes('create') || t.includes('initialize')) return { label: 'CREATE', color: C.bright };
|
|
114
|
+
if (t.includes('burn')) return { label: 'BURN', color: C.red };
|
|
115
|
+
return { label: type ? type.toUpperCase().slice(0, 10) : 'TX', color: C.dim };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function formatStatus(status) {
|
|
119
|
+
if (!status) return { label: '—', color: C.dim };
|
|
120
|
+
const s = status.toLowerCase();
|
|
121
|
+
if (s === 'success' || s === 'finalized' || s === 'confirmed') {
|
|
122
|
+
return { label: '✓ OK', color: C.green };
|
|
123
|
+
}
|
|
124
|
+
if (s === 'pending' || s === 'processing') {
|
|
125
|
+
return { label: '⏳', color: C.yellow };
|
|
126
|
+
}
|
|
127
|
+
if (s === 'failed') {
|
|
128
|
+
return { label: '✗ FAIL', color: C.red };
|
|
129
|
+
}
|
|
130
|
+
return { label: status.slice(0, 8), color: C.dim };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// Core RPC calls
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Fetch confirmed transaction signatures for an address using getSignaturesForAddress.
|
|
139
|
+
*/
|
|
140
|
+
async function fetchTxSignatures(rpcUrl, address, limit) {
|
|
141
|
+
const body = {
|
|
142
|
+
jsonrpc: '2.0',
|
|
143
|
+
id: 1,
|
|
144
|
+
method: 'getSignaturesForAddress',
|
|
145
|
+
params: [
|
|
146
|
+
address,
|
|
147
|
+
{ limit },
|
|
148
|
+
],
|
|
149
|
+
};
|
|
150
|
+
return httpPost(rpcUrl, '/', body);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Fetch a specific confirmed transaction by signature.
|
|
155
|
+
*/
|
|
156
|
+
async function fetchTx(rpcUrl, signature) {
|
|
157
|
+
const body = {
|
|
158
|
+
jsonrpc: '2.0',
|
|
159
|
+
id: 1,
|
|
160
|
+
method: 'getTransaction',
|
|
161
|
+
params: [
|
|
162
|
+
signature,
|
|
163
|
+
{ encoding: 'jsonParsed', maxSupportedTransactionVersion: 0 },
|
|
164
|
+
],
|
|
165
|
+
};
|
|
166
|
+
return httpPost(rpcUrl, '/', body);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Parse a transaction result into a normalized display object.
|
|
171
|
+
*/
|
|
172
|
+
function parseTransaction(txResult, sigInfo) {
|
|
173
|
+
const blockTime = sigInfo.blockTime || txResult.blockTime;
|
|
174
|
+
const slot = sigInfo.slot;
|
|
175
|
+
const status = sigInfo.err ? 'failed' : (sigInfo.confirmationStatus || 'confirmed');
|
|
176
|
+
|
|
177
|
+
let txType = 'unknown';
|
|
178
|
+
let amount = 0;
|
|
179
|
+
let fee = txResult.meta?.fee || 0;
|
|
180
|
+
let fromAddr = null;
|
|
181
|
+
let toAddr = null;
|
|
182
|
+
let memo = null;
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const msg = txResult.transaction?.message;
|
|
186
|
+
if (msg) {
|
|
187
|
+
// Parse instructions for transfer/stake types
|
|
188
|
+
const instructions = msg.instructions || [];
|
|
189
|
+
for (const ix of instructions) {
|
|
190
|
+
const programId = ix.programId || (ix.parsed && ix.parsed.info && ix.parsed.type);
|
|
191
|
+
// Native transfer
|
|
192
|
+
if (ix.parsed && ix.parsed.type === 'transfer') {
|
|
193
|
+
txType = 'transfer';
|
|
194
|
+
const info = ix.parsed.info;
|
|
195
|
+
fromAddr = info.source || info.from;
|
|
196
|
+
toAddr = info.destination || info.to;
|
|
197
|
+
amount = info.lamports || info.amount || 0;
|
|
198
|
+
} else if (ix.parsed && ix.parsed.type === 'stake') {
|
|
199
|
+
txType = 'stake';
|
|
200
|
+
const info = ix.parsed.info;
|
|
201
|
+
fromAddr = info.from || info.funder;
|
|
202
|
+
toAddr = info.validator;
|
|
203
|
+
amount = info.lamports || info.amount || 0;
|
|
204
|
+
} else if (ix.parsed && ix.parsed.type === 'withdrawStake') {
|
|
205
|
+
txType = 'unstake';
|
|
206
|
+
const info = ix.parsed.info;
|
|
207
|
+
toAddr = info.destination || info.withdrawer;
|
|
208
|
+
amount = info.lamports || info.amount || 0;
|
|
209
|
+
} else if (ix.parsed && ix.parsed.type === 'vote') {
|
|
210
|
+
txType = 'vote';
|
|
211
|
+
} else if (ix.parsed && ix.parsed.type === 'initialize') {
|
|
212
|
+
txType = 'initialize';
|
|
213
|
+
} else if (ix.parsed && ix.parsed.type === 'createAccount') {
|
|
214
|
+
txType = 'create';
|
|
215
|
+
} else if (ix.parsed && ix.parsed.type === 'approve') {
|
|
216
|
+
txType = 'stake';
|
|
217
|
+
const info = ix.parsed.info || {};
|
|
218
|
+
fromAddr = info.from || info.owner;
|
|
219
|
+
toAddr = info.stake;
|
|
220
|
+
amount = info.amount || info.lamports || 0;
|
|
221
|
+
} else if (ix.parsed && ix.parsed.type === 'delegate') {
|
|
222
|
+
txType = 'stake';
|
|
223
|
+
const info = ix.parsed.info || {};
|
|
224
|
+
fromAddr = info.stake || info.from;
|
|
225
|
+
toAddr = info.validator;
|
|
226
|
+
amount = info.lamports || 0;
|
|
227
|
+
} else if (ix.parsed && ix.parsed.type === 'withdraw') {
|
|
228
|
+
txType = 'unstake';
|
|
229
|
+
const info = ix.parsed.info || {};
|
|
230
|
+
toAddr = info.destination;
|
|
231
|
+
amount = info.lamports || 0;
|
|
232
|
+
}
|
|
233
|
+
// Check memo
|
|
234
|
+
if (ix.memo) memo = ix.memo;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Fallback: try legacy instructions if no parsed instructions
|
|
238
|
+
if (!instructions.length || instructions.every(ix => !ix.parsed)) {
|
|
239
|
+
for (const ix of instructions) {
|
|
240
|
+
if (ix.data === 'AAAA' || ix.data === '2ugJ4ELK3wW9qNXH' || !ix.data) {
|
|
241
|
+
txType = 'transfer';
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Compute fee
|
|
247
|
+
if (txResult.meta) {
|
|
248
|
+
fee = txResult.meta.fee || 0;
|
|
249
|
+
if (txResult.meta.postBalances && txResult.meta.preBalances) {
|
|
250
|
+
// Try to detect native transfer from balance changes
|
|
251
|
+
for (let i = 0; i < txResult.meta.postBalances.length; i++) {
|
|
252
|
+
const diff = txResult.meta.postBalances[i] - txResult.meta.preBalances[i];
|
|
253
|
+
if (diff < 0) {
|
|
254
|
+
amount = Math.abs(diff);
|
|
255
|
+
if (!fromAddr) fromAddr = msg.accountKeys?.[i];
|
|
256
|
+
} else if (diff > 0 && amount === 0) {
|
|
257
|
+
if (!toAddr) toAddr = msg.accountKeys?.[i];
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
} catch (e) {
|
|
264
|
+
// Parsing failed — use defaults
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
signature: sigInfo.signature || sigInfo.signatures?.[0],
|
|
269
|
+
slot,
|
|
270
|
+
blockTime,
|
|
271
|
+
status,
|
|
272
|
+
type: txType,
|
|
273
|
+
amount,
|
|
274
|
+
fee,
|
|
275
|
+
from: fromAddr,
|
|
276
|
+
to: toAddr,
|
|
277
|
+
memo,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ---------------------------------------------------------------------------
|
|
282
|
+
// Display
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
|
|
285
|
+
function displayTxTable(txs) {
|
|
286
|
+
// Header
|
|
287
|
+
console.log(
|
|
288
|
+
`\n${C.bright}${C.cyan} ╔══════════════════════════════════════════════════════════════════════════════════════════╗${C.reset}\n` +
|
|
289
|
+
` ║${C.bright} ${C.cyan}Transaction History${C.reset}${C.bright} ║${C.reset}\n` +
|
|
290
|
+
` ${C.cyan}╚══════════════════════════════════════════════════════════════════════════════════════════${C.reset}`
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
if (txs.length === 0) {
|
|
294
|
+
console.log(`\n ${C.yellow}No transactions found for this address.${C.reset}\n`);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
console.log(
|
|
299
|
+
` ${C.dim}┌────┬──────────────────────┬──────────┬───────────────┬────────────┬────────┬───────────┐${C.reset}\n` +
|
|
300
|
+
` ${C.dim}│ # │ ${C.reset}Timestamp ${C.dim}│ ${C.reset}Type ${C.dim}│ ${C.reset}Amount ${C.dim}│ ${C.reset}From ${C.dim}│ ${C.reset}To ${C.dim}│ ${C.reset}Status ${C.dim}│${C.reset}`
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
for (let i = 0; i < txs.length; i++) {
|
|
304
|
+
const tx = txs[i];
|
|
305
|
+
const num = String(i + 1).padStart(3);
|
|
306
|
+
const time = formatTimestamp(tx.blockTime).slice(0, 19).padEnd(19);
|
|
307
|
+
const typeInfo = formatTxType(tx.type);
|
|
308
|
+
const type = typeInfo.label.padEnd(10);
|
|
309
|
+
const amt = tx.amount > 0 ? formatAether(tx.amount).padEnd(13) : '—'.padEnd(13);
|
|
310
|
+
const from = shortAddress(tx.from || '').padEnd(11);
|
|
311
|
+
const to = shortAddress(tx.to || '').padEnd(11);
|
|
312
|
+
const statusInfo = formatStatus(tx.status);
|
|
313
|
+
const status = statusInfo.label;
|
|
314
|
+
|
|
315
|
+
const bgAlt = (i % 2 === 0) ? '' : C.dim;
|
|
316
|
+
const reset = C.reset + bgAlt;
|
|
317
|
+
|
|
318
|
+
console.log(
|
|
319
|
+
` ${bgAlt}${C.dim}├────┼──────────────────────┼──────────┼───────────────┼────────────┼────────┼───────────┤${reset}`
|
|
320
|
+
);
|
|
321
|
+
console.log(
|
|
322
|
+
` ${bgAlt}${C.dim}│${reset} ${num} ${bgAlt}${C.dim}│ ${reset}${time} ${bgAlt}${C.dim}│ ${reset}${typeInfo.color}${type}${reset} ${bgAlt}${C.dim}│ ${reset}${amt} ${bgAlt}${C.dim}│ ${reset}${from} ${bgAlt}${C.dim}│ ${reset}${to} ${bgAlt}${C.dim}│ ${reset}${statusInfo.color}${status}${reset}${bgAlt} │${reset}`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
console.log(
|
|
327
|
+
` ${C.dim}└────┴──────────────────────┴──────────┴───────────────┴────────────┴────────┴───────────┘${C.reset}`
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
// Summary
|
|
331
|
+
const totalVolume = txs.reduce((sum, tx) => sum + tx.amount, 0);
|
|
332
|
+
const successCount = txs.filter(tx => tx.status !== 'failed').length;
|
|
333
|
+
console.log(`\n ${C.dim} ${txs.length} transactions · Total volume: ${C.reset}${C.bright}${formatAether(totalVolume)}${C.reset} ${C.dim}· Success: ${C.reset}${C.green}${successCount}/${txs.length}${C.reset} ${C.dim}· Failed: ${C.reset}${C.red}${(txs.length - successCount)}${C.reset}\n`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function displayJson(txs, meta) {
|
|
337
|
+
console.log(JSON.stringify({
|
|
338
|
+
address: meta.address,
|
|
339
|
+
rpc: meta.rpc,
|
|
340
|
+
limit: meta.limit,
|
|
341
|
+
transaction_count: txs.length,
|
|
342
|
+
total_volume_lamports: txs.reduce((sum, tx) => sum + tx.amount, 0),
|
|
343
|
+
transactions: txs.map(tx => ({
|
|
344
|
+
signature: tx.signature,
|
|
345
|
+
slot: tx.slot,
|
|
346
|
+
timestamp: tx.blockTime ? new Date(tx.blockTime * 1000).toISOString() : null,
|
|
347
|
+
type: tx.type,
|
|
348
|
+
amount_lamports: tx.amount,
|
|
349
|
+
amount_aeth: (tx.amount / 1e9).toFixed(9),
|
|
350
|
+
fee_lamports: tx.fee,
|
|
351
|
+
from: tx.from,
|
|
352
|
+
to: tx.to,
|
|
353
|
+
memo: tx.memo,
|
|
354
|
+
status: tx.status,
|
|
355
|
+
})),
|
|
356
|
+
}, null, 2));
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ---------------------------------------------------------------------------
|
|
360
|
+
// Main
|
|
361
|
+
// ---------------------------------------------------------------------------
|
|
362
|
+
|
|
363
|
+
async function main() {
|
|
364
|
+
const opts = parseArgs();
|
|
365
|
+
|
|
366
|
+
if (opts.help) {
|
|
367
|
+
console.log(`
|
|
368
|
+
${C.bright}${C.cyan}tx-history${C.reset} — Fetch and display transaction history for an Aether address
|
|
369
|
+
|
|
370
|
+
${C.bright}USAGE${C.reset}
|
|
371
|
+
aether tx history --address <addr> [--limit <n>] [--json] [--rpc <url>]
|
|
372
|
+
aether history --address <addr> [--limit <n>] [--json]
|
|
373
|
+
|
|
374
|
+
${C.bright}OPTIONS${C.reset}
|
|
375
|
+
--address <addr> Aether wallet address (ATH...)
|
|
376
|
+
--limit <n> Max transactions to fetch (default: 20, max: 100)
|
|
377
|
+
--json Output raw JSON for scripting
|
|
378
|
+
--rpc <url> RPC endpoint (default: AETHER_RPC or http://127.0.0.1:8899)
|
|
379
|
+
--help Show this help
|
|
380
|
+
|
|
381
|
+
${C.bright}EXAMPLES${C.reset}
|
|
382
|
+
aether tx history --address ATH3abc... --limit 50
|
|
383
|
+
aether tx history --address ATH3abc... --json
|
|
384
|
+
aether history --address ATH3abc... --rpc https://mainnet.aether.io
|
|
385
|
+
`);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (!opts.address) {
|
|
390
|
+
console.log(` ${C.red}✗ Missing --address${C.reset}\n`);
|
|
391
|
+
console.log(` Usage: aether tx history --address <addr> [--limit <n>] [--json]\n`);
|
|
392
|
+
process.exit(1);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const rpcUrl = opts.rpc || getDefaultRpc();
|
|
396
|
+
const limit = Math.min(Math.max(1, opts.limit || 20), 100);
|
|
397
|
+
|
|
398
|
+
if (!opts.json) {
|
|
399
|
+
console.log(`\n${C.bright}${C.cyan} Tx History${C.reset} · ${C.dim}address:${C.reset} ${opts.address} ${C.dim}· ${C.dim}limit:${C.reset} ${limit} ${C.dim}· ${C.dim}rpc:${C.reset} ${rpcUrl}\n`);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
try {
|
|
403
|
+
// Step 1: Get transaction signatures
|
|
404
|
+
const sigsResult = await fetchTxSignatures(rpcUrl, opts.address, limit);
|
|
405
|
+
|
|
406
|
+
if (sigsResult.error) {
|
|
407
|
+
throw new Error(sigsResult.error.message || JSON.stringify(sigsResult.error));
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const signatures = Array.isArray(sigsResult.result) ? sigsResult.result : [];
|
|
411
|
+
|
|
412
|
+
if (signatures.length === 0) {
|
|
413
|
+
if (!opts.json) {
|
|
414
|
+
displayTxTable([]);
|
|
415
|
+
} else {
|
|
416
|
+
displayJson([], { address: opts.address, rpc: rpcUrl, limit });
|
|
417
|
+
}
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Step 2: Fetch each transaction in parallel (up to 10 at a time)
|
|
422
|
+
const txResults = [];
|
|
423
|
+
const BATCH = 10;
|
|
424
|
+
|
|
425
|
+
for (let i = 0; i < signatures.length; i += BATCH) {
|
|
426
|
+
const batch = signatures.slice(i, i + BATCH);
|
|
427
|
+
const batchPromises = batch.map(sig => fetchTx(rpcUrl, sig.signature).catch(err => ({ error: err.message })));
|
|
428
|
+
const batchResults = await Promise.all(batchPromises);
|
|
429
|
+
txResults.push(...batchResults);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Step 3: Parse and normalize
|
|
433
|
+
const txs = txResults
|
|
434
|
+
.map((res, idx) => {
|
|
435
|
+
if (res.error) return null;
|
|
436
|
+
try {
|
|
437
|
+
return parseTransaction(res.result || {}, signatures[idx] || {});
|
|
438
|
+
} catch {
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
})
|
|
442
|
+
.filter(Boolean);
|
|
443
|
+
|
|
444
|
+
if (opts.json) {
|
|
445
|
+
displayJson(txs, { address: opts.address, rpc: rpcUrl, limit });
|
|
446
|
+
} else {
|
|
447
|
+
displayTxTable(txs);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
} catch (err) {
|
|
451
|
+
console.log(`\n ${C.red}✗ Failed to fetch transaction history:${C.reset} ${err.message}\n`);
|
|
452
|
+
if (err.stack && !opts.json) {
|
|
453
|
+
console.log(` ${C.dim}${err.stack.split('\n').slice(0, 3).join('\n ')}${C.reset}\n`);
|
|
454
|
+
}
|
|
455
|
+
process.exit(1);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Run if called directly
|
|
460
|
+
if (require.main === module) {
|
|
461
|
+
main();
|
|
462
|
+
}
|