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/epoch.js CHANGED
@@ -14,8 +14,7 @@
14
14
  * Requires AETHER_RPC env var (default: http://127.0.0.1:8899)
15
15
  */
16
16
 
17
- const http = require('http');
18
- const https = require('https');
17
+ const path = require('path');
19
18
 
20
19
  // ANSI colours
21
20
  const C = {
@@ -32,68 +31,14 @@ const C = {
32
31
  const CLI_VERSION = '1.0.0';
33
32
 
34
33
  // ---------------------------------------------------------------------------
35
- // HTTP helpers
34
+ // SDK Import - Real blockchain RPC calls via @jellylegsai/aether-sdk
36
35
  // ---------------------------------------------------------------------------
37
36
 
38
- function httpRequest(rpcUrl, pathStr, timeoutMs) {
39
- timeoutMs = timeoutMs || 8000;
40
- return new Promise(function(resolve, reject) {
41
- const url = new URL(pathStr, rpcUrl);
42
- const lib = url.protocol === 'https:' ? https : http;
43
- const req = lib.request({
44
- hostname: url.hostname,
45
- port: url.port || (url.protocol === 'https:' ? 443 : 80),
46
- path: url.pathname + url.search,
47
- method: 'GET',
48
- timeout: timeoutMs,
49
- headers: { 'Content-Type': 'application/json' },
50
- }, function(res) {
51
- let data = '';
52
- res.on('data', function(chunk) { data += chunk; });
53
- res.on('end', function() {
54
- try { resolve(JSON.parse(data)); }
55
- catch { resolve({ raw: data }); }
56
- });
57
- });
58
- req.on('error', reject);
59
- req.on('timeout', function() { req.destroy(); reject(new Error('Request timeout')); });
60
- req.end();
61
- });
62
- }
63
-
64
- function httpPost(rpcUrl, pathStr, body, timeoutMs) {
65
- timeoutMs = timeoutMs || 8000;
66
- return new Promise(function(resolve, reject) {
67
- const url = new URL(pathStr, rpcUrl);
68
- const lib = url.protocol === 'https:' ? https : http;
69
- const bodyStr = JSON.stringify(body);
70
- const req = lib.request({
71
- hostname: url.hostname,
72
- port: url.port || (url.protocol === 'https:' ? 443 : 80),
73
- path: url.pathname + url.search,
74
- method: 'POST',
75
- timeout: timeoutMs,
76
- headers: {
77
- 'Content-Type': 'application/json',
78
- 'Content-Length': Buffer.byteLength(bodyStr),
79
- },
80
- }, function(res) {
81
- let data = '';
82
- res.on('data', function(chunk) { data += chunk; });
83
- res.on('end', function() {
84
- try { resolve(JSON.parse(data)); }
85
- catch { resolve({ raw: data }); }
86
- });
87
- });
88
- req.on('error', reject);
89
- req.on('timeout', function() { req.destroy(); reject(new Error('Request timeout')); });
90
- req.write(bodyStr);
91
- req.end();
92
- });
93
- }
37
+ const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
38
+ const aether = require(sdkPath);
94
39
 
95
40
  function getDefaultRpc() {
96
- return process.env.AETHER_RPC || 'http://127.0.0.1:8899';
41
+ return process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
97
42
  }
98
43
 
99
44
  // ---------------------------------------------------------------------------
@@ -111,46 +56,19 @@ function parseArgs() {
111
56
  }
112
57
 
113
58
  // ---------------------------------------------------------------------------
114
- // Fetch epoch info from RPC
59
+ // Fetch epoch info from RPC using SDK
115
60
  // ---------------------------------------------------------------------------
116
61
 
117
62
  async function fetchEpochInfo(rpc) {
118
- // Try Aether-native endpoint first
119
- try {
120
- const epochInfo = await httpRequest(rpc, '/v1/epoch-info');
121
- if (epochInfo && !epochInfo.error && (epochInfo.epoch !== undefined || epochInfo.current_epoch)) {
122
- return { data: epochInfo, source: 'aether' };
123
- }
124
- } catch(e) {
125
- // fall through
126
- }
127
-
128
- // Fallback: try Solana-compat JSON-RPC
129
- try {
130
- const result = await httpPost(rpc, '/', {
131
- jsonrpc: '2.0',
132
- id: 1,
133
- method: 'getEpochInfo',
134
- });
135
- if (result && result.result) {
136
- return { data: result.result, source: 'solana-compat' };
137
- }
138
- } catch(e) {
139
- // fall through
140
- }
141
-
142
- // Try getEpochSchedule
63
+ // Use SDK for real blockchain RPC calls
64
+ const client = new aether.AetherClient({ rpcUrl: rpc });
143
65
  try {
144
- const schedule = await httpPost(rpc, '/', {
145
- jsonrpc: '2.0',
146
- id: 1,
147
- method: 'getEpochSchedule',
148
- });
149
- if (schedule && schedule.result) {
150
- return { data: schedule.result, source: 'schedule-only' };
66
+ const epochInfo = await client.getEpochInfo();
67
+ if (epochInfo && (epochInfo.epoch !== undefined || epochInfo.current_epoch)) {
68
+ return { data: epochInfo, source: 'aether-sdk' };
151
69
  }
152
70
  } catch(e) {
153
- // fall through
71
+ throw new Error('Failed to fetch epoch info from RPC. Is your validator running?');
154
72
  }
155
73
 
156
74
  throw new Error('Failed to fetch epoch info from RPC. Is your validator running?');
@@ -0,0 +1,276 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * aether-cli fees
4
+ *
5
+ * Query current network fee estimates for Aether transactions.
6
+ * Shows priority fee tiers (low, medium, high) and recent average fees.
7
+ * Uses @jellylegsai/aether-sdk for real RPC calls to /v1/fees.
8
+ *
9
+ * Usage:
10
+ * aether fees Show current fee estimates
11
+ * aether fees --json JSON output for scripting
12
+ * aether fees --verbose Show detailed fee breakdown
13
+ * aether fees --rpc <url> Custom RPC endpoint
14
+ */
15
+
16
+ const path = require('path');
17
+ const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
18
+ const aether = require(sdkPath);
19
+
20
+ // ANSI colours
21
+ const C = {
22
+ reset: '\x1b[0m',
23
+ bright: '\x1b[1m',
24
+ dim: '\x1b[2m',
25
+ red: '\x1b[31m',
26
+ green: '\x1b[32m',
27
+ yellow: '\x1b[33m',
28
+ cyan: '\x1b[36m',
29
+ magenta: '\x1b[35m',
30
+ };
31
+
32
+ // Fee priority levels
33
+ const PRIORITY_LEVELS = {
34
+ LOW: 'low',
35
+ MEDIUM: 'medium',
36
+ HIGH: 'high',
37
+ TURBO: 'turbo',
38
+ };
39
+
40
+ /**
41
+ * Fetch fee data from Aether RPC endpoint using SDK
42
+ * Real RPC call: GET /v1/fees
43
+ */
44
+ async function fetchFromRpc(rpcUrl) {
45
+ const client = new aether.AetherClient({ rpcUrl });
46
+ try {
47
+ const fees = await client.getFees();
48
+ if (fees && fees.fee !== undefined) {
49
+ return {
50
+ baseFee: fees.baseFee || fees.fee || 5000,
51
+ prioritizationFee: fees.prioritizationFee || 0,
52
+ totalFee: fees.totalFee || fees.fee || 5000,
53
+ raw: fees,
54
+ };
55
+ }
56
+ return null;
57
+ } catch {
58
+ return null;
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Generate simulated fee estimates as fallback
64
+ * Used when RPC endpoint is unavailable
65
+ */
66
+ function generateSimulatedFees() {
67
+ const baseFee = 5000;
68
+ const congestionFactor = 1.0 + (Math.random() * 0.5);
69
+
70
+ return {
71
+ baseFee,
72
+ levels: {
73
+ [PRIORITY_LEVELS.LOW]: {
74
+ lamports: Math.round(baseFee * 1.0 * congestionFactor),
75
+ aeth: (baseFee * 1.0 * congestionFactor / 1e9).toFixed(9),
76
+ description: 'Economy - may take longer during congestion',
77
+ color: C.green,
78
+ },
79
+ [PRIORITY_LEVELS.MEDIUM]: {
80
+ lamports: Math.round(baseFee * 1.5 * congestionFactor),
81
+ aeth: (baseFee * 1.5 * congestionFactor / 1e9).toFixed(9),
82
+ description: 'Standard - typical confirmation time',
83
+ color: C.cyan,
84
+ },
85
+ [PRIORITY_LEVELS.HIGH]: {
86
+ lamports: Math.round(baseFee * 2.5 * congestionFactor),
87
+ aeth: (baseFee * 2.5 * congestionFactor / 1e9).toFixed(9),
88
+ description: 'Fast - priority during high congestion',
89
+ color: C.yellow,
90
+ },
91
+ [PRIORITY_LEVELS.TURBO]: {
92
+ lamports: Math.round(baseFee * 5.0 * congestionFactor),
93
+ aeth: (baseFee * 5.0 * congestionFactor / 1e9).toFixed(9),
94
+ description: 'Maximum - fastest confirmation',
95
+ color: C.magenta,
96
+ },
97
+ },
98
+ averageFee24h: Math.round(baseFee * 1.8 * congestionFactor),
99
+ medianFee24h: Math.round(baseFee * 1.5 * congestionFactor),
100
+ source: 'Aether Network (simulated)',
101
+ timestamp: new Date(),
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Build fee data from RPC response
107
+ */
108
+ function buildFeeData(rpcFees) {
109
+ const baseFee = rpcFees.baseFee || 5000;
110
+ const totalFee = rpcFees.totalFee || baseFee;
111
+
112
+ return {
113
+ baseFee,
114
+ levels: {
115
+ [PRIORITY_LEVELS.LOW]: {
116
+ lamports: totalFee,
117
+ aeth: (totalFee / 1e9).toFixed(9),
118
+ description: 'Economy',
119
+ color: C.green,
120
+ },
121
+ [PRIORITY_LEVELS.MEDIUM]: {
122
+ lamports: Math.round(totalFee * 1.5),
123
+ aeth: (totalFee * 1.5 / 1e9).toFixed(9),
124
+ description: 'Standard',
125
+ color: C.cyan,
126
+ },
127
+ [PRIORITY_LEVELS.HIGH]: {
128
+ lamports: Math.round(totalFee * 2.5),
129
+ aeth: (totalFee * 2.5 / 1e9).toFixed(9),
130
+ description: 'Fast',
131
+ color: C.yellow,
132
+ },
133
+ [PRIORITY_LEVELS.TURBO]: {
134
+ lamports: Math.round(totalFee * 5),
135
+ aeth: (totalFee * 5 / 1e9).toFixed(9),
136
+ description: 'Maximum',
137
+ color: C.magenta,
138
+ },
139
+ },
140
+ averageFee24h: totalFee,
141
+ medianFee24h: totalFee,
142
+ source: 'Aether RPC',
143
+ timestamp: new Date(),
144
+ };
145
+ }
146
+
147
+ /**
148
+ * Format lamports to human-readable string
149
+ */
150
+ function formatLamports(lamports) {
151
+ if (lamports >= 1e9) {
152
+ return `${(lamports / 1e9).toFixed(6)} AETH`;
153
+ } else if (lamports >= 1e6) {
154
+ return `${(lamports / 1e6).toFixed(3)} mAETH`;
155
+ } else if (lamports >= 1e3) {
156
+ return `${(lamports / 1e3).toFixed(1)} µAETH`;
157
+ }
158
+ return `${lamports} lamports`;
159
+ }
160
+
161
+ /**
162
+ * Format timestamp
163
+ */
164
+ function formatTime(date) {
165
+ return date.toISOString().replace('T', ' ').substring(0, 19) + ' UTC';
166
+ }
167
+
168
+ /**
169
+ * Main fees command
170
+ */
171
+ async function feesCommand() {
172
+ const args = process.argv.slice(2);
173
+ const asJson = args.includes('--json') || args.includes('-j');
174
+ const verbose = args.includes('--verbose') || args.includes('-v');
175
+ const rpcUrl = args.includes('--rpc')
176
+ ? args[args.indexOf('--rpc') + 1]
177
+ : process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
178
+
179
+ if (!asJson) {
180
+ console.log(`\n${C.bright}${C.cyan}── Aether Network Fees ──────────────────────────────────${C.reset}\n`);
181
+ }
182
+
183
+ // Fetch real fee data from RPC using SDK
184
+ let feeData = null;
185
+ let source = 'Simulated';
186
+
187
+ try {
188
+ const rpcFees = await fetchFromRpc(rpcUrl);
189
+ if (rpcFees) {
190
+ feeData = buildFeeData(rpcFees);
191
+ source = 'Aether RPC';
192
+ }
193
+ } catch { /* RPC not available */ }
194
+
195
+ // Use simulated fees as fallback
196
+ if (!feeData) {
197
+ feeData = generateSimulatedFees();
198
+ source = 'Simulated';
199
+ }
200
+
201
+ // JSON output
202
+ if (asJson) {
203
+ const output = {
204
+ source: feeData.source,
205
+ timestamp: formatTime(feeData.timestamp),
206
+ base_fee_lamports: feeData.baseFee,
207
+ priority_levels: Object.fromEntries(
208
+ Object.entries(feeData.levels).map(([key, val]) => [
209
+ key,
210
+ { lamports: val.lamports, aeth: parseFloat(val.aeth) }
211
+ ])
212
+ ),
213
+ average_fee_24h_lamports: feeData.averageFee24h,
214
+ median_fee_24h_lamports: feeData.medianFee24h,
215
+ };
216
+ console.log(JSON.stringify(output, null, 2));
217
+ return;
218
+ }
219
+
220
+ // Human-readable output
221
+ console.log(` ${C.dim}Source:${C.reset} ${C.bright}${feeData.source}${C.reset}`);
222
+ console.log(` ${C.dim}Updated:${C.reset} ${formatTime(feeData.timestamp)}`);
223
+ console.log(` ${C.dim}Base Fee:${C.reset} ${formatLamports(feeData.baseFee)}`);
224
+ console.log();
225
+
226
+ console.log(` ${C.bright}Priority Levels:${C.reset}\n`);
227
+ console.log(` ┌─────────────┬──────────────────────┬─────────────────────────────────────┐`);
228
+ console.log(` │ ${C.bright}Level${C.reset} │ ${C.bright}Fee${C.reset} │ ${C.bright}Description${C.reset} │`);
229
+ console.log(` ├─────────────┼──────────────────────┼─────────────────────────────────────┤`);
230
+
231
+ Object.entries(feeData.levels).forEach(([level, info]) => {
232
+ const levelName = level.charAt(0) + level.slice(1).toLowerCase();
233
+ const feeStr = `${info.color}${formatLamports(info.lamports).padEnd(20)}${C.reset}`;
234
+ const descStr = info.description.padEnd(35);
235
+ console.log(` │ ${levelName.padEnd(11)} │ ${feeStr} │ ${descStr} │`);
236
+ });
237
+
238
+ console.log(` └─────────────┴──────────────────────┴─────────────────────────────────────┘`);
239
+ console.log();
240
+
241
+ // 24h statistics
242
+ console.log(` ${C.dim}24h Statistics:${C.reset}`);
243
+ console.log(` ${C.dim}Average:${C.reset} ${formatLamports(feeData.averageFee24h)}`);
244
+ console.log(` ${C.dim}Median:${C.reset} ${formatLamports(feeData.medianFee24h)}`);
245
+ console.log();
246
+
247
+ // Verbose mode - show additional details
248
+ if (verbose) {
249
+ console.log(` ${C.bright}Fee Breakdown:${C.reset}\n`);
250
+ console.log(` ${C.dim}Base Fee:${C.reset} ${formatLamports(feeData.baseFee)}`);
251
+ console.log(` ${C.dim}Priority Multiplier:${C.reset} 1.0x - 5.0x (based on urgency)`);
252
+ console.log(` ${C.dim}Network Congestion:${C.reset} ${Math.round((feeData.averageFee24h / feeData.baseFee - 1) * 100)}% above base`);
253
+ console.log();
254
+
255
+ console.log(` ${C.bright}Recommendations:${C.reset}\n`);
256
+ console.log(` • Use ${C.cyan}Standard${C.reset} for routine transactions`);
257
+ console.log(` • Use ${C.yellow}Fast${C.reset} during network congestion or for time-sensitive ops`);
258
+ console.log(` • Use ${C.magenta}Maximum${C.reset} for validator operations or large transfers`);
259
+ console.log();
260
+ }
261
+
262
+ // Usage tip
263
+ console.log(` ${C.dim}Tip: Set ${C.cyan}--priority${C.reset}${C.dim} flag when submitting transactions to choose fee level.${C.reset}`);
264
+ console.log(` ${C.dim}Example: ${C.cyan}aether transfer --to <addr> --amount 10 --priority high${C.reset}\n`);
265
+ }
266
+
267
+ // Export for use in index.js
268
+ module.exports = { feesCommand };
269
+
270
+ // Run if called directly
271
+ if (require.main === module) {
272
+ feesCommand().catch(err => {
273
+ console.error(`\n${C.red}Fees error:${C.reset} ${err.message}\n`);
274
+ process.exit(1);
275
+ });
276
+ }
package/commands/info.js CHANGED
@@ -19,8 +19,10 @@
19
19
  const fs = require('fs');
20
20
  const path = require('path');
21
21
  const os = require('os');
22
- const http = require('http');
23
- const https = require('https');
22
+
23
+ // Import SDK for real blockchain RPC calls
24
+ const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
25
+ const aether = require(sdkPath);
24
26
 
25
27
  // ANSI colours
26
28
  const C = {
@@ -59,57 +61,15 @@ function loadConfig() {
59
61
  }
60
62
 
61
63
  // ---------------------------------------------------------------------------
62
- // HTTP helpers
64
+ // SDK helpers - Real blockchain RPC calls via Aether SDK
63
65
  // ---------------------------------------------------------------------------
64
66
 
65
- function httpRequest(rpcUrl, pathStr) {
66
- return new Promise((resolve, reject) => {
67
- const url = new URL(pathStr, rpcUrl);
68
- const lib = url.protocol === 'https:' ? https : http;
69
- const req = lib.request({
70
- hostname: url.hostname,
71
- port: url.port || (url.protocol === 'https:' ? 443 : 80),
72
- path: url.pathname + url.search,
73
- method: 'GET',
74
- timeout: 8000,
75
- headers: { 'Content-Type': 'application/json' },
76
- }, (res) => {
77
- let data = '';
78
- res.on('data', (chunk) => data += chunk);
79
- res.on('end', () => { try { resolve(JSON.parse(data)); } catch { resolve({ raw: data }); } });
80
- });
81
- req.on('error', reject);
82
- req.on('timeout', () => { req.destroy(); reject(new Error('Request timeout')); });
83
- req.end();
84
- });
85
- }
86
-
87
- function httpPost(rpcUrl, pathStr, body) {
88
- return new Promise((resolve, reject) => {
89
- const url = new URL(pathStr, rpcUrl);
90
- const lib = url.protocol === 'https:' ? https : http;
91
- const bodyStr = JSON.stringify(body);
92
- const req = lib.request({
93
- hostname: url.hostname,
94
- port: url.port || (url.protocol === 'https:' ? 443 : 80),
95
- path: url.pathname + url.search,
96
- method: 'POST',
97
- timeout: 15000,
98
- headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(bodyStr) },
99
- }, (res) => {
100
- let data = '';
101
- res.on('data', (chunk) => data += chunk);
102
- res.on('end', () => { try { resolve(JSON.parse(data)); } catch { resolve({ raw: data }); } });
103
- });
104
- req.on('error', reject);
105
- req.on('timeout', () => { req.destroy(); reject(new Error('Request timeout')); });
106
- req.write(bodyStr);
107
- req.end();
108
- });
67
+ function getDefaultRpc() {
68
+ return process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
109
69
  }
110
70
 
111
- function getDefaultRpc() {
112
- return process.env.AETHER_RPC || 'http://127.0.0.1:8899';
71
+ function createClient(rpcUrl) {
72
+ return new aether.AetherClient({ rpcUrl });
113
73
  }
114
74
 
115
75
  function formatAether(lamports) {
@@ -157,7 +117,8 @@ async function getIdentity(rpcUrl) {
157
117
 
158
118
  if (identity && identity.vote_account) {
159
119
  try {
160
- const res = await httpRequest(rpcUrl, `/v1/account/${identity.vote_account}`);
120
+ const client = createClient(rpcUrl);
121
+ const res = await client.getAccountInfo(identity.vote_account);
161
122
  if (res && !res.error) {
162
123
  delegatedStake = res.lamports || 0;
163
124
  stakeStatus = 'active';
@@ -191,44 +152,41 @@ async function getNetworkStatus(rpcUrl) {
191
152
  let totalPeers = 0;
192
153
 
193
154
  try {
155
+ const client = createClient(rpcUrl);
194
156
  const results = await Promise.allSettled([
195
- httpRequest(rpcUrl, '/v1/slot'),
196
- httpRequest(rpcUrl, '/v1/epoch-info'),
197
- httpRequest(rpcUrl, '/v1/block-height'),
198
- httpRequest(rpcUrl, '/v1/peers'),
199
- httpRequest(rpcUrl, '/v1/perf?limit=10'),
157
+ client.getSlot(),
158
+ client.getEpochInfo(),
159
+ client.getBlockHeight(),
160
+ client.getClusterPeers(),
161
+ { status: 'rejected', reason: new Error('Perf endpoint not in SDK') },
200
162
  ]);
201
163
 
202
- const [slotRes, epochRes, blockHeightRes, peersRes, perfRes] = results;
164
+ const [slotRes, epochRes, blockHeightRes, peersRes] = results;
203
165
 
204
- if (slotRes.status === 'fulfilled' && slotRes.value && !slotRes.value.error) {
205
- slot = slotRes.value.slot ?? slotRes.value;
206
- if (typeof slot === 'object') slot = slot.slot;
166
+ if (slotRes.status === 'fulfilled' && slotRes.value !== null) {
167
+ slot = typeof slotRes.value === 'object' ? slotRes.value.slot : slotRes.value;
207
168
  }
208
- if (epochRes.status === 'fulfilled' && epochRes.value && !epochRes.value.error) {
169
+ if (epochRes.status === 'fulfilled' && epochRes.value) {
209
170
  const ei = epochRes.value;
210
171
  epoch = ei.epoch;
211
- slotIndex = ei.slot_index;
212
- slotsInEpoch = ei.slots_in_epoch;
213
- blockTime = ei.block_time ?? null;
172
+ slotIndex = ei.slotIndex ?? ei.slot_index;
173
+ slotsInEpoch = ei.slotsInEpoch ?? ei.slots_in_epoch;
174
+ blockTime = ei.blockTime ?? ei.block_time ?? null;
214
175
  }
215
- if (blockHeightRes.status === 'fulfilled' && blockHeightRes.value && !blockHeightRes.value.error) {
176
+ if (blockHeightRes.status === 'fulfilled' && blockHeightRes.value !== null) {
216
177
  blockHeight = typeof blockHeightRes.value === 'object'
217
- ? blockHeightRes.value.block_height
178
+ ? blockHeightRes.value.blockHeight
218
179
  : blockHeightRes.value;
219
180
  }
220
- if (peersRes.status === 'fulfilled' && peersRes.value && !peersRes.value.error) {
221
- const pr = peersRes.value;
222
- peers = Array.isArray(pr) ? pr : (pr.peers || []);
181
+ if (peersRes.status === 'fulfilled' && peersRes.value) {
182
+ peers = Array.isArray(peersRes.value) ? peersRes.value : (peersRes.value.peers || []);
223
183
  totalPeers = peers.length;
224
184
  }
225
- if (perfRes.status === 'fulfilled' && perfRes.value && !perfRes.value.error && Array.isArray(perfRes.value)) {
226
- const samples = perfRes.value.slice(-5);
227
- if (samples.length > 0) {
228
- const totalTPS = samples.reduce((sum, s) => sum + (s.tps || 0), 0);
229
- TPS = Math.round(totalTPS / samples.length);
230
- }
231
- }
185
+
186
+ // Get TPS from SDK
187
+ try {
188
+ TPS = await client.getTPS();
189
+ } catch { /* TPS not available */ }
232
190
  } catch { /* Network info unavailable */ }
233
191
 
234
192
  let syncState = 'unknown';
@@ -278,12 +236,13 @@ async function getStakeSummary(rpcUrl) {
278
236
  const walletAddr = cfg.defaultWallet;
279
237
 
280
238
  if (walletAddr) {
239
+ const client = createClient(rpcUrl);
281
240
  const rawAddr = walletAddr.startsWith('ATH') ? walletAddr.slice(3) : walletAddr;
282
- const res = await httpRequest(rpcUrl, `/v1/stake?address=${encodeURIComponent(rawAddr)}`);
241
+ const res = await client.getStakePositions(rawAddr);
283
242
  if (res && !res.error) {
284
- const accounts = Array.isArray(res) ? res : (res.accounts || []);
243
+ const accounts = Array.isArray(res) ? res : (res.accounts || res.stakes || res.delegations || []);
285
244
  for (const acc of accounts) {
286
- const lamports = BigInt(acc.stake_lamports || acc.lamports || 0);
245
+ const lamports = BigInt(acc.stake_lamports || acc.lamports || acc.amount || 0);
287
246
  const status = (acc.status || acc.state || 'active').toLowerCase();
288
247
  const validator = acc.validator || acc.voter || acc.vote_account || 'unknown';
289
248
 
@@ -292,7 +251,7 @@ async function getStakeSummary(rpcUrl) {
292
251
 
293
252
  totalDelegated += lamports;
294
253
  positions.push({
295
- stake_account: acc.pubkey || acc.publicKey || acc.account || 'unknown',
254
+ stake_account: acc.pubkey || acc.publicKey || acc.account || acc.stakeAccount || 'unknown',
296
255
  validator,
297
256
  lamports: lamports.toString(),
298
257
  lamports_formatted: formatAether(lamports.toString()),