aether-hub 1.2.5 → 1.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,10 +6,11 @@
6
6
  * Shows accumulated rewards, estimated APY, and claimable amounts.
7
7
  *
8
8
  * Usage:
9
- * aether rewards list --address <addr> List all rewards per stake account
10
- * aether rewards list --address <addr> --json JSON output for scripting
11
- * aether rewards claim --address <addr> --account <stakeAcct> [--json]
12
- * aether rewards summary --address <addr> One-line summary of total rewards
9
+ * aether rewards list --address <addr> List all rewards per stake account
10
+ * aether rewards list --address <addr> --json JSON output for scripting
11
+ * aether rewards claim --address <addr> --account <stakeAcct> [--json]
12
+ * aether rewards summary --address <addr> One-line summary of total rewards
13
+ * aether rewards compound --address <addr> [--account <stakeAcct>] [--json] Claim and auto-re-stake
13
14
  *
14
15
  * Requires AETHER_RPC env var or local node running (default: http://127.0.0.1:8899)
15
16
  */
@@ -607,6 +608,184 @@ async function rewardsClaim(args) {
607
608
  }
608
609
  }
609
610
 
611
+ // ---------------------------------------------------------------------------
612
+ // Rewards compound command — claim and auto-re-stake
613
+ // ---------------------------------------------------------------------------
614
+
615
+ async function rewardsCompound(args) {
616
+ const rpc = args.rpc || getDefaultRpc();
617
+ const isJson = args.json || false;
618
+ let address = args.address || null;
619
+ let stakeAccount = args.account || null;
620
+
621
+ const config = loadConfig();
622
+ const rl = createRl();
623
+
624
+ if (!address) {
625
+ const ans = await question(rl, `\n${C.cyan}Enter wallet address: ${C.reset}`);
626
+ address = ans.trim();
627
+ }
628
+
629
+ if (!address) {
630
+ console.log(`\n${C.red}✗ No address provided.${C.reset}\n`);
631
+ rl.close();
632
+ return;
633
+ }
634
+
635
+ // Load wallet for signing
636
+ const wallet = loadWallet(address);
637
+ if (!wallet) {
638
+ console.log(`\n${C.red}✗ Wallet not found locally: ${address}${C.reset}`);
639
+ console.log(` ${C.dim}Import it: aether wallet import${C.reset}\n`);
640
+ rl.close();
641
+ return;
642
+ }
643
+
644
+ // Fetch stake accounts
645
+ let stakeAccounts = await fetchWalletStakeAccounts(address);
646
+ if (stakeAccounts.length === 0) {
647
+ console.log(`\n${C.red}✗ No stake accounts found for this wallet.${C.reset}\n`);
648
+ rl.close();
649
+ return;
650
+ }
651
+
652
+ // If --account specified, filter to that one
653
+ if (stakeAccount) {
654
+ stakeAccounts = stakeAccounts.filter(sa => sa === stakeAccount);
655
+ if (stakeAccounts.length === 0) {
656
+ console.log(`\n${C.red}✗ Stake account not found: ${stakeAccount}${C.reset}\n`);
657
+ rl.close();
658
+ return;
659
+ }
660
+ }
661
+
662
+ console.log(`\n${C.bright}${C.cyan}╔══════════════════════════════════════════════════════════════╗${C.reset}`);
663
+ console.log(`${C.bright}${C.cyan}║ Compound Staking Rewards ║${C.reset}`);
664
+ console.log(`${C.bright}${C.cyan}╚══════════════════════════════════════════════════════════════╝${C.reset}\n`);
665
+ console.log(` ${C.dim}Wallet:${C.reset} ${C.bright}${address}${C.reset}`);
666
+ console.log(` ${C.dim}RPC:${C.reset} ${rpc}`);
667
+ console.log(` ${C.dim}Stake accounts to process:${C.reset} ${stakeAccounts.length}\n`);
668
+
669
+ // Ask for mnemonic upfront
670
+ console.log(`${C.yellow} ⚠ Compound requires your wallet passphrase to sign transactions.${C.reset}`);
671
+ const mnemonic = await question(rl, ` ${C.cyan}Enter your 12/24-word mnemonic:${C.reset} `);
672
+ console.log();
673
+
674
+ let keypair;
675
+ try {
676
+ if (!bip39.validateMnemonic(mnemonic)) {
677
+ throw new Error('Invalid BIP39 mnemonic');
678
+ }
679
+ keypair = deriveKeypair(mnemonic);
680
+ } catch (err) {
681
+ console.log(` ${C.red}✗ Failed to derive keypair: ${err.message}${C.reset}\n`);
682
+ rl.close();
683
+ return;
684
+ }
685
+
686
+ // Verify address matches
687
+ const derivedAddress = formatAddress(keypair.publicKey);
688
+ if (derivedAddress !== address) {
689
+ console.log(` ${C.red}✗ Passphrase mismatch.${C.reset}`);
690
+ console.log(` ${C.dim} Derived: ${derivedAddress}${C.reset}`);
691
+ console.log(` ${C.dim} Expected: ${address}${C.reset}`);
692
+ console.log(` ${C.dim}Check your passphrase and try again.${C.reset}\n`);
693
+ rl.close();
694
+ return;
695
+ }
696
+
697
+ const compoundResults = [];
698
+ let totalCompounded = BigInt(0);
699
+ let successCount = 0;
700
+
701
+ for (const sa of stakeAccounts) {
702
+ console.log(` ${C.dim}Processing stake account:${C.reset} ${sa.substring(0, 20)}...`);
703
+
704
+ try {
705
+ // Fetch rewards for this stake account
706
+ const rewardData = await fetchStakeRewards(rpc, sa);
707
+ if (rewardData.error) {
708
+ console.log(` ${C.red}✗ Failed to fetch rewards: ${rewardData.error}${C.reset}`);
709
+ compoundResults.push({ stake_account: sa, status: 'error', error: rewardData.error });
710
+ continue;
711
+ }
712
+
713
+ const estimatedRewards = BigInt(rewardData.estimatedRewards);
714
+ if (estimatedRewards === BigInt(0)) {
715
+ console.log(` ${C.yellow}⚠ No rewards to compound${C.reset}`);
716
+ compoundResults.push({ stake_account: sa, status: 'no_rewards', rewards: '0' });
717
+ continue;
718
+ }
719
+
720
+ console.log(` ${C.dim}Rewards to compound:${C.reset} ${rewardData.estimatedRewardsFormatted}`);
721
+ console.log(` ${C.dim}Validator:${C.reset} ${rewardData.validator || 'unknown'}`);
722
+
723
+ // Build compound transaction (ClaimRewards + Stake in one)
724
+ const tx = {
725
+ type: 'CompoundRewards',
726
+ from: address,
727
+ stake_account: sa,
728
+ lamports: estimatedRewards.toString(),
729
+ validator: rewardData.validator || null,
730
+ timestamp: Math.floor(Date.now() / 1000),
731
+ };
732
+
733
+ // Sign transaction
734
+ const txData = JSON.stringify(tx);
735
+ const txHash = crypto.createHash('sha256').update(txData).digest('hex');
736
+ const signature = nacl.hash(Buffer.from(txHash, 'hex'));
737
+ const signatureB58 = bs58.encode(signature.slice(0, 64));
738
+ tx.signature = signatureB58;
739
+
740
+ // Submit transaction
741
+ const result = await httpPost(rpc, '/v1/tx', tx);
742
+
743
+ if (result.success || result.txid || result.signature) {
744
+ console.log(` ${C.green}✓ Compounded successfully${C.reset}`);
745
+ console.log(` ${C.dim}TX: ${(result.txid || result.signature || signatureB58).substring(0, 20)}...${C.reset}`);
746
+ totalCompounded += estimatedRewards;
747
+ successCount++;
748
+ compoundResults.push({
749
+ stake_account: sa,
750
+ status: 'compounded',
751
+ rewards: estimatedRewards.toString(),
752
+ rewards_formatted: rewardData.estimatedRewardsFormatted,
753
+ tx: result.txid || result.signature || signatureB58,
754
+ });
755
+ } else {
756
+ console.log(` ${C.red}✗ Compound failed: ${result.error || JSON.stringify(result)}${C.reset}`);
757
+ compoundResults.push({ stake_account: sa, status: 'failed', error: result.error });
758
+ }
759
+ } catch (err) {
760
+ console.log(` ${C.red}✗ Error: ${err.message}${C.reset}`);
761
+ compoundResults.push({ stake_account: sa, status: 'error', error: err.message });
762
+ }
763
+
764
+ console.log();
765
+ }
766
+
767
+ rl.close();
768
+
769
+ // Summary
770
+ console.log(`${C.bright}${C.cyan}╔══════════════════════════════════════════════════════════════╗${C.reset}`);
771
+ console.log(`${C.bright}${C.cyan}║ Compound Summary ║${C.reset}`);
772
+ console.log(`${C.bright}${C.cyan}╚══════════════════════════════════════════════════════════════╝${C.reset}\n`);
773
+ console.log(` ${C.dim}Accounts processed:${C.reset} ${stakeAccounts.length}`);
774
+ console.log(` ${C.green}✓ Successful:${C.reset} ${successCount}`);
775
+ console.log(` ${C.dim}Total compounded:${C.reset} ${C.green}${formatAether(totalCompounded.toString())}${C.reset}\n`);
776
+
777
+ if (isJson) {
778
+ console.log(JSON.stringify({
779
+ address,
780
+ total_compounded_lamports: totalCompounded.toString(),
781
+ total_compounded_formatted: formatAether(totalCompounded.toString()),
782
+ accounts_processed: stakeAccounts.length,
783
+ successful: successCount,
784
+ results: compoundResults,
785
+ }, null, 2));
786
+ }
787
+ }
788
+
610
789
  // ---------------------------------------------------------------------------
611
790
  // Parse CLI args
612
791
  // ---------------------------------------------------------------------------
@@ -664,12 +843,16 @@ async function main() {
664
843
  case 'claim':
665
844
  await rewardsClaim(parsed);
666
845
  break;
846
+ case 'compound':
847
+ await rewardsCompound(parsed);
848
+ break;
667
849
  default:
668
850
  console.log(`\n${C.cyan}Usage:${C.reset}`);
669
851
  console.log(` aether rewards list --address <addr> List all staking rewards`);
670
852
  console.log(` aether rewards summary --address <addr> One-line rewards summary`);
671
853
  console.log(` aether rewards pending --address <addr> Show pending (unclaimed) rewards`);
672
854
  console.log(` aether rewards claim --address <addr> [--account <stakeAcct>] Claim rewards`);
855
+ console.log(` aether rewards compound --address <addr> [--account <stakeAcct>] Claim and re-stake rewards`);
673
856
  console.log();
674
857
  console.log(` ${C.dim}--json Output as JSON`);
675
858
  console.log(` --rpc <url> Use specific RPC endpoint${C.reset}\n`);
@@ -0,0 +1,477 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * aether-cli sdk-test
4
+ *
5
+ * Comprehensive SDK test suite - exercises all major SDK functions
6
+ * with REAL HTTP RPC calls to verify the SDK works end-to-end.
7
+ *
8
+ * Uses @jellylegsai/aether-sdk for all blockchain interactions.
9
+ * No stubs, no mocks - every function makes actual RPC calls.
10
+ *
11
+ * Usage:
12
+ * aether sdk-test Run full test suite
13
+ * aether sdk-test --rpc <url> Test against specific RPC endpoint
14
+ * aether sdk-test --quick Run only essential tests (slot, balance, health)
15
+ * aether sdk-test --json JSON output for CI/monitoring
16
+ *
17
+ * Default RPC: http://127.0.0.1:8899 (or AETHER_RPC env var)
18
+ */
19
+
20
+ const path = require('path');
21
+ const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
22
+ const aether = require(sdkPath);
23
+
24
+ // ANSI colors
25
+ const C = {
26
+ reset: '\x1b[0m',
27
+ bright: '\x1b[1m',
28
+ dim: '\x1b[2m',
29
+ red: '\x1b[31m',
30
+ green: '\x1b[32m',
31
+ yellow: '\x1b[33m',
32
+ cyan: '\x1b[36m',
33
+ magenta: '\x1b[35m',
34
+ };
35
+
36
+ const CLI_VERSION = '1.0.0';
37
+
38
+ // Test results storage
39
+ const testResults = {
40
+ passed: 0,
41
+ failed: 0,
42
+ skipped: 0,
43
+ tests: [],
44
+ };
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Test Helpers
48
+ // ---------------------------------------------------------------------------
49
+
50
+ function recordTest(name, passed, error = null, data = null) {
51
+ testResults.tests.push({ name, passed, error, data });
52
+ if (passed) {
53
+ testResults.passed++;
54
+ } else if (error === 'SKIPPED') {
55
+ testResults.skipped++;
56
+ } else {
57
+ testResults.failed++;
58
+ }
59
+ }
60
+
61
+ function printTestResult(name, passed, error, data, asJson) {
62
+ if (asJson) return;
63
+
64
+ const icon = passed ? `${C.green}✓${C.reset}` : error === 'SKIPPED' ? `${C.yellow}○${C.reset}` : `${C.red}✗${C.reset}`;
65
+ const status = passed ? `${C.green}PASS${C.reset}` : error === 'SKIPPED' ? `${C.yellow}SKIP${C.reset}` : `${C.red}FAIL${C.reset}`;
66
+
67
+ console.log(` ${icon} ${name.padEnd(35)} ${status}`);
68
+
69
+ if (data && !passed && error !== 'SKIPPED') {
70
+ console.log(` ${C.red}Error: ${error}${C.reset}`);
71
+ } else if (data && passed) {
72
+ console.log(` ${C.dim}${formatData(data)}${C.reset}`);
73
+ } else if (error && error !== 'SKIPPED') {
74
+ console.log(` ${C.red}${error}${C.reset}`);
75
+ }
76
+ }
77
+
78
+ function formatData(data) {
79
+ if (typeof data === 'number') return data.toLocaleString();
80
+ if (typeof data === 'string') return data.length > 60 ? data.substring(0, 60) + '...' : data;
81
+ if (typeof data === 'object') {
82
+ try {
83
+ return JSON.stringify(data).substring(0, 80);
84
+ } catch {
85
+ return '[object]';
86
+ }
87
+ }
88
+ return String(data);
89
+ }
90
+
91
+ function getDefaultRpc() {
92
+ return process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
93
+ }
94
+
95
+ // ---------------------------------------------------------------------------
96
+ // Test Cases - All use REAL RPC calls via SDK
97
+ // ---------------------------------------------------------------------------
98
+
99
+ async function testGetSlot(client) {
100
+ const name = 'getSlot()';
101
+ try {
102
+ const slot = await client.getSlot();
103
+ const passed = typeof slot === 'number' && slot >= 0;
104
+ recordTest(name, passed, passed ? null : 'Invalid slot number', slot);
105
+ printTestResult(name, passed, passed ? null : 'Invalid slot number', slot, false);
106
+ return passed;
107
+ } catch (err) {
108
+ recordTest(name, false, err.message);
109
+ printTestResult(name, false, err.message, null, false);
110
+ return false;
111
+ }
112
+ }
113
+
114
+ async function testGetBlockHeight(client) {
115
+ const name = 'getBlockHeight()';
116
+ try {
117
+ const height = await client.getBlockHeight();
118
+ const passed = typeof height === 'number' && height >= 0;
119
+ recordTest(name, passed, passed ? null : 'Invalid block height', height);
120
+ printTestResult(name, passed, passed ? null : 'Invalid block height', height, false);
121
+ return passed;
122
+ } catch (err) {
123
+ recordTest(name, false, err.message);
124
+ printTestResult(name, false, err.message, null, false);
125
+ return false;
126
+ }
127
+ }
128
+
129
+ async function testGetHealth(client) {
130
+ const name = 'getHealth()';
131
+ try {
132
+ const health = await client.getHealth();
133
+ const passed = health === 'ok' || health === 'healthy' || typeof health === 'string';
134
+ recordTest(name, passed, passed ? null : 'Unhealthy node', health);
135
+ printTestResult(name, passed, passed ? null : 'Unhealthy node', health, false);
136
+ return passed;
137
+ } catch (err) {
138
+ recordTest(name, false, err.message);
139
+ printTestResult(name, false, err.message, null, false);
140
+ return false;
141
+ }
142
+ }
143
+
144
+ async function testGetVersion(client) {
145
+ const name = 'getVersion()';
146
+ try {
147
+ const version = await client.getVersion();
148
+ const passed = version && (version.aetherCore || version.featureSet || Object.keys(version).length > 0);
149
+ recordTest(name, passed, passed ? null : 'Empty version info', version);
150
+ printTestResult(name, passed, passed ? null : 'Empty version info', version, false);
151
+ return passed;
152
+ } catch (err) {
153
+ recordTest(name, false, err.message);
154
+ printTestResult(name, false, err.message, null, false);
155
+ return false;
156
+ }
157
+ }
158
+
159
+ async function testGetEpochInfo(client) {
160
+ const name = 'getEpochInfo()';
161
+ try {
162
+ const epoch = await client.getEpochInfo();
163
+ const passed = epoch && (epoch.epoch !== undefined || epoch.slot !== undefined);
164
+ recordTest(name, passed, passed ? null : 'Invalid epoch info', epoch);
165
+ printTestResult(name, passed, passed ? null : 'Invalid epoch info', epoch, false);
166
+ return passed;
167
+ } catch (err) {
168
+ recordTest(name, false, err.message);
169
+ printTestResult(name, false, err.message, null, false);
170
+ return false;
171
+ }
172
+ }
173
+
174
+ async function testGetSupply(client) {
175
+ const name = 'getSupply()';
176
+ try {
177
+ const supply = await client.getSupply();
178
+ const passed = supply && (supply.total !== undefined || supply.circulating !== undefined);
179
+ recordTest(name, passed, passed ? null : 'Invalid supply info', supply);
180
+ printTestResult(name, passed, passed ? null : 'Invalid supply info', supply, false);
181
+ return passed;
182
+ } catch (err) {
183
+ recordTest(name, false, err.message);
184
+ printTestResult(name, false, err.message, null, false);
185
+ return false;
186
+ }
187
+ }
188
+
189
+ async function testGetTPS(client) {
190
+ const name = 'getTPS()';
191
+ try {
192
+ const tps = await client.getTPS();
193
+ const passed = tps !== null && tps !== undefined;
194
+ recordTest(name, passed, passed ? null : 'Invalid TPS', tps);
195
+ printTestResult(name, passed, passed ? null : 'Invalid TPS', tps, false);
196
+ return passed;
197
+ } catch (err) {
198
+ recordTest(name, false, err.message);
199
+ printTestResult(name, false, err.message, null, false);
200
+ return false;
201
+ }
202
+ }
203
+
204
+ async function testGetFees(client) {
205
+ const name = 'getFees()';
206
+ try {
207
+ const fees = await client.getFees();
208
+ const passed = fees && Object.keys(fees).length > 0;
209
+ recordTest(name, passed, passed ? null : 'Empty fee info', fees);
210
+ printTestResult(name, passed, passed ? null : 'Empty fee info', fees, false);
211
+ return passed;
212
+ } catch (err) {
213
+ recordTest(name, false, err.message);
214
+ printTestResult(name, false, err.message, null, false);
215
+ return false;
216
+ }
217
+ }
218
+
219
+ async function testGetValidators(client) {
220
+ const name = 'getValidators()';
221
+ try {
222
+ const validators = await client.getValidators();
223
+ const passed = Array.isArray(validators);
224
+ recordTest(name, passed, passed ? null : 'Not an array', validators ? validators.length : null);
225
+ printTestResult(name, passed, passed ? null : 'Not an array', validators ? validators.length : null, false);
226
+ return passed;
227
+ } catch (err) {
228
+ recordTest(name, false, err.message);
229
+ printTestResult(name, false, err.message, null, false);
230
+ return false;
231
+ }
232
+ }
233
+
234
+ async function testGetClusterPeers(client) {
235
+ const name = 'getClusterPeers()';
236
+ try {
237
+ const peers = await client.getClusterPeers();
238
+ const passed = Array.isArray(peers);
239
+ recordTest(name, passed, passed ? null : 'Not an array', peers ? peers.length : null);
240
+ printTestResult(name, passed, passed ? null : 'Not an array', peers ? peers.length : null, false);
241
+ return passed;
242
+ } catch (err) {
243
+ recordTest(name, false, err.message);
244
+ printTestResult(name, false, err.message, null, false);
245
+ return false;
246
+ }
247
+ }
248
+
249
+ async function testGetRecentBlockhash(client) {
250
+ const name = 'getRecentBlockhash()';
251
+ try {
252
+ const blockhash = await client.getRecentBlockhash();
253
+ const passed = blockhash && (blockhash.blockhash || blockhash.value);
254
+ recordTest(name, passed, passed ? null : 'No blockhash returned', blockhash);
255
+ printTestResult(name, passed, passed ? null : 'No blockhash returned', blockhash, false);
256
+ return passed;
257
+ } catch (err) {
258
+ recordTest(name, false, err.message);
259
+ printTestResult(name, false, err.message, null, false);
260
+ return false;
261
+ }
262
+ }
263
+
264
+ async function testGetAccountInfo(client, testAddress) {
265
+ const name = 'getAccountInfo()';
266
+ try {
267
+ // Use system program address as test (always exists)
268
+ const address = testAddress || '11111111111111111111111111111111';
269
+ const account = await client.getAccountInfo(address);
270
+ const passed = account && (account.lamports !== undefined || account.owner !== undefined);
271
+ recordTest(name, passed, passed ? null : 'Invalid account info', account);
272
+ printTestResult(name, passed, passed ? null : 'Invalid account info', account, false);
273
+ return passed;
274
+ } catch (err) {
275
+ recordTest(name, false, err.message);
276
+ printTestResult(name, false, err.message, null, false);
277
+ return false;
278
+ }
279
+ }
280
+
281
+ async function testGetBalance(client, testAddress) {
282
+ const name = 'getBalance()';
283
+ try {
284
+ const address = testAddress || '11111111111111111111111111111111';
285
+ const balance = await client.getBalance(address);
286
+ const passed = typeof balance === 'number' && balance >= 0;
287
+ recordTest(name, passed, passed ? null : 'Invalid balance', balance);
288
+ printTestResult(name, passed, passed ? null : 'Invalid balance', balance, false);
289
+ return passed;
290
+ } catch (err) {
291
+ recordTest(name, false, err.message);
292
+ printTestResult(name, false, err.message, null, false);
293
+ return false;
294
+ }
295
+ }
296
+
297
+ async function testGetSlotProduction(client) {
298
+ const name = 'getSlotProduction()';
299
+ try {
300
+ const stats = await client.getSlotProduction();
301
+ const passed = stats && Object.keys(stats).length > 0;
302
+ recordTest(name, passed, passed ? null : 'Empty slot production stats', stats);
303
+ printTestResult(name, passed, passed ? null : 'Empty slot production stats', stats, false);
304
+ return passed;
305
+ } catch (err) {
306
+ recordTest(name, false, err.message);
307
+ printTestResult(name, false, err.message, null, false);
308
+ return false;
309
+ }
310
+ }
311
+
312
+ async function testConvenienceFunctions(rpcUrl) {
313
+ const name = 'Convenience Functions';
314
+ try {
315
+ // Test multiple convenience functions
316
+ const [slot, health, blockHeight] = await Promise.all([
317
+ aether.getSlot(),
318
+ aether.getHealth(),
319
+ aether.getBlockHeight(),
320
+ ]);
321
+
322
+ const passed = typeof slot === 'number' && health && typeof blockHeight === 'number';
323
+ recordTest(name, passed, passed ? null : 'One or more functions failed', { slot, health, blockHeight });
324
+ printTestResult(name, passed, passed ? null : 'Function failed', { slot, health, blockHeight }, false);
325
+ return passed;
326
+ } catch (err) {
327
+ recordTest(name, false, err.message);
328
+ printTestResult(name, false, err.message, null, false);
329
+ return false;
330
+ }
331
+ }
332
+
333
+ async function testPingUtility(rpcUrl) {
334
+ const name = 'ping()';
335
+ try {
336
+ const result = await aether.ping(rpcUrl);
337
+ const passed = result && result.ok === true && result.latency >= 0;
338
+ recordTest(name, passed, passed ? null : 'Ping failed', result);
339
+ printTestResult(name, passed, passed ? null : 'Ping failed', result, false);
340
+ return passed;
341
+ } catch (err) {
342
+ recordTest(name, false, err.message);
343
+ printTestResult(name, false, err.message, null, false);
344
+ return false;
345
+ }
346
+ }
347
+
348
+ // ---------------------------------------------------------------------------
349
+ // Test Runner
350
+ // ---------------------------------------------------------------------------
351
+
352
+ async function runFullTestSuite(client, rpcUrl) {
353
+ console.log(`\n${C.bright}${C.cyan}═══ Aether SDK Test Suite ═══${C.reset}\n`);
354
+ console.log(` ${C.dim}RPC Endpoint: ${rpcUrl}${C.reset}`);
355
+ console.log(` ${C.dim}Testing @jellylegsai/aether-sdk v1.0.0${C.reset}\n`);
356
+ console.log(` ${C.bright}Running tests...${C.reset}\n`);
357
+
358
+ // Core RPC methods
359
+ console.log(` ${C.cyan}── Core RPC Methods ──${C.reset}`);
360
+ await testGetSlot(client);
361
+ await testGetBlockHeight(client);
362
+ await testGetHealth(client);
363
+ await testGetVersion(client);
364
+ await testGetEpochInfo(client);
365
+
366
+ console.log(`\n ${C.cyan}── Network & Supply ──${C.reset}`);
367
+ await testGetSupply(client);
368
+ await testGetTPS(client);
369
+ await testGetFees(client);
370
+ await testGetValidators(client);
371
+ await testGetClusterPeers(client);
372
+
373
+ console.log(`\n ${C.cyan}── Transaction Support ──${C.reset}`);
374
+ await testGetRecentBlockhash(client);
375
+ await testGetSlotProduction(client);
376
+
377
+ console.log(`\n ${C.cyan}── Account Operations ──${C.reset}`);
378
+ await testGetAccountInfo(client);
379
+ await testGetBalance(client);
380
+
381
+ console.log(`\n ${C.cyan}── Convenience Functions ──${C.reset}`);
382
+ await testConvenienceFunctions(rpcUrl);
383
+ await testPingUtility(rpcUrl);
384
+ }
385
+
386
+ async function runQuickTest(client, rpcUrl) {
387
+ console.log(`\n${C.bright}${C.cyan}═══ Aether SDK Quick Test ═══${C.reset}\n`);
388
+ console.log(` ${C.dim}RPC Endpoint: ${rpcUrl}${C.reset}\n`);
389
+
390
+ console.log(` ${C.cyan}── Essential Tests ──${C.reset}`);
391
+ await testGetSlot(client);
392
+ await testGetHealth(client);
393
+ await testGetBalance(client);
394
+ }
395
+
396
+ function printSummary(asJson) {
397
+ if (asJson) {
398
+ console.log(JSON.stringify({
399
+ total: testResults.passed + testResults.failed + testResults.skipped,
400
+ passed: testResults.passed,
401
+ failed: testResults.failed,
402
+ skipped: testResults.skipped,
403
+ tests: testResults.tests,
404
+ cli_version: CLI_VERSION,
405
+ timestamp: new Date().toISOString(),
406
+ }, null, 2));
407
+ return;
408
+ }
409
+
410
+ console.log(`\n${C.bright}${C.cyan}═══ Test Summary ═══${C.reset}\n`);
411
+
412
+ const total = testResults.passed + testResults.failed + testResults.skipped;
413
+ const passRate = total > 0 ? ((testResults.passed / total) * 100).toFixed(1) : 0;
414
+
415
+ console.log(` ${C.bright}Total:${C.reset} ${total}`);
416
+ console.log(` ${C.green}Passed:${C.reset} ${testResults.passed}`);
417
+ console.log(` ${C.red}Failed:${C.reset} ${testResults.failed}`);
418
+ console.log(` ${C.yellow}Skipped:${C.reset} ${testResults.skipped}`);
419
+ console.log(` ${C.bright}Pass Rate:${C.reset} ${passRate}%\n`);
420
+
421
+ if (testResults.failed > 0) {
422
+ console.log(` ${C.red}── Failed Tests ──${C.reset}`);
423
+ testResults.tests.filter(t => !t.passed && t.error !== 'SKIPPED').forEach(t => {
424
+ console.log(` ${C.red}✗ ${t.name}: ${t.error}${C.reset}`);
425
+ });
426
+ console.log();
427
+ }
428
+
429
+ if (testResults.failed === 0) {
430
+ console.log(` ${C.green}✓ All tests passed!${C.reset}\n`);
431
+ }
432
+ }
433
+
434
+ // ---------------------------------------------------------------------------
435
+ // Main
436
+ // ---------------------------------------------------------------------------
437
+
438
+ async function sdkTestCommand() {
439
+ const args = process.argv.slice(2);
440
+ const asJson = args.includes('--json') || args.includes('-j');
441
+ const isQuick = args.includes('--quick') || args.includes('-q');
442
+
443
+ const rpcIdx = args.findIndex(a => a === '--rpc' || a === '-r');
444
+ const rpcUrl = rpcIdx !== -1 && args[rpcIdx + 1] ? args[rpcIdx + 1] : getDefaultRpc();
445
+
446
+ if (!asJson) {
447
+ console.log(`\n${C.bright}${C.cyan}╔═══════════════════════════════════════════════════════════╗${C.reset}`);
448
+ console.log(`${C.bright}${C.cyan}║ AETHER SDK COMPREHENSIVE TEST SUITE ║${C.reset}`);
449
+ console.log(`${C.bright}${C.cyan}╚═══════════════════════════════════════════════════════════╝${C.reset}\n`);
450
+ }
451
+
452
+ const client = new aether.AetherClient({ rpcUrl });
453
+
454
+ if (isQuick) {
455
+ await runQuickTest(client, rpcUrl);
456
+ } else {
457
+ await runFullTestSuite(client, rpcUrl);
458
+ }
459
+
460
+ printSummary(asJson);
461
+
462
+ // Exit with error if tests failed
463
+ if (testResults.failed > 0) {
464
+ process.exit(1);
465
+ }
466
+ }
467
+
468
+ // Export for module use
469
+ module.exports = { sdkTestCommand };
470
+
471
+ // Run if called directly
472
+ if (require.main === module) {
473
+ sdkTestCommand().catch(err => {
474
+ console.error(`${C.red}✗ Test suite failed: ${err.message}${C.reset}`);
475
+ process.exit(1);
476
+ });
477
+ }