@jellylegsai/aether-cli 1.8.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.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +110 -0
  3. package/aether-cli-1.0.0.tgz +0 -0
  4. package/aether-cli-1.8.0.tgz +0 -0
  5. package/aether-hub-1.0.5.tgz +0 -0
  6. package/aether-hub-1.1.8.tgz +0 -0
  7. package/aether-hub-1.2.1.tgz +0 -0
  8. package/commands/account.js +280 -0
  9. package/commands/apy.js +499 -0
  10. package/commands/balance.js +241 -0
  11. package/commands/blockhash.js +181 -0
  12. package/commands/broadcast.js +387 -0
  13. package/commands/claim.js +490 -0
  14. package/commands/config.js +851 -0
  15. package/commands/delegations.js +582 -0
  16. package/commands/doctor.js +769 -0
  17. package/commands/emergency.js +667 -0
  18. package/commands/epoch.js +275 -0
  19. package/commands/fees.js +276 -0
  20. package/commands/index.js +78 -0
  21. package/commands/info.js +495 -0
  22. package/commands/init.js +816 -0
  23. package/commands/install.js +666 -0
  24. package/commands/kyc.js +272 -0
  25. package/commands/logs.js +315 -0
  26. package/commands/monitor.js +431 -0
  27. package/commands/multisig.js +701 -0
  28. package/commands/network.js +429 -0
  29. package/commands/nft.js +857 -0
  30. package/commands/ping.js +266 -0
  31. package/commands/price.js +253 -0
  32. package/commands/rewards.js +931 -0
  33. package/commands/sdk-test.js +477 -0
  34. package/commands/sdk.js +656 -0
  35. package/commands/slot.js +155 -0
  36. package/commands/snapshot.js +470 -0
  37. package/commands/stake-info.js +139 -0
  38. package/commands/stake-positions.js +205 -0
  39. package/commands/stake.js +516 -0
  40. package/commands/stats.js +396 -0
  41. package/commands/status.js +327 -0
  42. package/commands/supply.js +391 -0
  43. package/commands/tps.js +238 -0
  44. package/commands/transfer.js +495 -0
  45. package/commands/tx-history.js +346 -0
  46. package/commands/unstake.js +597 -0
  47. package/commands/validator-info.js +657 -0
  48. package/commands/validator-register.js +593 -0
  49. package/commands/validator-start.js +323 -0
  50. package/commands/validator-status.js +227 -0
  51. package/commands/validators.js +626 -0
  52. package/commands/wallet.js +1570 -0
  53. package/index.js +593 -0
  54. package/lib/errors.js +398 -0
  55. package/package.json +76 -0
  56. package/sdk/README.md +210 -0
  57. package/sdk/index.js +1639 -0
  58. package/sdk/package.json +34 -0
  59. package/sdk/rpc.js +254 -0
  60. package/sdk/test.js +85 -0
  61. package/test/doctor.test.js +76 -0
  62. package/validator-identity.json +4 -0
package/lib/errors.js ADDED
@@ -0,0 +1,398 @@
1
+ /**
2
+ * aether-cli lib/errors.js
3
+ *
4
+ * Centralized error handling for production-grade CLI experience.
5
+ * Provides consistent error formatting, retry logic, and user-friendly messages.
6
+ */
7
+
8
+ // ANSI colours
9
+ const C = {
10
+ reset: '\x1b[0m',
11
+ bright: '\x1b[1m',
12
+ dim: '\x1b[2m',
13
+ red: '\x1b[31m',
14
+ green: '\x1b[32m',
15
+ yellow: '\x1b[33m',
16
+ cyan: '\x1b[36m',
17
+ magenta: '\x1b[35m',
18
+ };
19
+
20
+ /**
21
+ * Error categories for user-friendly messaging
22
+ */
23
+ const ERROR_CATEGORIES = {
24
+ NETWORK: 'network',
25
+ RPC: 'rpc',
26
+ VALIDATION: 'validation',
27
+ CONFIG: 'config',
28
+ WALLET: 'wallet',
29
+ PERMISSION: 'permission',
30
+ TIMEOUT: 'timeout',
31
+ UNKNOWN: 'unknown',
32
+ };
33
+
34
+ /**
35
+ * User-friendly error messages by category
36
+ */
37
+ const ERROR_MESSAGES = {
38
+ [ERROR_CATEGORIES.NETWORK]: {
39
+ title: 'Network Error',
40
+ description: 'Unable to reach the Aether network.',
41
+ suggestions: [
42
+ 'Check your internet connection',
43
+ 'Verify the RPC endpoint is accessible',
44
+ 'Try using --rpc to specify a different endpoint',
45
+ ],
46
+ },
47
+ [ERROR_CATEGORIES.RPC]: {
48
+ title: 'RPC Error',
49
+ description: 'The Aether node returned an error.',
50
+ suggestions: [
51
+ 'The node may be syncing - try again in a few moments',
52
+ 'Check if the RPC endpoint is correct',
53
+ 'Try a different RPC endpoint with --rpc <url>',
54
+ ],
55
+ },
56
+ [ERROR_CATEGORIES.VALIDATION]: {
57
+ title: 'Validation Error',
58
+ description: 'Invalid input provided.',
59
+ suggestions: [
60
+ 'Check the command syntax with --help',
61
+ 'Verify addresses are valid base58 format',
62
+ 'Ensure amounts are positive numbers',
63
+ ],
64
+ },
65
+ [ERROR_CATEGORIES.CONFIG]: {
66
+ title: 'Configuration Error',
67
+ description: 'CLI configuration issue detected.',
68
+ suggestions: [
69
+ 'Run "aether config init" to create default config',
70
+ 'Check ~/.aether/config.json exists and is valid JSON',
71
+ 'Use "aether config validate" to check settings',
72
+ ],
73
+ },
74
+ [ERROR_CATEGORIES.WALLET]: {
75
+ title: 'Wallet Error',
76
+ description: 'Unable to access wallet.',
77
+ suggestions: [
78
+ 'Ensure you have created a wallet: aether wallet create',
79
+ 'Check the wallet address is correct',
80
+ 'Verify wallet files exist in ~/.aether/wallets/',
81
+ ],
82
+ },
83
+ [ERROR_CATEGORIES.PERMISSION]: {
84
+ title: 'Permission Error',
85
+ description: 'Insufficient permissions.',
86
+ suggestions: [
87
+ 'Check file permissions in ~/.aether/',
88
+ 'Ensure you own the wallet files',
89
+ 'Try running with appropriate privileges',
90
+ ],
91
+ },
92
+ [ERROR_CATEGORIES.TIMEOUT]: {
93
+ title: 'Request Timeout',
94
+ description: 'The request took too long to complete.',
95
+ suggestions: [
96
+ 'The network may be congested - try again',
97
+ 'Increase timeout with AETHER_RPC_TIMEOUT env var',
98
+ 'Check if the RPC endpoint is responsive',
99
+ ],
100
+ },
101
+ [ERROR_CATEGORIES.UNKNOWN]: {
102
+ title: 'Unexpected Error',
103
+ description: 'An unexpected error occurred.',
104
+ suggestions: [
105
+ 'Try the command again',
106
+ 'Check logs with --verbose flag',
107
+ 'Report this issue with the error details',
108
+ ],
109
+ },
110
+ };
111
+
112
+ /**
113
+ * Categorize an error based on its properties
114
+ */
115
+ function categorizeError(error) {
116
+ if (!error) return ERROR_CATEGORIES.UNKNOWN;
117
+
118
+ const message = (error.message || '').toLowerCase();
119
+ const code = error.code || '';
120
+
121
+ // Network errors
122
+ if (message.includes('enetunreach') ||
123
+ message.includes('econnrefused') ||
124
+ message.includes('econnreset') ||
125
+ message.includes('socket') ||
126
+ message.includes('network') ||
127
+ code === 'ECONNREFUSED' ||
128
+ code === 'ENOTFOUND' ||
129
+ code === 'ENETUNREACH') {
130
+ return ERROR_CATEGORIES.NETWORK;
131
+ }
132
+
133
+ // RPC errors
134
+ if (message.includes('rpc') ||
135
+ message.includes('json-rpc') ||
136
+ message.includes('-32000') ||
137
+ message.includes('method not found') ||
138
+ message.includes('invalid params') ||
139
+ error.statusCode >= 500) {
140
+ return ERROR_CATEGORIES.RPC;
141
+ }
142
+
143
+ // Validation errors
144
+ if (message.includes('invalid') ||
145
+ message.includes('required') ||
146
+ message.includes('must be') ||
147
+ message.includes('cannot be') ||
148
+ message.includes('bad request') ||
149
+ error.statusCode === 400) {
150
+ return ERROR_CATEGORIES.VALIDATION;
151
+ }
152
+
153
+ // Config errors
154
+ if (message.includes('config') ||
155
+ message.includes('enoent') && message.includes('config')) {
156
+ return ERROR_CATEGORIES.CONFIG;
157
+ }
158
+
159
+ // Wallet errors
160
+ if (message.includes('wallet') ||
161
+ message.includes('keypair') ||
162
+ message.includes('mnemonic') ||
163
+ message.includes('signature')) {
164
+ return ERROR_CATEGORIES.WALLET;
165
+ }
166
+
167
+ // Permission errors
168
+ if (message.includes('eperm') ||
169
+ message.includes('eacces') ||
170
+ code === 'EACCES' ||
171
+ code === 'EPERM') {
172
+ return ERROR_CATEGORIES.PERMISSION;
173
+ }
174
+
175
+ // Timeout errors
176
+ if (message.includes('timeout') ||
177
+ message.includes('etimedout') ||
178
+ code === 'ETIMEDOUT') {
179
+ return ERROR_CATEGORIES.TIMEOUT;
180
+ }
181
+
182
+ return ERROR_CATEGORIES.UNKNOWN;
183
+ }
184
+
185
+ /**
186
+ * Format error for display
187
+ */
188
+ function formatError(error, options = {}) {
189
+ const { verbose = false, exit = true } = options;
190
+ const category = categorizeError(error);
191
+ const template = ERROR_MESSAGES[category];
192
+
193
+ let output = '\n';
194
+ output += `${C.red}${C.bright}✖ ${template.title}${C.reset}\n`;
195
+ output += `${C.dim}${template.description}${C.reset}\n\n`;
196
+
197
+ if (error.message) {
198
+ output += `${C.yellow}Error: ${error.message}${C.reset}\n`;
199
+ }
200
+
201
+ if (verbose && error.stack) {
202
+ output += `\n${C.dim}Stack trace:${C.reset}\n`;
203
+ output += `${C.dim}${error.stack}${C.reset}\n`;
204
+ }
205
+
206
+ output += `\n${C.bright}Suggestions:${C.reset}\n`;
207
+ template.suggestions.forEach(suggestion => {
208
+ output += ` ${C.cyan}•${C.reset} ${suggestion}\n`;
209
+ });
210
+
211
+ return output;
212
+ }
213
+
214
+ /**
215
+ * Display error and optionally exit
216
+ */
217
+ function displayError(error, options = {}) {
218
+ const { exit = true, exitCode = 1, verbose = process.env.AETHER_VERBOSE === '1' } = options;
219
+
220
+ console.error(formatError(error, { verbose }));
221
+
222
+ if (exit) {
223
+ process.exit(exitCode);
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Wrap an async function with error handling
229
+ */
230
+ function withErrorHandling(fn, options = {}) {
231
+ return async function(...args) {
232
+ try {
233
+ return await fn(...args);
234
+ } catch (error) {
235
+ displayError(error, options);
236
+ return null;
237
+ }
238
+ };
239
+ }
240
+
241
+ /**
242
+ * Retry an async operation with exponential backoff
243
+ */
244
+ async function withRetry(operation, options = {}) {
245
+ const {
246
+ maxRetries = 3,
247
+ initialDelay = 1000,
248
+ maxDelay = 30000,
249
+ backoffMultiplier = 2,
250
+ retryableErrors = null, // null = retry all
251
+ onRetry = null,
252
+ } = options;
253
+
254
+ let lastError;
255
+ let delay = initialDelay;
256
+
257
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
258
+ try {
259
+ return await operation();
260
+ } catch (error) {
261
+ lastError = error;
262
+
263
+ // Check if we should retry this error
264
+ if (retryableErrors && !retryableErrors.some(e => error instanceof e)) {
265
+ throw error;
266
+ }
267
+
268
+ // Don't retry on validation errors
269
+ const category = categorizeError(error);
270
+ if (category === ERROR_CATEGORIES.VALIDATION) {
271
+ throw error;
272
+ }
273
+
274
+ if (attempt < maxRetries) {
275
+ if (onRetry) {
276
+ onRetry(error, attempt + 1, maxRetries);
277
+ }
278
+
279
+ await sleep(delay);
280
+ delay = Math.min(delay * backoffMultiplier, maxDelay);
281
+ }
282
+ }
283
+ }
284
+
285
+ throw lastError;
286
+ }
287
+
288
+ /**
289
+ * Sleep utility
290
+ */
291
+ function sleep(ms) {
292
+ return new Promise(resolve => setTimeout(resolve, ms));
293
+ }
294
+
295
+ /**
296
+ * Create a RPC call wrapper with retry logic
297
+ */
298
+ function createRpcCaller(clientMethod, options = {}) {
299
+ return async function(...args) {
300
+ return withRetry(
301
+ () => clientMethod(...args),
302
+ {
303
+ maxRetries: 3,
304
+ initialDelay: 1000,
305
+ onRetry: (error, attempt, max) => {
306
+ console.log(
307
+ `${C.yellow}⚠ RPC call failed (attempt ${attempt}/${max}): ${error.message}${C.reset}`
308
+ );
309
+ },
310
+ ...options,
311
+ }
312
+ );
313
+ };
314
+ }
315
+
316
+ /**
317
+ * Validate required arguments
318
+ */
319
+ function validateRequired(args, requirements) {
320
+ const missing = [];
321
+
322
+ for (const requirement of requirements) {
323
+ if (requirement.validator ? !requirement.validator(args) : !args[requirement.name]) {
324
+ missing.push(requirement.displayName || requirement.name);
325
+ }
326
+ }
327
+
328
+ if (missing.length > 0) {
329
+ const error = new Error(`Missing required arguments: ${missing.join(', ')}`);
330
+ error.isValidationError = true;
331
+ throw error;
332
+ }
333
+ }
334
+
335
+ /**
336
+ * Validate address format (base58)
337
+ */
338
+ function validateAddress(address, fieldName = 'address') {
339
+ if (!address) {
340
+ throw new Error(`${fieldName} is required`);
341
+ }
342
+
343
+ // Base58 regex (simplified)
344
+ const base58Regex = /^[1-9A-HJ-NP-Za-km-z]+$/;
345
+ if (!base58Regex.test(address)) {
346
+ throw new Error(`Invalid ${fieldName} format: must be base58 encoded`);
347
+ }
348
+
349
+ // Typical Solana/Aether addresses are 32-44 chars
350
+ if (address.length < 32 || address.length > 44) {
351
+ throw new Error(`Invalid ${fieldName} length: expected 32-44 characters, got ${address.length}`);
352
+ }
353
+
354
+ return true;
355
+ }
356
+
357
+ /**
358
+ * Validate amount (positive number)
359
+ */
360
+ function validateAmount(amount, fieldName = 'amount') {
361
+ if (amount === undefined || amount === null || amount === '') {
362
+ throw new Error(`${fieldName} is required`);
363
+ }
364
+
365
+ const num = Number(amount);
366
+ if (isNaN(num) || !isFinite(num)) {
367
+ throw new Error(`Invalid ${fieldName}: must be a valid number`);
368
+ }
369
+
370
+ if (num <= 0) {
371
+ throw new Error(`Invalid ${fieldName}: must be greater than 0`);
372
+ }
373
+
374
+ return num;
375
+ }
376
+
377
+ module.exports = {
378
+ // Constants
379
+ ERROR_CATEGORIES,
380
+ ERROR_MESSAGES,
381
+
382
+ // Core functions
383
+ categorizeError,
384
+ formatError,
385
+ displayError,
386
+ withErrorHandling,
387
+ withRetry,
388
+ createRpcCaller,
389
+
390
+ // Validation
391
+ validateRequired,
392
+ validateAddress,
393
+ validateAmount,
394
+
395
+ // Utilities
396
+ sleep,
397
+ C,
398
+ };
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@jellylegsai/aether-cli",
3
+ "version": "1.8.0",
4
+ "description": "Aether CLI - Validator Onboarding, Staking, and Blockchain Operations",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "aether-cli": "./index.js",
8
+ "aether": "./index.js"
9
+ },
10
+ "scripts": {
11
+ "test": "node test.js && cd sdk && node test.js",
12
+ "lint": "echo 'No linting configured'",
13
+ "doctor": "node index.js doctor",
14
+ "init": "node index.js init",
15
+ "monitor": "node index.js monitor",
16
+ "logs": "node index.js logs",
17
+ "sdk": "node index.js sdk",
18
+ "sdk-test": "node index.js sdk-test",
19
+ "wallet": "node index.js wallet",
20
+ "stake": "node index.js stake",
21
+ "stake-positions": "node index.js stake-positions",
22
+ "unstake": "node index.js unstake",
23
+ "claim": "node index.js claim",
24
+ "delegations": "node index.js delegations",
25
+ "rewards": "node index.js rewards",
26
+ "validators": "node index.js validators",
27
+ "validator": "node index.js validator",
28
+ "validator-register": "node index.js validator-register",
29
+ "validator-start": "node index.js validator-start",
30
+ "validator-status": "node index.js validator-status",
31
+ "validator-info": "node index.js validator-info",
32
+ "multisig": "node index.js multisig",
33
+ "transfer": "node index.js transfer",
34
+ "balance": "node index.js balance",
35
+ "tx-history": "node index.js tx-history",
36
+ "tx": "node index.js tx-history",
37
+ "network": "node index.js network",
38
+ "epoch": "node index.js epoch",
39
+ "supply": "node index.js supply",
40
+ "status": "node index.js status",
41
+ "blockhash": "node index.js blockhash",
42
+ "tps": "node index.js tps",
43
+ "fees": "node index.js fees",
44
+ "apy": "node index.js apy",
45
+ "account": "node index.js account",
46
+ "price": "node index.js price",
47
+ "emergency": "node index.js emergency",
48
+ "snapshot": "node index.js snapshot",
49
+ "nft": "node index.js nft",
50
+ "prepare-publish": "npm pack && echo 'Package ready for publishing'",
51
+ "version-bump": "node -e \"const fs=require('fs');let p=JSON.parse(fs.readFileSync('package.json'));let v=p.version.split('.');v[2]=parseInt(v[2])+1;p.version=v.join('.');fs.writeFileSync('package.json',JSON.stringify(p,null,2));console.log('Version bumped to',p.version);\""
52
+ },
53
+ "keywords": [
54
+ "aether",
55
+ "blockchain",
56
+ "validator",
57
+ "staking",
58
+ "cryptocurrency",
59
+ "multi-signature",
60
+ "cli"
61
+ ],
62
+ "author": "Jelly-legs AI Team",
63
+ "license": "MIT",
64
+ "repository": {
65
+ "type": "git",
66
+ "url": "https://github.com/jelly-legs-ai/Jelly-legs-unsteady-workshop"
67
+ },
68
+ "engines": {
69
+ "node": ">=14.0.0"
70
+ },
71
+ "dependencies": {
72
+ "bip39": "^3.0.4",
73
+ "tweetnacl": "^1.0.3",
74
+ "bs58": "^5.0.0"
75
+ }
76
+ }
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