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
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
|
|
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
|
-
//
|
|
34
|
+
// SDK Import - Real blockchain RPC calls via @jellylegsai/aether-sdk
|
|
36
35
|
// ---------------------------------------------------------------------------
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
//
|
|
119
|
-
|
|
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
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
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?');
|
package/commands/fees.js
ADDED
|
@@ -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
|
-
|
|
23
|
-
|
|
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
|
-
//
|
|
64
|
+
// SDK helpers - Real blockchain RPC calls via Aether SDK
|
|
63
65
|
// ---------------------------------------------------------------------------
|
|
64
66
|
|
|
65
|
-
function
|
|
66
|
-
return
|
|
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
|
|
112
|
-
return
|
|
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
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
|
164
|
+
const [slotRes, epochRes, blockHeightRes, peersRes] = results;
|
|
203
165
|
|
|
204
|
-
if (slotRes.status === 'fulfilled' && slotRes.value
|
|
205
|
-
slot = slotRes.value.slot
|
|
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
|
|
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
|
|
176
|
+
if (blockHeightRes.status === 'fulfilled' && blockHeightRes.value !== null) {
|
|
216
177
|
blockHeight = typeof blockHeightRes.value === 'object'
|
|
217
|
-
? blockHeightRes.value.
|
|
178
|
+
? blockHeightRes.value.blockHeight
|
|
218
179
|
: blockHeightRes.value;
|
|
219
180
|
}
|
|
220
|
-
if (peersRes.status === 'fulfilled' && peersRes.value
|
|
221
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
|
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()),
|