aether-hub 1.2.6 → 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/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 -508
- 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 +54 -6
- package/package.json +1 -3
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* aether-cli - Stake Info Command
|
|
4
|
+
* Get staking information for an address using real chain RPC calls.
|
|
5
|
+
* All calls go through @jellylegsai/aether-sdk → AetherClient → real HTTP RPC.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* aether stake-info <address> Show stake info for address
|
|
9
|
+
* aether stake-info <address> --json JSON output
|
|
10
|
+
* aether stake-info <address> --rpc <url> Custom RPC endpoint
|
|
11
|
+
*
|
|
12
|
+
* SDK wired to: GET /v1/slot, GET /v1/account/<addr>, GET /v1/blockheight
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
|
|
17
|
+
const { AetherClient } = require(sdkPath);
|
|
18
|
+
|
|
19
|
+
// ANSI colours
|
|
20
|
+
const C = {
|
|
21
|
+
reset: '\x1b[0m',
|
|
22
|
+
bright: '\x1b[1m',
|
|
23
|
+
dim: '\x1b[2m',
|
|
24
|
+
red: '\x1b[31m',
|
|
25
|
+
green: '\x1b[32m',
|
|
26
|
+
yellow: '\x1b[33m',
|
|
27
|
+
cyan: '\x1b[36m',
|
|
28
|
+
magenta: '\x1b[35m',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Argument parsing
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
function parseArgs() {
|
|
36
|
+
const args = process.argv.slice(2);
|
|
37
|
+
const result = { address: null, json: false, rpc: null };
|
|
38
|
+
|
|
39
|
+
for (let i = 0; i < args.length; i++) {
|
|
40
|
+
if (!args[i].startsWith('-') && !result.address) {
|
|
41
|
+
result.address = args[i];
|
|
42
|
+
} else if (args[i] === '--json' || args[i] === '-j') {
|
|
43
|
+
result.json = true;
|
|
44
|
+
} else if ((args[i] === '--rpc' || args[i] === '-r') && args[i + 1]) {
|
|
45
|
+
result.rpc = args[++i];
|
|
46
|
+
} else if (args[i] === '--help' || args[i] === '-h') {
|
|
47
|
+
result.help = true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getDefaultRpc() {
|
|
54
|
+
return process.env.AETHER_RPC || 'http://127.0.0.1:8899';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Main
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
async function stakeInfoCommand() {
|
|
62
|
+
const opts = parseArgs();
|
|
63
|
+
|
|
64
|
+
if (opts.help || !opts.address) {
|
|
65
|
+
console.log(`
|
|
66
|
+
${C.bright}${C.cyan}aether-cli stake-info${C.reset} — Get stake/account info for an address
|
|
67
|
+
|
|
68
|
+
${C.bright}USAGE${C.reset}
|
|
69
|
+
aether stake-info <address> [--json] [--rpc <url>]
|
|
70
|
+
|
|
71
|
+
${C.bright}OPTIONS${C.reset}
|
|
72
|
+
--json, -j Output raw JSON
|
|
73
|
+
--rpc <url> RPC endpoint (default: AETHER_RPC or localhost:8899)
|
|
74
|
+
--help, -h Show this help
|
|
75
|
+
|
|
76
|
+
${C.bright}SDK METHODS USED${C.reset}
|
|
77
|
+
client.getSlot() → GET /v1/slot
|
|
78
|
+
client.getAccountInfo(addr) → GET /v1/account/<addr>
|
|
79
|
+
client.getBlockHeight() → GET /v1/blockheight
|
|
80
|
+
|
|
81
|
+
${C.bright}EXAMPLE${C.reset}
|
|
82
|
+
aether stake-info ATH3abc...def
|
|
83
|
+
`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const rpcUrl = opts.rpc || getDefaultRpc();
|
|
88
|
+
const client = new AetherClient({ rpcUrl });
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
// Real chain RPC calls — all parallel for speed
|
|
92
|
+
const [slot, accountInfo, blockHeight] = await Promise.all([
|
|
93
|
+
client.getSlot().catch(() => null),
|
|
94
|
+
client.getAccountInfo(opts.address).catch(() => null),
|
|
95
|
+
client.getBlockHeight().catch(() => null),
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
const balance = accountInfo?.lamports != null
|
|
99
|
+
? (accountInfo.lamports / 1e9).toFixed(4) + ' AETH'
|
|
100
|
+
: 'unavailable';
|
|
101
|
+
const owner = accountInfo?.owner || 'unknown';
|
|
102
|
+
const executable = accountInfo?.executable ? 'yes' : 'no';
|
|
103
|
+
|
|
104
|
+
if (opts.json) {
|
|
105
|
+
console.log(JSON.stringify({
|
|
106
|
+
address: opts.address,
|
|
107
|
+
slot: slot ?? null,
|
|
108
|
+
block_height: blockHeight ?? null,
|
|
109
|
+
account: {
|
|
110
|
+
lamports: accountInfo?.lamports ?? null,
|
|
111
|
+
balance_aeth: accountInfo?.lamports != null ? (accountInfo.lamports / 1e9).toFixed(4) : null,
|
|
112
|
+
owner: accountInfo?.owner ?? null,
|
|
113
|
+
executable: accountInfo?.executable ?? false,
|
|
114
|
+
rent_epoch: accountInfo?.rent_epoch ?? null,
|
|
115
|
+
},
|
|
116
|
+
rpc: rpcUrl,
|
|
117
|
+
timestamp: new Date().toISOString(),
|
|
118
|
+
}, null, 2));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log(`\n${C.bright}${C.cyan}── Aether Stake Info ─────────────────────────────────${C.reset}\n`);
|
|
123
|
+
console.log(` ${C.dim}Address:${C.reset} ${opts.address}`);
|
|
124
|
+
console.log(` ${C.dim}Balance:${C.reset} ${C.green}${balance}${C.reset}`);
|
|
125
|
+
console.log(` ${C.dim}Owner:${C.reset} ${owner}`);
|
|
126
|
+
console.log(` ${C.dim}Executable:${C.reset} ${executable}`);
|
|
127
|
+
console.log(` ${C.dim}Slot:${C.reset} ${slot ?? 'unavailable'}`);
|
|
128
|
+
console.log(` ${C.dim}Block Height:${C.reset} ${blockHeight ?? 'unavailable'}`);
|
|
129
|
+
console.log(` ${C.dim}RPC:${C.reset} ${rpcUrl}\n`);
|
|
130
|
+
console.log(` ${C.green}? Real chain RPC calls completed${C.reset}\n`);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.log(` ${C.red}? Error: ${error.message}${C.reset}\n`);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
stakeInfoCommand();
|
|
138
|
+
|
|
139
|
+
module.exports = { stakeInfoCommand };
|