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.
- package/commands/account.js +10 -34
- package/commands/balance.js +276 -0
- package/commands/blockhash.js +181 -0
- package/commands/broadcast.js +323 -323
- package/commands/claim.js +292 -0
- package/commands/emergency.js +657 -657
- package/commands/epoch.js +12 -94
- package/commands/fees.js +276 -0
- package/commands/info.js +38 -79
- package/commands/network.js +34 -108
- package/commands/ping.js +11 -65
- package/commands/price.js +253 -253
- package/commands/rewards.js +187 -4
- package/commands/sdk-test.js +477 -0
- package/commands/stake-info.js +139 -0
- package/commands/status.js +113 -157
- package/commands/supply.js +34 -82
- package/commands/tps.js +238 -0
- package/commands/transfer.js +495 -0
- package/commands/tx-history.js +462 -0
- package/commands/validator-info.js +10 -4
- package/commands/validator-start.js +1 -1
- package/commands/validator-status.js +32 -73
- package/commands/validators.js +36 -75
- package/commands/wallet.js +5 -29
- package/index.js +64 -17
- package/package.json +1 -3
package/commands/rewards.js
CHANGED
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
* Shows accumulated rewards, estimated APY, and claimable amounts.
|
|
7
7
|
*
|
|
8
8
|
* Usage:
|
|
9
|
-
* aether rewards list
|
|
10
|
-
* aether rewards list
|
|
11
|
-
* aether rewards claim
|
|
12
|
-
* aether rewards summary --address <addr>
|
|
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
|
+
}
|