aether-hub 1.2.8 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@jellylegsai/aether-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Official Aether Blockchain SDK - Real HTTP RPC calls to Aether chain",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "files": [
8
+ "index.js",
9
+ "rpc.js",
10
+ "README.md"
11
+ ],
12
+ "scripts": {
13
+ "test": "node test.js",
14
+ "build": "node -e \"require('./index.js'); console.log('SDK build OK')\""
15
+ },
16
+ "keywords": [
17
+ "aether",
18
+ "blockchain",
19
+ "sdk",
20
+ "crypto",
21
+ "rpc",
22
+ "web3"
23
+ ],
24
+ "author": "Jelly-legs AI Team",
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/jelly-legs-ai/Jelly-legs-unsteady-workshop"
29
+ },
30
+ "engines": {
31
+ "node": ">=14.0.0"
32
+ }
33
+ }
package/sdk/rpc.js ADDED
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @jellylegsai/aether-sdk - RPC Client
4
+ *
5
+ * Low-level HTTP RPC client for Aether blockchain.
6
+ * All functions make REAL HTTP calls to the blockchain RPC endpoint.
7
+ * No stubs, no mocks.
8
+ */
9
+
10
+ const http = require('http');
11
+ const https = require('https');
12
+
13
+ const DEFAULT_RPC = process.env.AETHER_RPC || 'http://127.0.0.1:8899';
14
+
15
+ /**
16
+ * Make a GET request to the RPC endpoint
17
+ * @param {string} rpcUrl - RPC endpoint URL
18
+ * @param {string} path - API path (e.g., /v1/slot)
19
+ * @param {number} timeout - Request timeout in ms
20
+ * @returns {Promise<object>} Parsed JSON response
21
+ */
22
+ function rpcGet(rpcUrl, path, timeout = 8000) {
23
+ return new Promise((resolve, reject) => {
24
+ const url = new URL(path, rpcUrl);
25
+ const isHttps = url.protocol === 'https:';
26
+ const lib = isHttps ? https : http;
27
+
28
+ const req = lib.request({
29
+ hostname: url.hostname,
30
+ port: url.port || (isHttps ? 443 : 80),
31
+ path: url.pathname + url.search,
32
+ method: 'GET',
33
+ timeout,
34
+ headers: { 'Content-Type': 'application/json' },
35
+ }, (res) => {
36
+ let data = '';
37
+ res.on('data', (chunk) => (data += chunk));
38
+ res.on('end', () => {
39
+ try {
40
+ resolve(JSON.parse(data));
41
+ } catch {
42
+ resolve({ raw: data });
43
+ }
44
+ });
45
+ });
46
+
47
+ req.on('error', reject);
48
+ req.on('timeout', () => {
49
+ req.destroy();
50
+ reject(new Error(`RPC request timeout after ${timeout}ms`));
51
+ });
52
+ req.end();
53
+ });
54
+ }
55
+
56
+ /**
57
+ * Make a POST request to the RPC endpoint
58
+ * @param {string} rpcUrl - RPC endpoint URL
59
+ * @param {string} path - API path
60
+ * @param {object} body - Request body (will be JSON.stringify'd)
61
+ * @param {number} timeout - Request timeout in ms
62
+ * @returns {Promise<object>} Parsed JSON response
63
+ */
64
+ function rpcPost(rpcUrl, path, body, timeout = 8000) {
65
+ return new Promise((resolve, reject) => {
66
+ const url = new URL(path, rpcUrl);
67
+ const isHttps = url.protocol === 'https:';
68
+ const lib = isHttps ? https : http;
69
+ const bodyStr = JSON.stringify(body);
70
+
71
+ const req = lib.request({
72
+ hostname: url.hostname,
73
+ port: url.port || (isHttps ? 443 : 80),
74
+ path: url.pathname + url.search,
75
+ method: 'POST',
76
+ timeout,
77
+ headers: {
78
+ 'Content-Type': 'application/json',
79
+ 'Content-Length': Buffer.byteLength(bodyStr),
80
+ },
81
+ }, (res) => {
82
+ let data = '';
83
+ res.on('data', (chunk) => (data += chunk));
84
+ res.on('end', () => {
85
+ try {
86
+ resolve(JSON.parse(data));
87
+ } catch {
88
+ resolve(data);
89
+ }
90
+ });
91
+ });
92
+
93
+ req.on('error', reject);
94
+ req.on('timeout', () => {
95
+ req.destroy();
96
+ reject(new Error(`RPC request timeout after ${timeout}ms`));
97
+ });
98
+
99
+ req.write(bodyStr);
100
+ req.end();
101
+ });
102
+ }
103
+
104
+ module.exports = {
105
+ rpcGet,
106
+ rpcPost,
107
+ DEFAULT_RPC,
108
+ };
package/sdk/test.js ADDED
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @jellylegsai/aether-sdk - Test Script
4
+ *
5
+ * Tests all SDK functions with REAL RPC calls to http://127.0.0.1:8899
6
+ */
7
+
8
+ const aether = require('./index');
9
+
10
+ const C = {
11
+ reset: '\x1b[0m',
12
+ green: '\x1b[32m',
13
+ red: '\x1b[31m',
14
+ yellow: '\x1b[33m',
15
+ cyan: '\x1b[36m',
16
+ dim: '\x1b[2m',
17
+ };
18
+
19
+ async function test(name, fn) {
20
+ try {
21
+ const result = await fn();
22
+ console.log(` ${C.green}✓${C.reset} ${name}`);
23
+ return { ok: true, result };
24
+ } catch (err) {
25
+ console.log(` ${C.red}✗${C.reset} ${name}: ${err.message}`);
26
+ return { ok: false, error: err.message };
27
+ }
28
+ }
29
+
30
+ async function main() {
31
+ console.log(`\n${C.cyan}══ @jellylegsai/aether-sdk Test Suite ══${C.reset}\n`);
32
+ console.log(` ${C.dim}RPC Endpoint: ${aether.DEFAULT_RPC_URL}${C.reset}\n`);
33
+
34
+ const results = [];
35
+
36
+ // Test ping first
37
+ results.push(await test('ping()', async () => {
38
+ const ping = await aether.ping();
39
+ if (!ping.ok) throw new Error(ping.error);
40
+ return ping;
41
+ }));
42
+
43
+ // Test chain queries
44
+ results.push(await test('getSlot()', () => aether.getSlot()));
45
+ results.push(await test('getBlockHeight()', () => aether.getBlockHeight()));
46
+ results.push(await test('getEpoch()', () => aether.getEpoch()));
47
+ results.push(await test('getTPS()', () => aether.getTPS()));
48
+ results.push(await test('getSupply()', () => aether.getSupply()));
49
+ results.push(await test('getFees()', () => aether.getFees()));
50
+ results.push(await test('getValidators()', () => aether.getValidators()));
51
+ results.push(await test('getPeers()', () => aether.getPeers()));
52
+ results.push(await test('getHealth()', () => aether.getHealth()));
53
+ results.push(await test('getSlotProduction()', () => aether.getSlotProduction()));
54
+
55
+ // Test with a sample address (may not exist, but tests the call)
56
+ const testAddress = 'ATH111111111111111111111111111111111';
57
+ results.push(await test(`getAccount('${testAddress.substring(0, 12)}...')`, () =>
58
+ aether.getAccount(testAddress)
59
+ ));
60
+ results.push(await test(`getBalance('${testAddress.substring(0, 12)}...')`, () =>
61
+ aether.getBalance(testAddress)
62
+ ));
63
+ results.push(await test(`getStakePositions('${testAddress.substring(0, 12)}...')`, () =>
64
+ aether.getStakePositions(testAddress)
65
+ ));
66
+ results.push(await test(`getRewards('${testAddress.substring(0, 12)}...')`, () =>
67
+ aether.getRewards(testAddress)
68
+ ));
69
+
70
+ // Summary
71
+ const passed = results.filter(r => r.ok).length;
72
+ const failed = results.filter(r => !r.ok).length;
73
+
74
+ console.log();
75
+ if (failed === 0) {
76
+ console.log(` ${C.green}══ All ${passed} tests passed! ══${C.reset}\n`);
77
+ } else {
78
+ console.log(` ${C.yellow}══ ${passed} passed, ${failed} failed ══${C.reset}`);
79
+ console.log(` ${C.dim} (Failures may be due to RPC not running or test data not existing)${C.reset}\n`);
80
+ }
81
+
82
+ process.exit(failed > 0 ? 1 : 0);
83
+ }
84
+
85
+ main();
package/theme.js ADDED
@@ -0,0 +1,211 @@
1
+ /**
2
+ * aether-cli - Shared Theme & Branding
3
+ *
4
+ * Consistent colors, ASCII art, and styling across all CLI commands.
5
+ * Import this module to ensure unified visual identity.
6
+ */
7
+
8
+ // ANSI color codes - Aether Brand Palette
9
+ const COLORS = {
10
+ // Reset
11
+ reset: '\x1b[0m',
12
+
13
+ // Styles
14
+ bright: '\x1b[1m',
15
+ dim: '\x1b[2m',
16
+ italic: '\x1b[3m',
17
+ underline: '\x1b[4m',
18
+
19
+ // Core palette
20
+ red: '\x1b[31m',
21
+ green: '\x1b[32m',
22
+ yellow: '\x1b[33m',
23
+ blue: '\x1b[34m',
24
+ magenta: '\x1b[35m',
25
+ cyan: '\x1b[36m',
26
+ white: '\x1b[37m',
27
+
28
+ // Extended palette
29
+ crimson: '\x1b[38;5;161m',
30
+ gold: '\x1b[38;5;220m',
31
+ lime: '\x1b[38;5;118m',
32
+ orange: '\x1b[38;5;208m',
33
+ purple: '\x1b[38;5;93m',
34
+ teal: '\x1b[38;5;6m',
35
+
36
+ // Tier colors
37
+ full: '\x1b[36m', // cyan - Full validator
38
+ lite: '\x1b[33m', // yellow - Lite validator
39
+ observer: '\x1b[32m', // green - Observer
40
+ };
41
+
42
+ // Short alias for convenience
43
+ const C = COLORS;
44
+
45
+ // ASCII Art Banners
46
+ const BANNERS = {
47
+ // Main CLI banner
48
+ main: `
49
+ ${C.cyan} _ _____ _ _ ________ ______ _ _ ${C.reset}
50
+ ${C.cyan} / \\ | ____| | | |__ /_ _| |__ /| | | |${C.reset}
51
+ ${C.cyan} / _ \\ | _| | |_| | / / | | / /| |_| |${C.reset}
52
+ ${C.cyan} / ___ \\| |___| _ |/ /_ | | / /_| _ |${C.reset}
53
+ ${C.cyan}/_/ \\_\\_____|_| |_/____|___| /____|_| |_|${C.reset}
54
+ ${C.dim} Validator Command Line Interface${C.reset}
55
+ `,
56
+
57
+ // Compact banner for subcommands
58
+ compact: `
59
+ ${C.cyan}╔═══════════════════════════════════════════════════════════════╗${C.reset}
60
+ ${C.cyan}║${C.reset} ${C.bright}AETHER CHAIN — Validator CLI${C.reset}${C.cyan} ║${C.reset}
61
+ ${C.cyan}╚═══════════════════════════════════════════════════════════════╝${C.reset}`,
62
+
63
+ // Section headers
64
+ network: `${C.cyan}╔═══════════════════════════════════════════════════════════════════╗${C.reset}
65
+ ${C.cyan}║${C.reset} ${C.bright}AETHER NETWORK STATUS${C.reset}${C.cyan} ║${C.reset}
66
+ ${C.cyan}╚═══════════════════════════════════════════════════════════════════╝${C.reset}`,
67
+
68
+ balance: `${C.bright}${C.cyan}── Aether Account Balance ───────────────────────────────${C.reset}`,
69
+ slot: `${C.bright}${C.cyan}── Aether Current Slot ──────────────────────────────────${C.reset}`,
70
+ tps: `${C.bright}${C.cyan}── Aether Network TPS ─────────────────────────────────${C.reset}`,
71
+ fees: `${C.bright}${C.cyan}── Aether Network Fees ──────────────────────────────────${C.reset}`,
72
+ stake: `${C.bright}${C.cyan}── Aether Stake Info ─────────────────────────────────${C.reset}`,
73
+ rewards: `${C.bright}${C.cyan}╔═══════════════════════════════════════════════════════╗${C.reset}
74
+ ${C.bright}${C.cyan}║ Staking Rewards ║${C.reset}
75
+ ${C.bright}${C.cyan}╚═══════════════════════════════════════════════════════╝${C.reset}`,
76
+ claim: `${C.bright}${C.cyan}── Claim Staking Rewards ────────────────────────────────${C.reset}`,
77
+ emergency: `${C.bright}${C.cyan}🔔 Aether Emergency Status${C.reset}`,
78
+ multisig: `${C.bright}${C.cyan}── Multi-Signature Wallet ─────────────────────────${C.reset}`,
79
+ validator: `${C.bright}${C.cyan}── Validator Management ─────────────────────────────${C.reset}`,
80
+ wallet: `${C.bright}${C.cyan}── Wallet Management ────────────────────────────────${C.reset}`,
81
+ txHistory: `${C.bright}${C.cyan} Transaction History${C.reset}`,
82
+ };
83
+
84
+ // Status icons
85
+ const ICONS = {
86
+ success: `${C.green}✓${C.reset}`,
87
+ error: `${C.red}✗${C.reset}`,
88
+ warning: `${C.yellow}⚠${C.reset}`,
89
+ info: `${C.cyan}ℹ${C.reset}`,
90
+ pending: `${C.yellow}⏳${C.reset}`,
91
+ active: `${C.green}●${C.reset}`,
92
+ inactive: `${C.dim}○${C.reset}`,
93
+ bullet: `${C.cyan}★${C.reset}`,
94
+ arrow: `${C.cyan}▸${C.reset}`,
95
+ lightning: `${C.yellow}⚡${C.reset}`,
96
+ fire: `${C.red}🔥${C.reset}`,
97
+ bell: `${C.yellow}🔔${C.reset}`,
98
+ };
99
+
100
+ // Box drawing characters
101
+ const BOX = {
102
+ h: '─',
103
+ v: '│',
104
+ tl: '┌',
105
+ tr: '┐',
106
+ bl: '└',
107
+ br: '┘',
108
+ t: '┬',
109
+ b: '┴',
110
+ l: '├',
111
+ r: '┤',
112
+ cross: '┼',
113
+ };
114
+
115
+ // CLI Metadata
116
+ const META = {
117
+ version: '1.3.0',
118
+ name: 'aether-hub',
119
+ description: 'AeTHer Validator CLI — tiered validators, system checks, and node management',
120
+ author: 'Jelly-legs AI Team',
121
+ repository: 'https://github.com/jelly-legs-ai/Jelly-legs-unsteady-workshop',
122
+ rpc: {
123
+ default: 'http://127.0.0.1:8899',
124
+ env: 'AETHER_RPC',
125
+ },
126
+ };
127
+
128
+ // Helper functions
129
+ function printBanner(type = 'compact') {
130
+ console.log(BANNERS[type] || BANNERS.compact);
131
+ }
132
+
133
+ function printBox(content, width = 70) {
134
+ const lines = content.split('\n');
135
+ console.log(`${C.cyan}${BOX.tl}${BOX.h.repeat(width - 2)}${BOX.tr}${C.reset}`);
136
+ lines.forEach(line => {
137
+ const padded = line.padEnd(width - 4);
138
+ console.log(`${C.cyan}${BOX.v}${C.reset} ${padded} ${C.cyan}${BOX.v}${C.reset}`);
139
+ });
140
+ console.log(`${C.cyan}${BOX.bl}${BOX.h.repeat(width - 2)}${BOX.br}${C.reset}`);
141
+ }
142
+
143
+ function printDivider(char = '─', width = 70) {
144
+ console.log(`${C.dim}${char.repeat(width)}${C.reset}`);
145
+ }
146
+
147
+ function formatLabel(label, value, options = {}) {
148
+ const { color = C.bright, valueColor = C.reset, indent = 2 } = options;
149
+ const spaces = ' '.repeat(indent);
150
+ return `${spaces}${color}${label}:${C.reset} ${valueColor}${value}${C.reset}`;
151
+ }
152
+
153
+ function formatAeth(lamports, options = {}) {
154
+ const { decimals = 6, showLamports = false } = options;
155
+ const aeth = (Number(lamports) / 1e9).toFixed(decimals).replace(/\.?0+$/, '');
156
+ if (showLamports) {
157
+ return `${C.green}${aeth} AETH${C.reset} ${C.dim}(${Number(lamports).toLocaleString()} lamports)${C.reset}`;
158
+ }
159
+ return `${C.green}${aeth} AETH${C.reset}`;
160
+ }
161
+
162
+ function formatDate(date) {
163
+ if (!date) return `${C.dim}—${C.reset}`;
164
+ const d = date instanceof Date ? date : new Date(date);
165
+ return d.toISOString().replace('T', ' ').slice(0, 19);
166
+ }
167
+
168
+ function shortenAddress(addr, start = 6, end = 4) {
169
+ if (!addr || addr.length <= start + end + 3) return addr || `${C.dim}unknown${C.reset}`;
170
+ return `${addr.slice(0, start)}...${addr.slice(-end)}`;
171
+ }
172
+
173
+ function getRpcUrl(customUrl = null) {
174
+ return customUrl || process.env[META.rpc.env] || META.rpc.default;
175
+ }
176
+
177
+ // Status colors based on value
178
+ function statusColor(value, thresholds = {}) {
179
+ const { good = 80, warning = 50 } = thresholds;
180
+ if (value >= good) return C.green;
181
+ if (value >= warning) return C.yellow;
182
+ return C.red;
183
+ }
184
+
185
+ function tpsColor(tps) {
186
+ if (tps === null || tps === undefined) return C.red;
187
+ if (tps >= 1000) return C.green;
188
+ if (tps >= 100) return C.cyan;
189
+ if (tps >= 10) return C.yellow;
190
+ return C.red;
191
+ }
192
+
193
+ // Export everything
194
+ module.exports = {
195
+ COLORS,
196
+ C,
197
+ BANNERS,
198
+ ICONS,
199
+ BOX,
200
+ META,
201
+ printBanner,
202
+ printBox,
203
+ printDivider,
204
+ formatLabel,
205
+ formatAeth,
206
+ formatDate,
207
+ shortenAddress,
208
+ getRpcUrl,
209
+ statusColor,
210
+ tpsColor,
211
+ };