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.
@@ -131,141 +131,42 @@ function formatStatus(status) {
131
131
  }
132
132
 
133
133
  // ---------------------------------------------------------------------------
134
- // Core RPC calls
134
+ // Parse a transaction result into a normalized display object
135
135
  // ---------------------------------------------------------------------------
136
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
- }
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.meta?.fee || 0;
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
- 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
- }
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: sigInfo.signature || sigInfo.signatures?.[0],
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
- // 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);
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
- 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`);
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();
@@ -19,6 +19,9 @@ const bip39 = require('bip39');
19
19
  const nacl = require('tweetnacl');
20
20
  const bs58 = require('bs58').default;
21
21
 
22
+ // Import SDK for blockchain RPC calls
23
+ const aether = require('../sdk');
24
+
22
25
  // ANSI colours
23
26
  const C = {
24
27
  reset: '\x1b[0m',
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.5';
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(' ' + TIER_COLORS.FULL + '1)' + TIER_COLORS.reset + ' 🩺 Doctor — Check if your system meets requirements');
76
- console.log(' ' + TIER_COLORS.FULL + '2)' + TIER_COLORS.reset + ' 🚀 Start — Begin validator onboarding (recommended)');
77
- console.log(' ' + TIER_COLORS.FULL + '3)' + TIER_COLORS.reset + ' 📊 Monitor — Watch live validator stats');
78
- console.log(' ' + TIER_COLORS.FULL + '4)' + TIER_COLORS.reset + ' 📋 Logs — Tail and colourise validator logs');
79
- console.log(' ' + TIER_COLORS.FULL + '5)' + TIER_COLORS.reset + ' 📦 SDK — Get SDK links and install tools');
80
- console.log(' ' + TIER_COLORS.FULL + '6)' + TIER_COLORS.reset + ' 🌐 Network — Aether network status (slot, peers, TPS)');
81
- console.log(' ' + TIER_COLORS.FULL + '7)' + TIER_COLORS.reset + ' ❓ Help — Show all commands\n');
82
- console.log(' ' + TIER_COLORS.reset + ' Type a number or command name. Press Ctrl+C to exit.\n');
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
- const header = `
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(' aether-cli doctor # Check system requirements');
478
- console.log(' aether-cli init # Start onboarding wizard');
479
- console.log(' aether-cli monitor # Real-time validator dashboard');
480
- console.log(' aether-cli validator start # Start validator node');
481
- console.log(' aether-cli validator status # Check validator status');
482
- console.log(' aether-cli wallet balance # Query AETH balance');
483
- console.log(' aether-cli network # Network status, peers, slot info');
484
- console.log(' aether-cli network --peers # Detailed peer list');
485
- console.log(' aether-cli tx history # Show transaction history');
486
- console.log(' aether-cli price # AETH/USD price check');
487
- console.log(' aether-cli --version # Show version');
488
- console.log('\nDocumentation: https://github.com/jelly-legs-ai/Jelly-legs-unsteady-workshop');
489
- console.log('Spec: docs/MINING_VALIDATOR_TOOLS.md\n');
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.2.7",
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