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/price.js CHANGED
@@ -1,253 +1,253 @@
1
- #!/usr/bin/env node
2
- /**
3
- * aether-cli price
4
- *
5
- * Show real-time AETH/USD price from free public crypto APIs.
6
- * Supports CoinGecko (free tier), and falls back to simulated data
7
- * if no API key is available.
8
- *
9
- * Usage:
10
- * aether price Show current AETH/USD price
11
- * aether price --pair AETH/USD Specify trading pair (default: AETH/USD)
12
- * aether price --json JSON output for scripting
13
- * aether price --source coingecko Fallback to CoinGecko (no API key needed)
14
- */
15
-
16
- const https = require('https');
17
- const http = require('http');
18
-
19
- const C = {
20
- reset: '\x1b[0m',
21
- bright: '\x1b[1m',
22
- dim: '\x1b[2m',
23
- red: '\x1b[31m',
24
- green: '\x1b[32m',
25
- yellow: '\x1b[33m',
26
- cyan: '\x1b[36m',
27
- magenta: '\x1b[35m',
28
- };
29
-
30
- const AETHER_CONTRACT = 'ATH'; // Aether token contract (hypothetical)
31
- const DEFAULT_PAIR = 'AETH/USD';
32
-
33
- function httpGet(url) {
34
- return new Promise((resolve, reject) => {
35
- const lib = url.startsWith('https') ? https : http;
36
- const parsed = new URL(url);
37
- const req = lib.request({
38
- hostname: parsed.hostname,
39
- port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
40
- path: parsed.pathname + parsed.search,
41
- method: 'GET',
42
- timeout: 10000,
43
- headers: { 'Accept': 'application/json', 'User-Agent': 'Aether-CLI/1.0' },
44
- }, (res) => {
45
- let data = '';
46
- res.on('data', (chunk) => data += chunk);
47
- res.on('end', () => {
48
- try { resolve(JSON.parse(data)); }
49
- catch { resolve({ _raw: data }); }
50
- });
51
- });
52
- req.on('error', reject);
53
- req.on('timeout', () => { req.destroy(); reject(new Error('Request timeout')); });
54
- req.end();
55
- });
56
- }
57
-
58
- function formatPrice(num, decimals = 4) {
59
- if (num === null || num === undefined || isNaN(num)) return '—';
60
- return num.toFixed(decimals);
61
- }
62
-
63
- function formatTime(date) {
64
- return date.toISOString().replace('T', ' ').substring(0, 19) + ' UTC';
65
- }
66
-
67
- /**
68
- * Fetch AETH price from CoinGecko (free, no API key).
69
- * Coins API: /coins/list → find ATH/AETH → /coins/{id}/market_chart
70
- * Fallback: try known Aether token addresses on major DEXes via /simple/price
71
- */
72
- async function fetchFromCoinGecko() {
73
- try {
74
- // Try CoinGecko simple price for AETH token
75
- // We'll try a few known Aether token addresses on Ethereum mainnet as a proxy
76
- const url = 'https://api.coingecko.com/api/v3/simple/price?vs_currencies=usd&ids=aether,ath,ath-token,aether-network';
77
- const data = await httpGet(url);
78
-
79
- if (data && !data.error) {
80
- // Find first available AETH quote
81
- const pairs = [
82
- { key: 'aether-network', name: 'AETH' },
83
- { key: 'aether', name: 'AETH' },
84
- { key: 'ath-token', name: 'ATH' },
85
- { key: 'ath', name: 'ATH' },
86
- ];
87
-
88
- for (const { key, name } of pairs) {
89
- if (data[key] && data[key].usd !== undefined) {
90
- return {
91
- source: 'CoinGecko',
92
- symbol: name,
93
- price: data[key].usd,
94
- currency: 'USD',
95
- timestamp: new Date(),
96
- };
97
- }
98
- }
99
- }
100
-
101
- return null;
102
- } catch {
103
- return null;
104
- }
105
- }
106
-
107
- /**
108
- * Fetch price data from a public DEX aggregator or mock Aether RPC.
109
- * Primary: use the Aether chain's own price oracle if available.
110
- * Fallback: use CoinGecko.
111
- */
112
- async function fetchAetherPrice() {
113
- // Try Aether chain's built-in price oracle (if validator is running)
114
- const rpcUrl = process.env.AETHER_RPC || 'http://127.0.0.1:8899';
115
- try {
116
- const res = await httpGet(`${rpcUrl}/v1/price`);
117
- if (res && !res.error && res.price !== undefined) {
118
- return {
119
- source: 'Aether Oracle',
120
- symbol: 'AETH',
121
- price: parseFloat(res.price),
122
- currency: res.currency || 'USD',
123
- timestamp: new Date(),
124
- change_24h: res.change_24h || null,
125
- volume_24h: res.volume_24h || null,
126
- };
127
- }
128
- } catch { /* chain oracle not available */ }
129
-
130
- // Try CoinGecko
131
- const cg = await fetchFromCoinGecko();
132
- if (cg) return cg;
133
-
134
- return null;
135
- }
136
-
137
- /**
138
- * Fetch 24h price change using CoinGecko market chart.
139
- */
140
- async function fetchPriceChange24h(symbol) {
141
- try {
142
- const idMap = {
143
- 'AETH': 'aether-network',
144
- 'ATH': 'ath-token',
145
- };
146
- const id = idMap[symbol] || 'aether-network';
147
- const url = `https://api.coingecko.com/api/v3/coins/${id}/market_chart?vs_currency=usd&days=1`;
148
- const data = await httpGet(url);
149
-
150
- if (data && data.prices && data.prices.length >= 2) {
151
- const latest = data.prices[data.prices.length - 1][1];
152
- const yesterday = data.prices[0][1];
153
- const change = ((latest - yesterday) / yesterday) * 100;
154
- const volume = data.total_volumes ? data.total_volumes[data.total_volumes.length - 1][1] : null;
155
- return {
156
- change_24h: change,
157
- volume_24h: volume,
158
- };
159
- }
160
- } catch { /* ignore */ }
161
- return { change_24h: null, volume_24h: null };
162
- }
163
-
164
- async function priceCommand() {
165
- const args = process.argv.slice(2);
166
- const asJson = args.includes('--json') || args.includes('-j');
167
- const pair = args.includes('--pair')
168
- ? args[args.indexOf('--pair') + 1] || DEFAULT_PAIR
169
- : DEFAULT_PAIR;
170
- const source = args.includes('--source') ? args[args.indexOf('--source') + 1] : null;
171
-
172
- // Parse pair
173
- const [fromSymbol, toSymbol = 'USD'] = pair.split('/');
174
- const symbol = fromSymbol.toUpperCase();
175
-
176
- console.log(`\n${C.bright}${C.cyan}── Aether Price ───────────────────────────────────────${C.reset}\n`);
177
-
178
- let priceData;
179
- if (source === 'coingecko') {
180
- priceData = await fetchFromCoinGecko();
181
- } else {
182
- priceData = await fetchAetherPrice();
183
- }
184
-
185
- if (!priceData) {
186
- if (asJson) {
187
- console.log(JSON.stringify({ error: 'Price data unavailable', symbol, pair }, null, 2));
188
- } else {
189
- console.log(` ${C.yellow}⚠ Price data temporarily unavailable.${C.reset}`);
190
- console.log(` ${C.dim}Make sure your validator is running or check network connectivity.${C.reset}`);
191
- console.log(` ${C.dim}Set AETHER_RPC env var to your validator's RPC address.${C.reset}`);
192
- console.log(` ${C.dim}Fallback: aether price --source coingecko${C.reset}\n`);
193
- }
194
- return;
195
- }
196
-
197
- // Fetch 24h change if available
198
- const changeData = await fetchPriceChange24h(symbol);
199
- const priceInfo = { ...priceData, ...changeData };
200
-
201
- if (asJson) {
202
- console.log(JSON.stringify({
203
- symbol: priceInfo.symbol,
204
- pair: `${symbol}/USD`,
205
- price_usd: priceInfo.price,
206
- change_24h_pct: priceInfo.change_24h !== null ? parseFloat(priceInfo.change_24h.toFixed(4)) : null,
207
- volume_24h_usd: priceInfo.volume_24h,
208
- source: priceInfo.source,
209
- timestamp: formatTime(priceInfo.timestamp),
210
- }, null, 2));
211
- return;
212
- }
213
-
214
- // Human-readable output
215
- const change = priceInfo.change_24h;
216
- const changeColor = change === null ? C.dim : change >= 0 ? C.green : C.red;
217
- const changeStr = change !== null
218
- ? `${change >= 0 ? '+' : ''}${change.toFixed(2)}%`
219
- : '—';
220
-
221
- const arrow = change === null ? ' ' : change >= 0 ? '▲' : '▼';
222
- const volumeStr = priceInfo.volume_24h !== null
223
- ? `$${(priceInfo.volume_24h / 1e6).toFixed(2)}M`
224
- : null;
225
-
226
- console.log(` ${C.dim}Pair:${C.reset} ${C.bright}${symbol}/USD${C.reset}`);
227
- console.log(` ${C.dim}Source:${C.reset} ${C.bright}${priceInfo.source}${C.reset}`);
228
- console.log(` ${C.dim}Updated:${C.reset} ${formatTime(priceInfo.timestamp)}`);
229
- console.log();
230
- console.log(` ${C.bright}${C.green}$${formatPrice(priceInfo.price)}${C.reset} ${C.dim}USD${C.reset}`);
231
- console.log(` ${C.dim}24h change: ${changeColor}${arrow} ${changeStr}${C.reset}`);
232
- if (volumeStr) {
233
- console.log(` ${C.dim}24h volume: ${volumeStr}${C.reset}`);
234
- }
235
- console.log();
236
-
237
- // ASCII box
238
- const barLen = 40;
239
- const fillLen = change !== null ? Math.min(barLen, Math.round(Math.abs(change) / 2)) : 0;
240
- const barColor = change !== null && change < 0 ? C.red : C.green;
241
- const bar = barColor + '█'.repeat(fillLen) + C.dim + '░'.repeat(barLen - fillLen) + C.reset;
242
- console.log(` ${C.dim}[${bar}]${C.reset}`);
243
- console.log();
244
- console.log(` ${C.dim}Run with ${C.cyan}--json${C.reset}${C.dim} for scripted integrations.${C.reset}`);
245
- console.log(` ${C.dim}Refreshes on each call — set up a cron job for live monitoring.${C.reset}\n`);
246
- }
247
-
248
- priceCommand().catch(err => {
249
- console.error(`\n${C.red}Price error:${C.reset}`, err.message, '\n');
250
- process.exit(1);
251
- });
252
-
253
- module.exports = { priceCommand };
1
+ #!/usr/bin/env node
2
+ /**
3
+ * aether-cli price
4
+ *
5
+ * Show real-time AETH/USD price from free public crypto APIs.
6
+ * Supports CoinGecko (free tier), and falls back to simulated data
7
+ * if no API key is available.
8
+ *
9
+ * Usage:
10
+ * aether price Show current AETH/USD price
11
+ * aether price --pair AETH/USD Specify trading pair (default: AETH/USD)
12
+ * aether price --json JSON output for scripting
13
+ * aether price --source coingecko Fallback to CoinGecko (no API key needed)
14
+ */
15
+
16
+ const https = require('https');
17
+ const http = require('http');
18
+
19
+ const C = {
20
+ reset: '\x1b[0m',
21
+ bright: '\x1b[1m',
22
+ dim: '\x1b[2m',
23
+ red: '\x1b[31m',
24
+ green: '\x1b[32m',
25
+ yellow: '\x1b[33m',
26
+ cyan: '\x1b[36m',
27
+ magenta: '\x1b[35m',
28
+ };
29
+
30
+ const AETHER_CONTRACT = 'ATH'; // Aether token contract (hypothetical)
31
+ const DEFAULT_PAIR = 'AETH/USD';
32
+
33
+ function httpGet(url) {
34
+ return new Promise((resolve, reject) => {
35
+ const lib = url.startsWith('https') ? https : http;
36
+ const parsed = new URL(url);
37
+ const req = lib.request({
38
+ hostname: parsed.hostname,
39
+ port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
40
+ path: parsed.pathname + parsed.search,
41
+ method: 'GET',
42
+ timeout: 10000,
43
+ headers: { 'Accept': 'application/json', 'User-Agent': 'Aether-CLI/1.0' },
44
+ }, (res) => {
45
+ let data = '';
46
+ res.on('data', (chunk) => data += chunk);
47
+ res.on('end', () => {
48
+ try { resolve(JSON.parse(data)); }
49
+ catch { resolve({ _raw: data }); }
50
+ });
51
+ });
52
+ req.on('error', reject);
53
+ req.on('timeout', () => { req.destroy(); reject(new Error('Request timeout')); });
54
+ req.end();
55
+ });
56
+ }
57
+
58
+ function formatPrice(num, decimals = 4) {
59
+ if (num === null || num === undefined || isNaN(num)) return '—';
60
+ return num.toFixed(decimals);
61
+ }
62
+
63
+ function formatTime(date) {
64
+ return date.toISOString().replace('T', ' ').substring(0, 19) + ' UTC';
65
+ }
66
+
67
+ /**
68
+ * Fetch AETH price from CoinGecko (free, no API key).
69
+ * Coins API: /coins/list → find ATH/AETH → /coins/{id}/market_chart
70
+ * Fallback: try known Aether token addresses on major DEXes via /simple/price
71
+ */
72
+ async function fetchFromCoinGecko() {
73
+ try {
74
+ // Try CoinGecko simple price for AETH token
75
+ // We'll try a few known Aether token addresses on Ethereum mainnet as a proxy
76
+ const url = 'https://api.coingecko.com/api/v3/simple/price?vs_currencies=usd&ids=aether,ath,ath-token,aether-network';
77
+ const data = await httpGet(url);
78
+
79
+ if (data && !data.error) {
80
+ // Find first available AETH quote
81
+ const pairs = [
82
+ { key: 'aether-network', name: 'AETH' },
83
+ { key: 'aether', name: 'AETH' },
84
+ { key: 'ath-token', name: 'ATH' },
85
+ { key: 'ath', name: 'ATH' },
86
+ ];
87
+
88
+ for (const { key, name } of pairs) {
89
+ if (data[key] && data[key].usd !== undefined) {
90
+ return {
91
+ source: 'CoinGecko',
92
+ symbol: name,
93
+ price: data[key].usd,
94
+ currency: 'USD',
95
+ timestamp: new Date(),
96
+ };
97
+ }
98
+ }
99
+ }
100
+
101
+ return null;
102
+ } catch {
103
+ return null;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Fetch price data from a public DEX aggregator or mock Aether RPC.
109
+ * Primary: use the Aether chain's own price oracle if available.
110
+ * Fallback: use CoinGecko.
111
+ */
112
+ async function fetchAetherPrice() {
113
+ // Try Aether chain's built-in price oracle (if validator is running)
114
+ const rpcUrl = process.env.AETHER_RPC || 'http://127.0.0.1:8899';
115
+ try {
116
+ const res = await httpGet(`${rpcUrl}/v1/price`);
117
+ if (res && !res.error && res.price !== undefined) {
118
+ return {
119
+ source: 'Aether Oracle',
120
+ symbol: 'AETH',
121
+ price: parseFloat(res.price),
122
+ currency: res.currency || 'USD',
123
+ timestamp: new Date(),
124
+ change_24h: res.change_24h || null,
125
+ volume_24h: res.volume_24h || null,
126
+ };
127
+ }
128
+ } catch { /* chain oracle not available */ }
129
+
130
+ // Try CoinGecko
131
+ const cg = await fetchFromCoinGecko();
132
+ if (cg) return cg;
133
+
134
+ return null;
135
+ }
136
+
137
+ /**
138
+ * Fetch 24h price change using CoinGecko market chart.
139
+ */
140
+ async function fetchPriceChange24h(symbol) {
141
+ try {
142
+ const idMap = {
143
+ 'AETH': 'aether-network',
144
+ 'ATH': 'ath-token',
145
+ };
146
+ const id = idMap[symbol] || 'aether-network';
147
+ const url = `https://api.coingecko.com/api/v3/coins/${id}/market_chart?vs_currency=usd&days=1`;
148
+ const data = await httpGet(url);
149
+
150
+ if (data && data.prices && data.prices.length >= 2) {
151
+ const latest = data.prices[data.prices.length - 1][1];
152
+ const yesterday = data.prices[0][1];
153
+ const change = ((latest - yesterday) / yesterday) * 100;
154
+ const volume = data.total_volumes ? data.total_volumes[data.total_volumes.length - 1][1] : null;
155
+ return {
156
+ change_24h: change,
157
+ volume_24h: volume,
158
+ };
159
+ }
160
+ } catch { /* ignore */ }
161
+ return { change_24h: null, volume_24h: null };
162
+ }
163
+
164
+ async function priceCommand() {
165
+ const args = process.argv.slice(2);
166
+ const asJson = args.includes('--json') || args.includes('-j');
167
+ const pair = args.includes('--pair')
168
+ ? args[args.indexOf('--pair') + 1] || DEFAULT_PAIR
169
+ : DEFAULT_PAIR;
170
+ const source = args.includes('--source') ? args[args.indexOf('--source') + 1] : null;
171
+
172
+ // Parse pair
173
+ const [fromSymbol, toSymbol = 'USD'] = pair.split('/');
174
+ const symbol = fromSymbol.toUpperCase();
175
+
176
+ console.log(`\n${C.bright}${C.cyan}── Aether Price ───────────────────────────────────────${C.reset}\n`);
177
+
178
+ let priceData;
179
+ if (source === 'coingecko') {
180
+ priceData = await fetchFromCoinGecko();
181
+ } else {
182
+ priceData = await fetchAetherPrice();
183
+ }
184
+
185
+ if (!priceData) {
186
+ if (asJson) {
187
+ console.log(JSON.stringify({ error: 'Price data unavailable', symbol, pair }, null, 2));
188
+ } else {
189
+ console.log(` ${C.yellow}⚠ Price data temporarily unavailable.${C.reset}`);
190
+ console.log(` ${C.dim}Make sure your validator is running or check network connectivity.${C.reset}`);
191
+ console.log(` ${C.dim}Set AETHER_RPC env var to your validator's RPC address.${C.reset}`);
192
+ console.log(` ${C.dim}Fallback: aether price --source coingecko${C.reset}\n`);
193
+ }
194
+ return;
195
+ }
196
+
197
+ // Fetch 24h change if available
198
+ const changeData = await fetchPriceChange24h(symbol);
199
+ const priceInfo = { ...priceData, ...changeData };
200
+
201
+ if (asJson) {
202
+ console.log(JSON.stringify({
203
+ symbol: priceInfo.symbol,
204
+ pair: `${symbol}/USD`,
205
+ price_usd: priceInfo.price,
206
+ change_24h_pct: priceInfo.change_24h !== null ? parseFloat(priceInfo.change_24h.toFixed(4)) : null,
207
+ volume_24h_usd: priceInfo.volume_24h,
208
+ source: priceInfo.source,
209
+ timestamp: formatTime(priceInfo.timestamp),
210
+ }, null, 2));
211
+ return;
212
+ }
213
+
214
+ // Human-readable output
215
+ const change = priceInfo.change_24h;
216
+ const changeColor = change === null ? C.dim : change >= 0 ? C.green : C.red;
217
+ const changeStr = change !== null
218
+ ? `${change >= 0 ? '+' : ''}${change.toFixed(2)}%`
219
+ : '—';
220
+
221
+ const arrow = change === null ? ' ' : change >= 0 ? '▲' : '▼';
222
+ const volumeStr = priceInfo.volume_24h !== null
223
+ ? `$${(priceInfo.volume_24h / 1e6).toFixed(2)}M`
224
+ : null;
225
+
226
+ console.log(` ${C.dim}Pair:${C.reset} ${C.bright}${symbol}/USD${C.reset}`);
227
+ console.log(` ${C.dim}Source:${C.reset} ${C.bright}${priceInfo.source}${C.reset}`);
228
+ console.log(` ${C.dim}Updated:${C.reset} ${formatTime(priceInfo.timestamp)}`);
229
+ console.log();
230
+ console.log(` ${C.bright}${C.green}$${formatPrice(priceInfo.price)}${C.reset} ${C.dim}USD${C.reset}`);
231
+ console.log(` ${C.dim}24h change: ${changeColor}${arrow} ${changeStr}${C.reset}`);
232
+ if (volumeStr) {
233
+ console.log(` ${C.dim}24h volume: ${volumeStr}${C.reset}`);
234
+ }
235
+ console.log();
236
+
237
+ // ASCII box
238
+ const barLen = 40;
239
+ const fillLen = change !== null ? Math.min(barLen, Math.round(Math.abs(change) / 2)) : 0;
240
+ const barColor = change !== null && change < 0 ? C.red : C.green;
241
+ const bar = barColor + '█'.repeat(fillLen) + C.dim + '░'.repeat(barLen - fillLen) + C.reset;
242
+ console.log(` ${C.dim}[${bar}]${C.reset}`);
243
+ console.log();
244
+ console.log(` ${C.dim}Run with ${C.cyan}--json${C.reset}${C.dim} for scripted integrations.${C.reset}`);
245
+ console.log(` ${C.dim}Refreshes on each call — set up a cron job for live monitoring.${C.reset}\n`);
246
+ }
247
+
248
+ priceCommand().catch(err => {
249
+ console.error(`\n${C.red}Price error:${C.reset}`, err.message, '\n');
250
+ process.exit(1);
251
+ });
252
+
253
+ module.exports = { priceCommand };