aether-hub 1.2.6 → 1.2.8

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.
@@ -15,8 +15,11 @@
15
15
  * aether history --address ATHxxx --rpc https://rpc.aether.io
16
16
  */
17
17
 
18
- const https = require('https');
19
- const http = require('http');
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);
20
23
 
21
24
  // ANSI colours
22
25
  const C = {
@@ -33,67 +36,15 @@ const C = {
33
36
  const CLI_VERSION = '1.0.0';
34
37
 
35
38
  // ---------------------------------------------------------------------------
36
- // Config
39
+ // SDK helpers - Real blockchain RPC calls via Aether SDK
37
40
  // ---------------------------------------------------------------------------
38
41
 
39
42
  function getDefaultRpc() {
40
- return process.env.AETHER_RPC || 'http://127.0.0.1:8899';
41
- }
42
-
43
- // ---------------------------------------------------------------------------
44
- // HTTP helpers
45
- // ---------------------------------------------------------------------------
46
-
47
- function httpRequest(rpcUrl, path, timeoutMs = 10000) {
48
- return new Promise((resolve, reject) => {
49
- const url = new URL(path, rpcUrl);
50
- const lib = url.protocol === 'https:' ? https : http;
51
- const req = lib.request({
52
- hostname: url.hostname,
53
- port: url.port || (url.protocol === 'https:' ? 443 : 80),
54
- path: url.pathname + url.search,
55
- method: 'GET',
56
- timeout: timeoutMs,
57
- headers: { 'Content-Type': 'application/json' },
58
- }, (res) => {
59
- let data = '';
60
- res.on('data', (chunk) => data += chunk);
61
- res.on('end', () => {
62
- try { resolve(JSON.parse(data)); }
63
- catch { resolve({ raw: data }); }
64
- });
65
- });
66
- req.on('error', reject);
67
- req.on('timeout', () => { req.destroy(); reject(new Error(`Request timeout after ${timeoutMs}ms`)); });
68
- req.end();
69
- });
43
+ return process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
70
44
  }
71
45
 
72
- function httpPost(rpcUrl, path, body, timeoutMs = 15000) {
73
- return new Promise((resolve, reject) => {
74
- const url = new URL(path, rpcUrl);
75
- const lib = url.protocol === 'https:' ? https : http;
76
- const bodyStr = JSON.stringify(body);
77
- const req = lib.request({
78
- hostname: url.hostname,
79
- port: url.port || (url.protocol === 'https:' ? 443 : 80),
80
- path: url.pathname + url.search,
81
- method: 'POST',
82
- timeout: timeoutMs,
83
- headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(bodyStr) },
84
- }, (res) => {
85
- let data = '';
86
- res.on('data', (chunk) => data += chunk);
87
- res.on('end', () => {
88
- try { resolve(JSON.parse(data)); }
89
- catch { resolve({ raw: data }); }
90
- });
91
- });
92
- req.on('error', reject);
93
- req.on('timeout', () => { req.destroy(); reject(new Error(`Request timeout after ${timeoutMs}ms`)); });
94
- req.write(bodyStr);
95
- req.end();
96
- });
46
+ function createClient(rpcUrl) {
47
+ return new aether.AetherClient({ rpcUrl });
97
48
  }
98
49
 
99
50
  // ---------------------------------------------------------------------------
@@ -180,141 +131,42 @@ function formatStatus(status) {
180
131
  }
181
132
 
182
133
  // ---------------------------------------------------------------------------
183
- // Core RPC calls
134
+ // Parse a transaction result into a normalized display object
184
135
  // ---------------------------------------------------------------------------
185
136
 
186
- /**
187
- * Fetch confirmed transaction signatures for an address using getSignaturesForAddress.
188
- */
189
- async function fetchTxSignatures(rpcUrl, address, limit) {
190
- const body = {
191
- jsonrpc: '2.0',
192
- id: 1,
193
- method: 'getSignaturesForAddress',
194
- params: [
195
- address,
196
- { limit },
197
- ],
198
- };
199
- return httpPost(rpcUrl, '/', body);
200
- }
137
+ function parseTransaction(txResult) {
138
+ const blockTime = txResult.blockTime;
139
+ const slot = txResult.slot;
140
+ const status = txResult.status || 'confirmed';
201
141
 
202
- /**
203
- * Fetch a specific confirmed transaction by signature.
204
- */
205
- async function fetchTx(rpcUrl, signature) {
206
- const body = {
207
- jsonrpc: '2.0',
208
- id: 1,
209
- method: 'getTransaction',
210
- params: [
211
- signature,
212
- { encoding: 'jsonParsed', maxSupportedTransactionVersion: 0 },
213
- ],
214
- };
215
- return httpPost(rpcUrl, '/', body);
216
- }
217
-
218
- /**
219
- * Parse a transaction result into a normalized display object.
220
- */
221
- function parseTransaction(txResult, sigInfo) {
222
- const blockTime = sigInfo.blockTime || txResult.blockTime;
223
- const slot = sigInfo.slot;
224
- const status = sigInfo.err ? 'failed' : (sigInfo.confirmationStatus || 'confirmed');
225
-
226
- let txType = 'unknown';
142
+ let txType = txResult.tx_type || txResult.type || 'unknown';
227
143
  let amount = 0;
228
- let fee = txResult.meta?.fee || 0;
229
- let fromAddr = null;
144
+ let fee = txResult.fee || 0;
145
+ let fromAddr = txResult.signer || null;
230
146
  let toAddr = null;
231
- let memo = null;
147
+ let memo = txResult.memo || null;
232
148
 
233
- try {
234
- const msg = txResult.transaction?.message;
235
- if (msg) {
236
- // Parse instructions for transfer/stake types
237
- const instructions = msg.instructions || [];
238
- for (const ix of instructions) {
239
- const programId = ix.programId || (ix.parsed && ix.parsed.info && ix.parsed.type);
240
- // Native transfer
241
- if (ix.parsed && ix.parsed.type === 'transfer') {
242
- txType = 'transfer';
243
- const info = ix.parsed.info;
244
- fromAddr = info.source || info.from;
245
- toAddr = info.destination || info.to;
246
- amount = info.lamports || info.amount || 0;
247
- } else if (ix.parsed && ix.parsed.type === 'stake') {
248
- txType = 'stake';
249
- const info = ix.parsed.info;
250
- fromAddr = info.from || info.funder;
251
- toAddr = info.validator;
252
- amount = info.lamports || info.amount || 0;
253
- } else if (ix.parsed && ix.parsed.type === 'withdrawStake') {
254
- txType = 'unstake';
255
- const info = ix.parsed.info;
256
- toAddr = info.destination || info.withdrawer;
257
- amount = info.lamports || info.amount || 0;
258
- } else if (ix.parsed && ix.parsed.type === 'vote') {
259
- txType = 'vote';
260
- } else if (ix.parsed && ix.parsed.type === 'initialize') {
261
- txType = 'initialize';
262
- } else if (ix.parsed && ix.parsed.type === 'createAccount') {
263
- txType = 'create';
264
- } else if (ix.parsed && ix.parsed.type === 'approve') {
265
- txType = 'stake';
266
- const info = ix.parsed.info || {};
267
- fromAddr = info.from || info.owner;
268
- toAddr = info.stake;
269
- amount = info.amount || info.lamports || 0;
270
- } else if (ix.parsed && ix.parsed.type === 'delegate') {
271
- txType = 'stake';
272
- const info = ix.parsed.info || {};
273
- fromAddr = info.stake || info.from;
274
- toAddr = info.validator;
275
- amount = info.lamports || 0;
276
- } else if (ix.parsed && ix.parsed.type === 'withdraw') {
277
- txType = 'unstake';
278
- const info = ix.parsed.info || {};
279
- toAddr = info.destination;
280
- amount = info.lamports || 0;
281
- }
282
- // Check memo
283
- if (ix.memo) memo = ix.memo;
284
- }
285
-
286
- // Fallback: try legacy instructions if no parsed instructions
287
- if (!instructions.length || instructions.every(ix => !ix.parsed)) {
288
- for (const ix of instructions) {
289
- if (ix.data === 'AAAA' || ix.data === '2ugJ4ELK3wW9qNXH' || !ix.data) {
290
- txType = 'transfer';
291
- }
292
- }
293
- }
294
-
295
- // Compute fee
296
- if (txResult.meta) {
297
- fee = txResult.meta.fee || 0;
298
- if (txResult.meta.postBalances && txResult.meta.preBalances) {
299
- // Try to detect native transfer from balance changes
300
- for (let i = 0; i < txResult.meta.postBalances.length; i++) {
301
- const diff = txResult.meta.postBalances[i] - txResult.meta.preBalances[i];
302
- if (diff < 0) {
303
- amount = Math.abs(diff);
304
- if (!fromAddr) fromAddr = msg.accountKeys?.[i];
305
- } else if (diff > 0 && amount === 0) {
306
- if (!toAddr) toAddr = msg.accountKeys?.[i];
307
- }
308
- }
309
- }
310
- }
149
+ // Parse payload for details
150
+ if (txResult.payload) {
151
+ const payload = txResult.payload;
152
+ if (payload.amount !== undefined) {
153
+ amount = Number(payload.amount);
154
+ }
155
+ if (payload.recipient) {
156
+ toAddr = payload.recipient;
157
+ }
158
+ if (payload.validator) {
159
+ toAddr = payload.validator;
160
+ txType = 'stake';
161
+ }
162
+ if (payload.stake_account) {
163
+ fromAddr = payload.stake_account;
164
+ txType = 'unstake';
311
165
  }
312
- } catch (e) {
313
- // Parsing failed — use defaults
314
166
  }
315
167
 
316
168
  return {
317
- signature: sigInfo.signature || sigInfo.signatures?.[0],
169
+ signature: txResult.signature,
318
170
  slot,
319
171
  blockTime,
320
172
  status,
@@ -411,6 +263,14 @@ function displayJson(txs, meta) {
411
263
 
412
264
  async function main() {
413
265
  const opts = parseArgs();
266
+ // Shift args if this module was called via index.js (extra argument in argv)
267
+ // Detect by checking if first non-option arg after 'tx' is the command name
268
+ const args = process.argv.slice(2);
269
+ const txIdx = args.indexOf('tx');
270
+ const historyIdx = args.indexOf('history');
271
+ if (txIdx !== -1 && historyIdx !== -1) {
272
+ // Already parsed correctly above - no action needed
273
+ }
414
274
 
415
275
  if (opts.help) {
416
276
  console.log(`
@@ -449,46 +309,11 @@ ${C.bright}EXAMPLES${C.reset}
449
309
  }
450
310
 
451
311
  try {
452
- // Step 1: Get transaction signatures
453
- const sigsResult = await fetchTxSignatures(rpcUrl, opts.address, limit);
454
-
455
- if (sigsResult.error) {
456
- throw new Error(sigsResult.error.message || JSON.stringify(sigsResult.error));
457
- }
458
-
459
- const signatures = Array.isArray(sigsResult.result) ? sigsResult.result : [];
460
-
461
- if (signatures.length === 0) {
462
- if (!opts.json) {
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);
312
+ // Use SDK for real blockchain RPC calls
313
+ const client = createClient(rpcUrl);
314
+ const history = await client.getTransactionHistory(opts.address, limit);
315
+
316
+ const txs = (history.transactions || []).map(tx => parseTransaction(tx));
492
317
 
493
318
  if (opts.json) {
494
319
  displayJson(txs, { address: opts.address, rpc: rpcUrl, limit });
@@ -497,12 +322,25 @@ ${C.bright}EXAMPLES${C.reset}
497
322
  }
498
323
 
499
324
  } 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`);
325
+ if (opts.json) {
326
+ console.log(JSON.stringify({
327
+ error: err.message,
328
+ address: opts.address,
329
+ rpc: rpcUrl,
330
+ }, null, 2));
331
+ } else {
332
+ console.log(`\n ${C.red}✗ Failed to fetch transaction history:${C.reset} ${err.message}\n`);
333
+ console.log(` ${C.dim} Is your validator running? RPC: ${rpcUrl}${C.reset}`);
334
+ console.log(` ${C.dim} Set custom RPC: AETHER_RPC=https://your-rpc-url${C.reset}\n`);
503
335
  }
504
336
  process.exit(1);
505
337
  }
506
338
  }
507
339
 
508
- main();
340
+ // Export for CLI integration
341
+ module.exports = { txHistoryCommand: main };
342
+
343
+ // Run if called directly
344
+ if (require.main === module) {
345
+ main();
346
+ }
@@ -634,7 +634,13 @@ async function main() {
634
634
  console.log();
635
635
  }
636
636
 
637
- main().catch(err => {
638
- console.error(`\n${C.red}✗ Validator info failed:${C.reset} ${err.message}\n`);
639
- process.exit(1);
640
- });
637
+ // Export for module use
638
+ module.exports = { validatorInfo: main };
639
+
640
+ // Only run if called directly (not when required as module)
641
+ if (require.main === module) {
642
+ main().catch(err => {
643
+ console.error(`\n${C.red}✗ Validator info failed:${C.reset} ${err.message}\n`);
644
+ process.exit(1);
645
+ });
646
+ }
@@ -254,7 +254,7 @@ function startValidatorProcess({ type, path: binaryPath, inPath }, options) {
254
254
  if (options.testnet) {
255
255
  validatorArgs.push('--testnet');
256
256
  }
257
- validatorArgs.push('--tier', options.tier);
257
+ validatorArgs.push('--tier', options.tier.toLowerCase());
258
258
  validatorArgs.push('--rpc-addr', options.rpcAddr);
259
259
  validatorArgs.push('--p2p-addr', options.p2pAddr);
260
260
  if (options.identity) {
@@ -3,9 +3,15 @@
3
3
  *
4
4
  * Queries the validator's RPC endpoint and displays status information.
5
5
  * Shows slot height, peer count, block production, and epoch info.
6
+ *
7
+ * Uses @jellylegsai/aether-sdk for real blockchain RPC calls.
6
8
  */
7
9
 
8
- const http = require('http');
10
+ const path = require('path');
11
+
12
+ // Import SDK for real blockchain RPC calls
13
+ const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
14
+ const aether = require(sdkPath);
9
15
 
10
16
  // ANSI colors
11
17
  const colors = {
@@ -19,54 +25,10 @@ const colors = {
19
25
  };
20
26
 
21
27
  /**
22
- * Make an RPC call to the validator
28
+ * Create SDK client
23
29
  */
24
- function rpcCall(url, method, params = []) {
25
- return new Promise((resolve, reject) => {
26
- const urlObj = new URL(url);
27
-
28
- const postData = JSON.stringify({
29
- jsonrpc: '2.0',
30
- id: 1,
31
- method,
32
- params,
33
- });
34
-
35
- const options = {
36
- hostname: urlObj.hostname,
37
- port: urlObj.port || 8899,
38
- path: '/',
39
- method: 'POST',
40
- headers: {
41
- 'Content-Type': 'application/json',
42
- 'Content-Length': Buffer.byteLength(postData),
43
- },
44
- };
45
-
46
- const req = http.request(options, (res) => {
47
- let data = '';
48
- res.on('data', (chunk) => data += chunk);
49
- res.on('end', () => {
50
- try {
51
- const json = JSON.parse(data);
52
- if (json.error) {
53
- reject(new Error(json.error.message || JSON.stringify(json.error)));
54
- } else {
55
- resolve(json.result);
56
- }
57
- } catch (e) {
58
- reject(new Error(`Invalid JSON response: ${data}`));
59
- }
60
- });
61
- });
62
-
63
- req.on('error', (e) => {
64
- reject(new Error(`Connection failed: ${e.message}`));
65
- });
66
-
67
- req.write(postData);
68
- req.end();
69
- });
30
+ function createClient(rpcUrl) {
31
+ return new aether.AetherClient({ rpcUrl });
70
32
  }
71
33
 
72
34
  /**
@@ -181,24 +143,25 @@ async function validatorStatus() {
181
143
  let epochInfo = {};
182
144
  let blockProduction = {};
183
145
 
146
+ const client = createClient(options.rpcUrl);
147
+
184
148
  try {
185
- // Make parallel RPC calls
186
- const [slot, blockHeight, transactionCount, epochInfoResult, blockProdResult] = await Promise.all([
187
- rpcCall(options.rpcUrl, 'getSlot').catch(e => ({ error: e.message })),
188
- rpcCall(options.rpcUrl, 'getBlockHeight').catch(e => ({ error: e.message })),
189
- rpcCall(options.rpcUrl, 'getTransactionCount').catch(e => ({ error: e.message })),
190
- rpcCall(options.rpcUrl, 'getEpochInfo').catch(e => ({})),
191
- options.details ? rpcCall(options.rpcUrl, 'getBlockProduction').catch(e => ({})) : Promise.resolve({}),
149
+ // Make parallel RPC calls using SDK
150
+ const [slotResult, blockHeightResult, epochInfoResult, peersResult] = await Promise.all([
151
+ client.getSlot().catch(e => ({ error: e.message })),
152
+ client.getBlockHeight().catch(e => ({ error: e.message })),
153
+ client.getEpochInfo().catch(e => ({})),
154
+ client.getClusterPeers().catch(e => ([])),
192
155
  ]);
193
156
 
194
- if (typeof slot === 'object' && slot.error) {
157
+ if (typeof slotResult === 'object' && slotResult.error) {
195
158
  if (options.json) {
196
- console.log(JSON.stringify({ error: slot.error }, null, 2));
159
+ console.log(JSON.stringify({ error: slotResult.error }, null, 2));
197
160
  process.exit(1);
198
161
  }
199
162
  console.log();
200
163
  console.log(` ${colors.red}❌ Cannot connect to validator${colors.reset}`);
201
- console.log(` ${colors.yellow}${slot.error}${colors.reset}`);
164
+ console.log(` ${colors.yellow}${slotResult.error}${colors.reset}`);
202
165
  console.log();
203
166
  console.log(` ${colors.bright}Start the validator first:${colors.reset}`);
204
167
  console.log(` ${colors.cyan}aether-cli validator start${colors.reset}`);
@@ -206,26 +169,22 @@ async function validatorStatus() {
206
169
  process.exit(1);
207
170
  }
208
171
 
209
- status.slot = typeof slot === 'number' ? slot : 0;
210
- status.blockHeight = typeof blockHeight === 'number' ? blockHeight : status.slot;
211
- status.transactionCount = typeof transactionCount === 'number' ? transactionCount : 0;
172
+ status.slot = typeof slotResult === 'number' ? slotResult : (slotResult.slot || 0);
173
+ status.blockHeight = typeof blockHeightResult === 'number' ? blockHeightResult : status.slot;
174
+ status.transactionCount = 0; // Transaction count not available via SDK
175
+ status.peerCount = Array.isArray(peersResult) ? peersResult.length : 0;
212
176
 
213
177
  if (epochInfoResult && typeof epochInfoResult === 'object') {
214
178
  epochInfo = epochInfoResult;
215
179
  status.epoch = epochInfo.epoch || 0;
216
- epochInfo.slotIndex = epochInfo.slotIndex || 0;
217
- epochInfo.slotsInEpoch = epochInfo.slotsInEpoch || 432000;
180
+ epochInfo.slotIndex = epochInfo.slotIndex || epochInfo.slot_index || 0;
181
+ epochInfo.slotsInEpoch = epochInfo.slotsInEpoch || epochInfo.slots_in_epoch || 432000;
218
182
  }
219
183
 
220
- if (blockProdResult && typeof blockProdResult === 'object') {
221
- blockProduction = blockProdResult;
222
- }
223
-
224
- // Get peer count
225
- try {
226
- status.peerCount = await rpcCall(options.rpcUrl, 'getPeerCount') || 0;
227
- } catch (e) {
228
- status.peerCount = 0;
184
+ if (options.details) {
185
+ try {
186
+ blockProduction = await client.getSlotProduction();
187
+ } catch { /* Block production not available */ }
229
188
  }
230
189
 
231
190
  if (options.json) {
@@ -260,7 +219,7 @@ async function validatorStatus() {
260
219
  }
261
220
 
262
221
  // Export for use as module
263
- module.exports = { validatorStatus, rpcCall };
222
+ module.exports = { validatorStatus };
264
223
 
265
224
  // Run if called directly
266
225
  if (require.main === module) {