aether-hub 1.2.6 → 1.2.8

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.
@@ -17,8 +17,7 @@
17
17
  * Requires AETHER_RPC env var (default: http://127.0.0.1:8899)
18
18
  */
19
19
 
20
- const http = require('http');
21
- const https = require('https');
20
+ const path = require('path');
22
21
 
23
22
  // ANSI colours
24
23
  const C = {
@@ -36,63 +35,19 @@ const C = {
36
35
  const CLI_VERSION = '1.0.0';
37
36
 
38
37
  // ---------------------------------------------------------------------------
39
- // Helpers
38
+ // SDK Import - Real blockchain RPC calls via @jellylegsai/aether-sdk
40
39
  // ---------------------------------------------------------------------------
41
40
 
42
- function getDefaultRpc() {
43
- return process.env.AETHER_RPC || 'http://127.0.0.1:8899';
44
- }
41
+ const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
42
+ const aether = require(sdkPath);
45
43
 
46
- function httpRequest(rpcUrl, pathStr, timeoutMs = 10000) {
47
- return new Promise((resolve, reject) => {
48
- const url = new URL(pathStr, rpcUrl);
49
- const lib = url.protocol === 'https:' ? https : http;
50
- const req = lib.request({
51
- hostname: url.hostname,
52
- port: url.port || (url.protocol === 'https:' ? 443 : 80),
53
- path: url.pathname + url.search,
54
- method: 'GET',
55
- timeout: timeoutMs,
56
- headers: { 'Content-Type': 'application/json' },
57
- }, (res) => {
58
- let data = '';
59
- res.on('data', (chunk) => data += chunk);
60
- res.on('end', () => {
61
- try { resolve(JSON.parse(data)); }
62
- catch { resolve({ raw: data }); }
63
- });
64
- });
65
- req.on('error', reject);
66
- req.on('timeout', () => { req.destroy(); reject(new Error('Request timeout after ' + timeoutMs + 'ms')); });
67
- req.end();
68
- });
44
+ function getDefaultRpc() {
45
+ return process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
69
46
  }
70
47
 
71
- function httpPost(rpcUrl, pathStr, body, timeoutMs = 10000) {
72
- return new Promise((resolve, reject) => {
73
- const url = new URL(pathStr, rpcUrl);
74
- const lib = url.protocol === 'https:' ? https : http;
75
- const bodyStr = JSON.stringify(body);
76
- const req = lib.request({
77
- hostname: url.hostname,
78
- port: url.port || (url.protocol === 'https:' ? 443 : 80),
79
- path: url.pathname + url.search,
80
- method: 'POST',
81
- timeout: timeoutMs,
82
- headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(bodyStr) },
83
- }, (res) => {
84
- let data = '';
85
- res.on('data', (chunk) => data += chunk);
86
- res.on('end', () => {
87
- try { resolve(JSON.parse(data)); }
88
- catch { resolve({ raw: data }); }
89
- });
90
- });
91
- req.on('error', reject);
92
- req.on('timeout', () => { req.destroy(); reject(new Error('Request timeout after ' + timeoutMs + 'ms')); });
93
- req.write(bodyStr);
94
- req.end();
95
- });
48
+ /** Create SDK client */
49
+ function createClient(rpcUrl) {
50
+ return new aether.AetherClient({ rpcUrl });
96
51
  }
97
52
 
98
53
  function formatAether(lamports) {
@@ -110,22 +65,23 @@ function formatLargeNum(n) {
110
65
  }
111
66
 
112
67
  // ---------------------------------------------------------------------------
113
- // Core supply fetchers
68
+ // Core supply fetchers using SDK
114
69
  // ---------------------------------------------------------------------------
115
70
 
116
71
  /**
117
- * Fetch the total supply of AETH from the chain.
118
- * Uses the supply inflation schedule / mint account.
72
+ * Fetch the total supply of AETH from the chain using SDK.
73
+ * Makes real RPC call: GET /v1/supply
119
74
  */
120
75
  async function fetchTotalSupply(rpc) {
76
+ const client = createClient(rpc);
121
77
  try {
122
- // Primary: dedicated supply endpoint
123
- const res = await httpRequest(rpc, '/v1/supply');
124
- if (res && !res.error && (res.total !== undefined || res.supply !== undefined)) {
78
+ // Primary: SDK getSupply() → GET /v1/supply
79
+ const res = await client.getSupply();
80
+ if (res && (res.total !== undefined || res.supply !== undefined)) {
125
81
  return {
126
82
  total: BigInt(res.total || res.supply?.total || 0),
127
83
  circulating: BigInt(res.circulating || res.supply?.circulating || 0),
128
- nonCirculating: BigInt(res.non_circulating || res.supply?.non_circulating || 0),
84
+ nonCirculating: BigInt(res.non_circulating || res.nonCirculating || res.supply?.non_circulating || 0),
129
85
  source: 'rpc_v1_supply',
130
86
  };
131
87
  }
@@ -133,8 +89,8 @@ async function fetchTotalSupply(rpc) {
133
89
 
134
90
  // Fallback: fetch epoch info which contains total token count
135
91
  try {
136
- const epochInfo = await httpRequest(rpc, '/v1/epoch-info');
137
- if (epochInfo && !epochInfo.error) {
92
+ const epochInfo = await client.getEpochInfo();
93
+ if (epochInfo) {
138
94
  const totalStaked = BigInt(epochInfo.total_staked || 0);
139
95
  const rewardsPerEpoch = BigInt(epochInfo.rewards_per_epoch || '2000000000');
140
96
  const currentEpoch = BigInt(epochInfo.epoch || 0);
@@ -159,33 +115,27 @@ async function fetchTotalSupply(rpc) {
159
115
  }
160
116
 
161
117
  /**
162
- * Fetch staked supply by querying stake program accounts.
118
+ * Fetch staked supply by querying stake program accounts using SDK.
119
+ * Makes real RPC call: GET /v1/validators
163
120
  */
164
121
  async function fetchStakedSupply(rpc) {
122
+ const client = createClient(rpc);
165
123
  try {
166
- // Try stake program accounts count / total
167
- const res = await httpRequest(rpc, '/v1/stake/total');
168
- if (res && !res.error && res.total_staked !== undefined) {
169
- return BigInt(res.total_staked);
170
- }
171
- } catch { /* fall through */ }
172
-
173
- try {
174
- // Fallback: sum delegated stake across top validators
175
- const validators = await httpRequest(rpc, '/v1/validators?limit=50');
124
+ // SDK getValidators() GET /v1/validators
125
+ const validators = await client.getValidators();
176
126
  if (validators && Array.isArray(validators)) {
177
127
  let total = BigInt(0);
178
128
  for (const v of validators) {
179
- total += BigInt(v.delegated_stake || v.stake || 0);
129
+ total += BigInt(v.delegated_stake || v.stake || v.delegatedStake || 0);
180
130
  }
181
131
  return total;
182
132
  }
183
133
  } catch { /* fall through */ }
184
134
 
185
135
  try {
186
- // Last resort: epoch info staked amount
187
- const epochInfo = await httpRequest(rpc, '/v1/epoch-info');
188
- if (epochInfo && !epochInfo.error && epochInfo.total_staked) {
136
+ // Last resort: epoch info staked amount via SDK
137
+ const epochInfo = await client.getEpochInfo();
138
+ if (epochInfo && epochInfo.total_staked) {
189
139
  return BigInt(epochInfo.total_staked);
190
140
  }
191
141
  } catch { /* fall through */ }
@@ -194,10 +144,11 @@ async function fetchStakedSupply(rpc) {
194
144
  }
195
145
 
196
146
  /**
197
- * Estimate burned supply by querying accounts at known burn/mint addresses.
198
- * Uses a set of common Aether burn addresses.
147
+ * Estimate burned supply by querying accounts at known burn/mint addresses using SDK.
148
+ * Makes real RPC calls: GET /v1/account/<address>
199
149
  */
200
150
  async function fetchBurnedSupply(rpc) {
151
+ const client = createClient(rpc);
201
152
  const BURN_ADDRESSES = [
202
153
  'ATH1111111111111111111111111111111111111', // mint authority burn
203
154
  'ATH2222222222222222222222222222222222222', // zero authority
@@ -209,8 +160,9 @@ async function fetchBurnedSupply(rpc) {
209
160
  for (const addr of BURN_ADDRESSES) {
210
161
  try {
211
162
  const rawAddr = addr.startsWith('ATH') ? addr.slice(3) : addr;
212
- const account = await httpRequest(rpc, `/v1/account/${encodeURIComponent(rawAddr)}`);
213
- if (account && !account.error && account.lamports !== undefined && Number(account.lamports) > 0) {
163
+ // SDK getAccountInfo() GET /v1/account/<address>
164
+ const account = await client.getAccountInfo(rawAddr);
165
+ if (account && account.lamports !== undefined && Number(account.lamports) > 0) {
214
166
  totalBurned += BigInt(account.lamports);
215
167
  }
216
168
  } catch { /* skip inaccessible addresses */ }
@@ -222,10 +174,12 @@ async function fetchBurnedSupply(rpc) {
222
174
  /**
223
175
  * Fetch circulating supply = total - non-circulating (locked/vesting/burned).
224
176
  * Non-circulating includes: burn address, escrow/staking vault, team vesting.
177
+ * Uses SDK client for RPC calls.
225
178
  */
226
179
  async function fetchNonCirculatingAccounts(rpc) {
180
+ const client = createClient(rpc);
227
181
  try {
228
- const res = await httpRequest(rpc, '/v1/supply/non-circulating');
182
+ const res = await client._httpGet('/v1/supply/non-circulating');
229
183
  if (res && !res.error && Array.isArray(res.accounts)) {
230
184
  let total = BigInt(0);
231
185
  for (const acct of res.accounts) {
@@ -0,0 +1,238 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * aether-cli tps
4
+ *
5
+ * Monitor real-time transactions per second (TPS) on the Aether blockchain.
6
+ * Uses @jellylegsai/aether-sdk for real RPC calls to /v1/tps endpoint.
7
+ *
8
+ * Usage:
9
+ * aether tps Show current TPS
10
+ * aether tps --monitor Continuous monitoring (updates every 2s)
11
+ * aether tps --interval <sec> Custom interval for monitoring (default: 2)
12
+ * aether tps --json JSON output for scripting
13
+ * aether tps --rpc <url> Custom RPC endpoint
14
+ *
15
+ * Examples:
16
+ * aether tps # Single TPS reading
17
+ * aether tps --monitor # Live monitoring dashboard
18
+ * aether tps --monitor --interval 1 # Update every second
19
+ * aether tps --json # JSON for alerting/monitoring
20
+ */
21
+
22
+ const path = require('path');
23
+ const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
24
+ const aether = require(sdkPath);
25
+
26
+ // ANSI colours
27
+ const C = {
28
+ reset: '\x1b[0m',
29
+ bright: '\x1b[1m',
30
+ dim: '\x1b[2m',
31
+ red: '\x1b[31m',
32
+ green: '\x1b[32m',
33
+ yellow: '\x1b[33m',
34
+ cyan: '\x1b[36m',
35
+ magenta: '\x1b[35m',
36
+ };
37
+
38
+ const CLI_VERSION = '1.0.0';
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Helpers
42
+ // ---------------------------------------------------------------------------
43
+
44
+ function getDefaultRpc() {
45
+ return process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
46
+ }
47
+
48
+ function createClient(rpc) {
49
+ return new aether.AetherClient({ rpcUrl: rpc });
50
+ }
51
+
52
+ function tpsColor(tps) {
53
+ if (tps === null || tps === undefined) return C.red;
54
+ if (tps >= 1000) return C.green;
55
+ if (tps >= 100) return C.cyan;
56
+ if (tps >= 10) return C.yellow;
57
+ return C.red;
58
+ }
59
+
60
+ function tpsLabel(tps) {
61
+ if (tps === null || tps === undefined) return '✗ unreachable';
62
+ if (tps >= 1000) return `● ${tps.toLocaleString()} TPS (excellent)`;
63
+ if (tps >= 100) return `● ${tps.toLocaleString()} TPS (good)`;
64
+ if (tps >= 10) return `○ ${tps.toLocaleString()} TPS (fair)`;
65
+ return `○ ${tps.toLocaleString()} TPS (low)`;
66
+ }
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // Single TPS reading
70
+ // ---------------------------------------------------------------------------
71
+
72
+ async function getTpsOnce(rpcUrl) {
73
+ const client = createClient(rpcUrl);
74
+ const start = Date.now();
75
+ let tps = null;
76
+ let error = null;
77
+ let latencyMs = null;
78
+
79
+ try {
80
+ // Real RPC call: GET /v1/tps
81
+ tps = await client.getTPS();
82
+ latencyMs = Date.now() - start;
83
+ } catch (err) {
84
+ latencyMs = Date.now() - start;
85
+ error = err.message;
86
+ }
87
+
88
+ return { tps, error, latencyMs, rpcUrl, timestamp: new Date() };
89
+ }
90
+
91
+ // ---------------------------------------------------------------------------
92
+ // Continuous monitoring
93
+ // ---------------------------------------------------------------------------
94
+
95
+ async function monitorTps(rpcUrl, intervalSec) {
96
+ console.log(`\n${C.bright}${C.cyan}── Aether TPS Monitor ───────────────────────────────────${C.reset}`);
97
+ console.log(` ${C.dim}RPC: ${rpcUrl}${C.reset}`);
98
+ console.log(` ${C.dim}Interval: ${intervalSec}s${C.reset}`);
99
+ console.log(` ${C.dim}Press Ctrl+C to stop${C.reset}\n`);
100
+
101
+ const history = [];
102
+ const maxHistory = 20;
103
+
104
+ process.on('SIGINT', () => {
105
+ console.log(`\n${C.dim}Monitoring stopped.${C.reset}`);
106
+ if (history.length > 1) {
107
+ const tpsValues = history.map(h => h.tps).filter(t => t !== null);
108
+ if (tpsValues.length > 0) {
109
+ const avg = Math.round(tpsValues.reduce((a, b) => a + b, 0) / tpsValues.length);
110
+ const min = Math.min(...tpsValues);
111
+ const max = Math.max(...tpsValues);
112
+ console.log(` ${C.bright}Summary:${C.reset} avg=${avg} min=${min} max=${max} samples=${history.length}`);
113
+ }
114
+ }
115
+ console.log();
116
+ process.exit(0);
117
+ });
118
+
119
+ while (true) {
120
+ const result = await getTpsOnce(rpcUrl);
121
+ history.push(result);
122
+ if (history.length > maxHistory) history.shift();
123
+
124
+ // Clear line and print
125
+ process.stdout.write('\x1b[2K\r');
126
+
127
+ const tc = tpsColor(result.tps);
128
+ const barLen = result.tps !== null ? Math.min(30, Math.floor(result.tps / 50)) : 0;
129
+ const bar = tc + '█'.repeat(barLen) + C.dim + '░'.repeat(30 - barLen) + C.reset;
130
+
131
+ const slotInfo = result.tps !== null ? ` ${C.dim}RPC latency: ${result.latencyMs}ms${C.reset}` : '';
132
+
133
+ console.log(` ${tc}${bar}${C.reset} ${tpsLabel(result.tps)}${C.reset}${slotInfo}`);
134
+
135
+ // Show trend
136
+ if (history.length >= 3) {
137
+ const recent = history.slice(-3).map(h => h.tps).filter(t => t !== null);
138
+ if (recent.length >= 2) {
139
+ const trend = recent[recent.length - 1] - recent[0];
140
+ const arrow = trend > 0 ? C.green + '▲' : trend < 0 ? C.red + '▼' : C.dim + '─';
141
+ console.log(` ${C.dim}Trend: ${arrow} ${Math.abs(trend)} TPS${C.reset}`);
142
+ }
143
+ }
144
+
145
+ await new Promise(resolve => setTimeout(resolve, intervalSec * 1000));
146
+ }
147
+ }
148
+
149
+ // ---------------------------------------------------------------------------
150
+ // Main command
151
+ // ---------------------------------------------------------------------------
152
+
153
+ async function tpsCommand() {
154
+ const args = process.argv.slice(2);
155
+ const asJson = args.includes('--json') || args.includes('-j');
156
+ const isMonitor = args.includes('--monitor') || args.includes('-m');
157
+ const rpcIdx = args.findIndex(a => a === '--rpc' || a === '-r');
158
+ const rpcUrl = rpcIdx !== -1 && args[rpcIdx + 1] ? args[rpcIdx + 1] : getDefaultRpc();
159
+
160
+ const intervalIdx = args.findIndex(a => a === '--interval' || a === '-i');
161
+ const intervalSec = intervalIdx !== -1 && args[intervalIdx + 1]
162
+ ? Math.max(1, parseInt(args[intervalIdx + 1], 10) || 2)
163
+ : 2;
164
+
165
+ if (isMonitor) {
166
+ await monitorTps(rpcUrl, intervalSec);
167
+ return;
168
+ }
169
+
170
+ // Single reading
171
+ const result = await getTpsOnce(rpcUrl);
172
+
173
+ if (asJson) {
174
+ console.log(JSON.stringify({
175
+ rpc: rpcUrl,
176
+ tps: result.tps,
177
+ online: result.tps !== null,
178
+ latency_ms: result.latencyMs,
179
+ error: result.error || null,
180
+ timestamp: result.timestamp.toISOString(),
181
+ cli_version: CLI_VERSION,
182
+ }, null, 2));
183
+ return;
184
+ }
185
+
186
+ console.log(`\n${C.bright}${C.cyan}── Aether Network TPS ─────────────────────────────────${C.reset}\n`);
187
+
188
+ const tc = tpsColor(result.tps);
189
+ const barLen = result.tps !== null ? Math.min(40, Math.floor(result.tps / 50)) : 0;
190
+ const bar = tc + '█'.repeat(barLen) + C.dim + '░'.repeat(40 - barLen) + C.reset;
191
+
192
+ console.log(` ${C.dim}RPC:${C.reset} ${rpcUrl}`);
193
+ console.log(` ${C.dim}Latency:${C.reset} ${result.latencyMs}ms`);
194
+ console.log();
195
+ console.log(` ${C.bright}${tc}${tpsLabel(result.tps)}${C.reset}`);
196
+ console.log();
197
+ console.log(` ${bar}`);
198
+ console.log();
199
+
200
+ if (result.error) {
201
+ console.log(` ${C.red}✗ ${result.error}${C.reset}`);
202
+ console.log();
203
+ }
204
+
205
+ // Context info
206
+ if (result.tps !== null) {
207
+ console.log(` ${C.dim}Network Health:${C.reset}`);
208
+ if (result.tps >= 1000) {
209
+ console.log(` ${C.green}● Network is handling high throughput${C.reset}`);
210
+ } else if (result.tps >= 100) {
211
+ console.log(` ${C.cyan}● Network operating normally${C.reset}`);
212
+ } else if (result.tps >= 10) {
213
+ console.log(` ${C.yellow}○ Network has low activity${C.reset}`);
214
+ } else {
215
+ console.log(` ${C.red}○ Network is idle or experiencing issues${C.reset}`);
216
+ }
217
+ }
218
+
219
+ console.log();
220
+ console.log(` ${C.dim}Run ${C.cyan}aether tps --monitor${C.reset}${C.dim} for live tracking.${C.reset}\n`);
221
+
222
+ if (result.tps === null) {
223
+ process.exit(1);
224
+ }
225
+ }
226
+
227
+ // ---------------------------------------------------------------------------
228
+ // Entry point
229
+ // ---------------------------------------------------------------------------
230
+
231
+ module.exports = { tpsCommand };
232
+
233
+ if (require.main === module) {
234
+ tpsCommand().catch(err => {
235
+ console.error(`\n${C.red}TPS command failed:${C.reset} ${err.message}\n`);
236
+ process.exit(1);
237
+ });
238
+ }