aether-hub 1.2.7 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/account.js +280 -280
- package/commands/apy.js +480 -480
- package/commands/balance.js +276 -276
- package/commands/claim.js +43 -77
- package/commands/delegations.js +412 -462
- package/commands/emergency.js +607 -657
- package/commands/epoch.js +275 -275
- package/commands/fees.js +276 -276
- package/commands/info.js +495 -495
- package/commands/monitor.js +431 -431
- package/commands/multisig.js +685 -726
- package/commands/network.js +429 -429
- package/commands/ping.js +266 -266
- package/commands/rewards.js +479 -866
- package/commands/sdk-test.js +477 -477
- package/commands/sdk.js +537 -537
- package/commands/slot.js +155 -0
- package/commands/snapshot.js +509 -509
- package/commands/stake-positions.js +205 -220
- package/commands/stats.js +418 -418
- package/commands/status.js +326 -326
- package/commands/supply.js +3 -1
- package/commands/tps.js +238 -238
- package/commands/tx-history.js +52 -168
- package/commands/wallet.js +3 -0
- package/index.js +42 -43
- package/package.json +3 -1
- package/sdk/README.md +210 -0
- package/sdk/index.js +1013 -0
- package/sdk/package.json +33 -0
- package/sdk/rpc.js +108 -0
- package/sdk/test.js +85 -0
- package/theme.js +211 -0
package/commands/tx-history.js
CHANGED
|
@@ -131,141 +131,42 @@ function formatStatus(status) {
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
// ---------------------------------------------------------------------------
|
|
134
|
-
//
|
|
134
|
+
// Parse a transaction result into a normalized display object
|
|
135
135
|
// ---------------------------------------------------------------------------
|
|
136
136
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
}
|
|
137
|
+
function parseTransaction(txResult) {
|
|
138
|
+
const blockTime = txResult.blockTime;
|
|
139
|
+
const slot = txResult.slot;
|
|
140
|
+
const status = txResult.status || 'confirmed';
|
|
152
141
|
|
|
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';
|
|
142
|
+
let txType = txResult.tx_type || txResult.type || 'unknown';
|
|
178
143
|
let amount = 0;
|
|
179
|
-
let fee = txResult.
|
|
180
|
-
let fromAddr = null;
|
|
144
|
+
let fee = txResult.fee || 0;
|
|
145
|
+
let fromAddr = txResult.signer || null;
|
|
181
146
|
let toAddr = null;
|
|
182
|
-
let memo = null;
|
|
147
|
+
let memo = txResult.memo || null;
|
|
183
148
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
}
|
|
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';
|
|
262
165
|
}
|
|
263
|
-
} catch (e) {
|
|
264
|
-
// Parsing failed — use defaults
|
|
265
166
|
}
|
|
266
167
|
|
|
267
168
|
return {
|
|
268
|
-
signature:
|
|
169
|
+
signature: txResult.signature,
|
|
269
170
|
slot,
|
|
270
171
|
blockTime,
|
|
271
172
|
status,
|
|
@@ -362,6 +263,14 @@ function displayJson(txs, meta) {
|
|
|
362
263
|
|
|
363
264
|
async function main() {
|
|
364
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
|
+
}
|
|
365
274
|
|
|
366
275
|
if (opts.help) {
|
|
367
276
|
console.log(`
|
|
@@ -400,46 +309,11 @@ ${C.bright}EXAMPLES${C.reset}
|
|
|
400
309
|
}
|
|
401
310
|
|
|
402
311
|
try {
|
|
403
|
-
//
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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);
|
|
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));
|
|
443
317
|
|
|
444
318
|
if (opts.json) {
|
|
445
319
|
displayJson(txs, { address: opts.address, rpc: rpcUrl, limit });
|
|
@@ -448,14 +322,24 @@ ${C.bright}EXAMPLES${C.reset}
|
|
|
448
322
|
}
|
|
449
323
|
|
|
450
324
|
} catch (err) {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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`);
|
|
454
335
|
}
|
|
455
336
|
process.exit(1);
|
|
456
337
|
}
|
|
457
338
|
}
|
|
458
339
|
|
|
340
|
+
// Export for CLI integration
|
|
341
|
+
module.exports = { txHistoryCommand: main };
|
|
342
|
+
|
|
459
343
|
// Run if called directly
|
|
460
344
|
if (require.main === module) {
|
|
461
345
|
main();
|
package/commands/wallet.js
CHANGED
package/index.js
CHANGED
|
@@ -36,10 +36,15 @@ const { blockhashCommand } = require('./commands/blockhash');
|
|
|
36
36
|
const { sdkTestCommand } = require('./commands/sdk-test');
|
|
37
37
|
const { balanceCommand } = require('./commands/balance');
|
|
38
38
|
const { transferCommand } = require('./commands/transfer');
|
|
39
|
+
const { slotCommand } = require('./commands/slot');
|
|
39
40
|
const readline = require('readline');
|
|
40
41
|
|
|
41
42
|
// CLI version
|
|
42
|
-
const VERSION = '1.0
|
|
43
|
+
const VERSION = '1.3.0';
|
|
44
|
+
|
|
45
|
+
// Import shared theme
|
|
46
|
+
const theme = require('./theme');
|
|
47
|
+
const { C, BANNERS, ICONS } = theme;
|
|
43
48
|
|
|
44
49
|
// Parse args early to support flags on commands
|
|
45
50
|
function getCommandArgs() {
|
|
@@ -65,21 +70,17 @@ async function showMenu() {
|
|
|
65
70
|
|
|
66
71
|
const prompt = (q) => new Promise((res) => rl.question(q, res));
|
|
67
72
|
|
|
68
|
-
console.log(
|
|
69
|
-
TIER_COLORS.FULL + '\n ╔═══════════════════════════════════════════════╗\n' +
|
|
70
|
-
' ║ AETHER CHAIN — Validator Setup Wizard ║\n' +
|
|
71
|
-
' ╚═══════════════════════════════════════════════╝' + TIER_COLORS.reset + '\n'
|
|
72
|
-
);
|
|
73
|
+
console.log(BANNERS.compact + '\n');
|
|
73
74
|
|
|
74
75
|
console.log(' Welcome to AeTHer Chain. What would you like to do?\n');
|
|
75
|
-
console.log(
|
|
76
|
-
console.log(
|
|
77
|
-
console.log(
|
|
78
|
-
console.log(
|
|
79
|
-
console.log(
|
|
80
|
-
console.log(
|
|
81
|
-
console.log(
|
|
82
|
-
console.log(
|
|
76
|
+
console.log(` ${C.cyan}1)${C.reset} ${ICONS.active} ${C.bright}Doctor${C.reset} — Check if your system meets requirements`);
|
|
77
|
+
console.log(` ${C.cyan}2)${C.reset} 🚀 ${C.bright}Start${C.reset} — Begin validator onboarding (recommended)`);
|
|
78
|
+
console.log(` ${C.cyan}3)${C.reset} 📊 ${C.bright}Monitor${C.reset} — Watch live validator stats`);
|
|
79
|
+
console.log(` ${C.cyan}4)${C.reset} 📋 ${C.bright}Logs${C.reset} — Tail and colourise validator logs`);
|
|
80
|
+
console.log(` ${C.cyan}5)${C.reset} 📦 ${C.bright}SDK${C.reset} — Get SDK links and install tools`);
|
|
81
|
+
console.log(` ${C.cyan}6)${C.reset} 🌐 ${C.bright}Network${C.reset} — Aether network status (slot, peers, TPS)`);
|
|
82
|
+
console.log(` ${C.cyan}7)${C.reset} ❓ ${C.bright}Help${C.reset} — Show all commands\n`);
|
|
83
|
+
console.log(` ${C.dim}Type a number or command name. Press Ctrl+C to exit.\n`);
|
|
83
84
|
|
|
84
85
|
const VALID_CHOICES = ['1', '2', '3', '4', '5', '6', '7', 'doctor', 'init', 'monitor', 'logs', 'sdk', 'network', 'help'];
|
|
85
86
|
|
|
@@ -429,6 +430,12 @@ const COMMANDS = {
|
|
|
429
430
|
tpsCommand();
|
|
430
431
|
},
|
|
431
432
|
},
|
|
433
|
+
slot: {
|
|
434
|
+
description: 'Get current slot number — aether slot [--json] [--rpc <url>]',
|
|
435
|
+
handler: () => {
|
|
436
|
+
slotCommand();
|
|
437
|
+
},
|
|
438
|
+
},
|
|
432
439
|
claim: {
|
|
433
440
|
description: 'Claim accumulated staking rewards — aether claim --address <addr> [--json] [--dry-run]',
|
|
434
441
|
handler: () => {
|
|
@@ -456,37 +463,27 @@ const COMMANDS = {
|
|
|
456
463
|
* Display help message with ASCII art
|
|
457
464
|
*/
|
|
458
465
|
function showHelp() {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
██╔████╔██║██║███████╗███████╗██║██║ ██║██╔██╗ ██║
|
|
463
|
-
██║╚██╔╝██║██║╚════██║╚════██║██║██║ ██║██║╚██╗██║
|
|
464
|
-
██║ ╚═╝ ██║██║███████║███████║██║╚██████╔╝██║ ╚████║
|
|
465
|
-
╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝
|
|
466
|
-
|
|
467
|
-
Validator CLI v${VERSION}
|
|
468
|
-
`.trim();
|
|
469
|
-
|
|
470
|
-
console.log(header);
|
|
471
|
-
console.log('\nUsage: aether-cli <command> [options]\n');
|
|
466
|
+
console.log(BANNERS.main);
|
|
467
|
+
console.log(`${C.bright}Validator CLI v${VERSION}${C.reset}\n`);
|
|
468
|
+
console.log(`Usage: aether <command> [options]\n`);
|
|
472
469
|
console.log('Commands:');
|
|
473
470
|
Object.entries(COMMANDS).forEach(([cmd, info]) => {
|
|
474
|
-
console.log(` ${cmd.padEnd(18)} ${info.description}`);
|
|
471
|
+
console.log(` ${C.cyan}${cmd.padEnd(18)}${C.reset} ${info.description}`);
|
|
475
472
|
});
|
|
476
473
|
console.log('\nExamples:');
|
|
477
|
-
console.log(
|
|
478
|
-
console.log(
|
|
479
|
-
console.log(
|
|
480
|
-
console.log(
|
|
481
|
-
console.log(
|
|
482
|
-
console.log(
|
|
483
|
-
console.log(
|
|
484
|
-
console.log(
|
|
485
|
-
console.log(
|
|
486
|
-
console.log(
|
|
487
|
-
console.log(
|
|
488
|
-
console.log(
|
|
489
|
-
console.log(
|
|
474
|
+
console.log(` ${C.cyan}aether doctor${C.reset} # Check system requirements`);
|
|
475
|
+
console.log(` ${C.cyan}aether init${C.reset} # Start onboarding wizard`);
|
|
476
|
+
console.log(` ${C.cyan}aether monitor${C.reset} # Real-time validator dashboard`);
|
|
477
|
+
console.log(` ${C.cyan}aether validator start${C.reset} # Start validator node`);
|
|
478
|
+
console.log(` ${C.cyan}aether validator status${C.reset} # Check validator status`);
|
|
479
|
+
console.log(` ${C.cyan}aether wallet balance${C.reset} # Query AETH balance`);
|
|
480
|
+
console.log(` ${C.cyan}aether network${C.reset} # Network status, peers, slot info`);
|
|
481
|
+
console.log(` ${C.cyan}aether network --peers${C.reset} # Detailed peer list`);
|
|
482
|
+
console.log(` ${C.cyan}aether tx history${C.reset} # Show transaction history`);
|
|
483
|
+
console.log(` ${C.cyan}aether price${C.reset} # AETH/USD price check`);
|
|
484
|
+
console.log(` ${C.cyan}aether --version${C.reset} # Show version`);
|
|
485
|
+
console.log(`\n${C.dim}Documentation: https://github.com/jelly-legs-ai/Jelly-legs-unsteady-workshop${C.reset}`);
|
|
486
|
+
console.log(`${C.dim}Spec: docs/MINING_VALIDATOR_TOOLS.md\n`);
|
|
490
487
|
}
|
|
491
488
|
|
|
492
489
|
/**
|
|
@@ -532,5 +529,7 @@ function main() {
|
|
|
532
529
|
}
|
|
533
530
|
}
|
|
534
531
|
|
|
535
|
-
// Run CLI
|
|
536
|
-
main
|
|
532
|
+
// Run CLI only if executed directly
|
|
533
|
+
if (require.main === module) {
|
|
534
|
+
main();
|
|
535
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aether-hub",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "AeTHer Validator CLI — tiered validators (Full/Lite/Observer), system checks, onboarding, and node management",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"index.js",
|
|
12
|
+
"theme.js",
|
|
12
13
|
"commands/",
|
|
14
|
+
"sdk/",
|
|
13
15
|
"test/",
|
|
14
16
|
"README.md",
|
|
15
17
|
"LICENSE"
|
package/sdk/README.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# @jellylegsai/aether-sdk
|
|
2
|
+
|
|
3
|
+
Official Aether Blockchain SDK for Node.js. Every function makes **REAL HTTP RPC calls** to the Aether blockchain at `http://127.0.0.1:8899`. No stubs, no mocks.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @jellylegsai/aether-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or use locally from the `sdk/` folder in this repo:
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
const aether = require('./sdk');
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
const aether = require('@jellylegsai/aether-sdk');
|
|
21
|
+
|
|
22
|
+
// Get current slot
|
|
23
|
+
const slot = await aether.getSlot();
|
|
24
|
+
console.log('Current slot:', slot);
|
|
25
|
+
|
|
26
|
+
// Get account balance
|
|
27
|
+
const balance = await aether.getBalance('ATH...');
|
|
28
|
+
console.log('Balance:', balance);
|
|
29
|
+
|
|
30
|
+
// Use custom RPC endpoint
|
|
31
|
+
const client = new aether.AetherClient({ rpcUrl: 'http://127.0.0.1:8899' });
|
|
32
|
+
const epoch = await client.getEpochInfo();
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## SDK Architecture
|
|
36
|
+
|
|
37
|
+
- **`AetherClient`** — Full-featured blockchain client class. All methods make real HTTP calls.
|
|
38
|
+
- **Convenience functions** — Top-level exports for one-off queries using the default RPC.
|
|
39
|
+
- **Low-level RPC helpers** — `rpcGet` / `rpcPost` for custom requests.
|
|
40
|
+
|
|
41
|
+
Default RPC: `http://127.0.0.1:8899` (override via `AETHER_RPC` env or constructor option).
|
|
42
|
+
|
|
43
|
+
## AetherClient
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
const aether = require('@jellylegsai/aether-sdk');
|
|
47
|
+
|
|
48
|
+
const client = new aether.AetherClient({
|
|
49
|
+
rpcUrl: 'http://127.0.0.1:8899', // default
|
|
50
|
+
timeoutMs: 10000, // default: 10s
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## API Reference
|
|
55
|
+
|
|
56
|
+
### Chain Queries
|
|
57
|
+
|
|
58
|
+
| Method | Returns | RPC Endpoint |
|
|
59
|
+
|--------|---------|--------------|
|
|
60
|
+
| `getSlot()` | `number` — current slot | `GET /v1/slot` |
|
|
61
|
+
| `getBlockHeight()` | `number` — block height | `GET /v1/blockheight` |
|
|
62
|
+
| `getEpochInfo()` | `object` — epoch + slot info | `GET /v1/epoch` |
|
|
63
|
+
| `getAccountInfo(address)` | `object` — lamports, owner, data | `GET /v1/account/:address` |
|
|
64
|
+
| `getBalance(address)` | `number` — lamports | `GET /v1/account/:address` |
|
|
65
|
+
| `getTransaction(signature)` | `object` — tx details | `GET /v1/transaction/:sig` |
|
|
66
|
+
| `getRecentTransactions(address, limit?)` | `array` — recent txs | `GET /v1/transactions/:address` |
|
|
67
|
+
| `getTokenAccounts(address)` | `array` — SPL token accounts | `GET /v1/tokens/:address` |
|
|
68
|
+
| `getStakeAccounts(address)` | `array` — stake accounts | `GET /v1/stake-accounts/:address` |
|
|
69
|
+
| `getStakePositions(address)` | `array` — delegations | `GET /v1/stake/:address` |
|
|
70
|
+
| `getRewards(address)` | `object` — staking rewards | `GET /v1/rewards/:address` |
|
|
71
|
+
| `getValidators()` | `array` — validator list | `GET /v1/validators` |
|
|
72
|
+
| `getValidatorAPY(validatorAddr)` | `object` — APY data | `GET /v1/validator/:addr/apy` |
|
|
73
|
+
| `getTPS()` | `number` — TPS | `GET /v1/tps` |
|
|
74
|
+
| `getSupply()` | `object` — total/circulating/non-circulating | `GET /v1/supply` |
|
|
75
|
+
| `getFees()` | `object` — fee estimates | `GET /v1/fees` |
|
|
76
|
+
| `getSlotProduction()` | `object` — slot production | `POST /v1/slot_production` |
|
|
77
|
+
| `getClusterPeers()` | `array` — peer list | `GET /v1/peers` |
|
|
78
|
+
| `getHealth()` | `string` — "ok" if healthy | `GET /v1/health` |
|
|
79
|
+
| `getVersion()` | `object` — node version | `GET /v1/version` |
|
|
80
|
+
|
|
81
|
+
### Blockhash (required for signing)
|
|
82
|
+
|
|
83
|
+
| Method | Returns | RPC Endpoint |
|
|
84
|
+
|--------|---------|--------------|
|
|
85
|
+
| `getRecentBlockhash()` | `{ blockhash, lastValidBlockHeight }` | `GET /v1/recent-blockhash` |
|
|
86
|
+
|
|
87
|
+
### Transaction Submission
|
|
88
|
+
|
|
89
|
+
| Method | Returns | RPC Endpoint |
|
|
90
|
+
|--------|---------|--------------|
|
|
91
|
+
| `sendTransaction(tx)` | `object` — receipt | `POST /v1/transaction` |
|
|
92
|
+
|
|
93
|
+
### Transaction Builders (build + submit)
|
|
94
|
+
|
|
95
|
+
| Method | Description |
|
|
96
|
+
|--------|-------------|
|
|
97
|
+
| `transfer({ from, to, amount, nonce, signFn })` | Build + send a Transfer TX |
|
|
98
|
+
| `stake({ staker, validator, amount, signFn })` | Build + send a Stake TX |
|
|
99
|
+
| `unstake({ stakeAccount, amount, signFn })` | Build + send an Unstake TX |
|
|
100
|
+
| `claimRewards({ stakeAccount, signFn })` | Build + send a ClaimRewards TX |
|
|
101
|
+
| `createNFT({ creator, metadataUrl, royalties, signFn })` | Build + send a CreateNFT TX |
|
|
102
|
+
| `transferNFT({ from, nftId, to, signFn })` | Build + send a TransferNFT TX |
|
|
103
|
+
| `updateMetadata({ creator, nftId, metadataUrl, signFn })` | Build + send an UpdateMetadata TX |
|
|
104
|
+
|
|
105
|
+
Each builder fetches a fresh `blockhash` + current `slot` from the chain, then submits the signed transaction via `sendTransaction()`. All steps are real RPC calls.
|
|
106
|
+
|
|
107
|
+
## Convenience Functions
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
// All of these use the default RPC (http://127.0.0.1:8899)
|
|
111
|
+
const slot = await aether.getSlot();
|
|
112
|
+
const balance = await aether.getBalance('ATH...');
|
|
113
|
+
const epoch = await aether.getEpoch();
|
|
114
|
+
const tps = await aether.getTPS();
|
|
115
|
+
const supply = await aether.getSupply();
|
|
116
|
+
const fees = await aether.getFees();
|
|
117
|
+
const validators = await aether.getValidators();
|
|
118
|
+
const peers = await aether.getPeers();
|
|
119
|
+
const health = await aether.getHealth();
|
|
120
|
+
const { blockhash } = await aether.getRecentBlockhash();
|
|
121
|
+
const tx = await aether.getTransaction('sig...');
|
|
122
|
+
const txs = await aether.getRecentTransactions('ATH...', 20);
|
|
123
|
+
const stakePos = await aether.getStakePositions('ATH...');
|
|
124
|
+
const rewards = await aether.getRewards('ATH...');
|
|
125
|
+
const tokenAccts = await aether.getTokenAccounts('ATH...');
|
|
126
|
+
const stakeAccts = await aether.getStakeAccounts('ATH...');
|
|
127
|
+
const apy = await aether.getValidatorAPY('validatorAddr');
|
|
128
|
+
const pingResult = await aether.ping(); // { ok, latency, rpc }
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Utilities
|
|
132
|
+
|
|
133
|
+
| Function | Description |
|
|
134
|
+
|----------|-------------|
|
|
135
|
+
| `createClient(options)` | Factory: create an `AetherClient` instance |
|
|
136
|
+
| `ping(rpcUrl?)` | Ping RPC, returns `{ ok, latency, rpc }` |
|
|
137
|
+
| `rpcGet(rpcUrl, path, timeout?)` | Low-level GET |
|
|
138
|
+
| `rpcPost(rpcUrl, path, body, timeout?)` | Low-level POST |
|
|
139
|
+
| `DEFAULT_RPC_URL` | `"http://127.0.0.1:8899"` |
|
|
140
|
+
| `DEFAULT_TIMEOUT_MS` | `10000` |
|
|
141
|
+
|
|
142
|
+
## CLI Integration
|
|
143
|
+
|
|
144
|
+
The SDK is wired into these CLI commands in `aether-cli`:
|
|
145
|
+
|
|
146
|
+
| CLI Command | SDK Method Used |
|
|
147
|
+
|-------------|----------------|
|
|
148
|
+
| `aether status` | `getEpochInfo`, `getSupply`, `getSlot`, `getBlockHeight`, `getClusterPeers`, `getVersion`, `getStakePositions`, `getRewards` |
|
|
149
|
+
| `aether network` | `getSlot`, `getBlockHeight`, `getClusterPeers`, `getValidators`, `getEpochInfo`, `getSupply`, `getTPS` |
|
|
150
|
+
| `aether blockhash` | `getRecentBlockhash` |
|
|
151
|
+
| `aether fees` | `getFees`, `getRecentBlockhash` |
|
|
152
|
+
| `aether tps` | `getTPS`, `getSlot` |
|
|
153
|
+
| `aether account` | `getAccountInfo`, `getBalance`, `getRecentTransactions`, `getStakePositions`, `getTokenAccounts`, `getStakeAccounts` |
|
|
154
|
+
|
|
155
|
+
## Configuration
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# Environment variable
|
|
159
|
+
export AETHER_RPC=http://127.0.0.1:8899
|
|
160
|
+
|
|
161
|
+
# Or per-call
|
|
162
|
+
const client = new aether.AetherClient({ rpcUrl: 'http://custom:8899' });
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Example: Full Dashboard
|
|
166
|
+
|
|
167
|
+
```javascript
|
|
168
|
+
const aether = require('@jellylegsai/aether-sdk');
|
|
169
|
+
|
|
170
|
+
async function dashboard() {
|
|
171
|
+
const [slot, blockHeight, tps, supply, { blockhash }] = await Promise.all([
|
|
172
|
+
aether.getSlot(),
|
|
173
|
+
aether.getBlockHeight(),
|
|
174
|
+
aether.getTPS(),
|
|
175
|
+
aether.getSupply(),
|
|
176
|
+
aether.getRecentBlockhash(),
|
|
177
|
+
]);
|
|
178
|
+
|
|
179
|
+
console.log('=== Aether Network Dashboard ===');
|
|
180
|
+
console.log(`Slot: ${slot}`);
|
|
181
|
+
console.log(`Block: ${blockHeight}`);
|
|
182
|
+
console.log(`TPS: ${tps}`);
|
|
183
|
+
console.log(`Total Supply: ${supply.total}`);
|
|
184
|
+
console.log(`Blockhash: ${blockhash}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
dashboard();
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Example: Check Staking Rewards
|
|
191
|
+
|
|
192
|
+
```javascript
|
|
193
|
+
const aether = require('@jellylegsai/aether-sdk');
|
|
194
|
+
|
|
195
|
+
async function checkRewards(walletAddress) {
|
|
196
|
+
const [stakePositions, rewards] = await Promise.all([
|
|
197
|
+
aether.getStakePositions(walletAddress),
|
|
198
|
+
aether.getRewards(walletAddress),
|
|
199
|
+
]);
|
|
200
|
+
|
|
201
|
+
console.log(`Stake positions: ${stakePositions.length}`);
|
|
202
|
+
console.log(`Total rewards: ${rewards.total || rewards.amount} lamports`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
checkRewards('ATH...');
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## License
|
|
209
|
+
|
|
210
|
+
MIT © Jelly-legs AI Team
|