aether-hub 1.2.5 → 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.
@@ -10,11 +10,10 @@
10
10
  * aether account --address <addr> --json JSON output for scripting
11
11
  * aether account --address <addr> --data Show raw account data as base64/hex
12
12
  *
13
- * Requires AETHER_RPC env var (default: http://127.0.0.1:8899)
13
+ * Uses @jellylegsai/aether-sdk for real blockchain RPC calls to http://127.0.0.1:8899
14
14
  */
15
15
 
16
- const http = require('http');
17
- const https = require('https');
16
+ const path = require('path');
18
17
  const bs58 = require('bs58').default;
19
18
 
20
19
  // ANSI colours
@@ -29,37 +28,12 @@ const C = {
29
28
  magenta: '\x1b[35m',
30
29
  };
31
30
 
32
- // ---------------------------------------------------------------------------
33
- // HTTP helpers
34
- // ---------------------------------------------------------------------------
35
-
36
- function httpRequest(rpcUrl, path) {
37
- return new Promise((resolve, reject) => {
38
- const url = new URL(path, rpcUrl);
39
- const lib = url.protocol === 'https:' ? https : http;
40
- const req = lib.request({
41
- hostname: url.hostname,
42
- port: url.port || (url.protocol === 'https:' ? 443 : 80),
43
- path: url.pathname + url.search,
44
- method: 'GET',
45
- timeout: 8000,
46
- headers: { 'Content-Type': 'application/json' },
47
- }, (res) => {
48
- let data = '';
49
- res.on('data', (chunk) => data += chunk);
50
- res.on('end', () => {
51
- try { resolve(JSON.parse(data)); }
52
- catch { resolve({ raw: data }); }
53
- });
54
- });
55
- req.on('error', reject);
56
- req.on('timeout', () => { req.destroy(); reject(new Error('Request timeout')); });
57
- req.end();
58
- });
59
- }
31
+ // Import SDK for real blockchain RPC calls
32
+ const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
33
+ const aether = require(sdkPath);
60
34
 
61
35
  function getDefaultRpc() {
62
- return process.env.AETHER_RPC || 'http://127.0.0.1:8899';
36
+ return process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
63
37
  }
64
38
 
65
39
  function formatAether(lamports) {
@@ -73,7 +47,7 @@ function formatAether(lamports) {
73
47
  // ---------------------------------------------------------------------------
74
48
 
75
49
  function parseArgs() {
76
- return process.argv.slice(3); // [node, index.js, account, ...]
50
+ return process.argv.slice(2); // [node, account.js, ...]
77
51
  }
78
52
 
79
53
  function findArg(args, ...flags) {
@@ -165,7 +139,9 @@ async function accountCommand() {
165
139
  }
166
140
 
167
141
  try {
168
- const account = await httpRequest(rpcUrl, `/v1/account/${apiAddress}`);
142
+ // Use SDK for real blockchain RPC call
143
+ const client = new aether.AetherClient({ rpcUrl: rpcUrl });
144
+ const account = await client.getAccountInfo(apiAddress);
169
145
 
170
146
  if (!account || account.error) {
171
147
  if (asJson) {
@@ -0,0 +1,276 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * aether-cli balance
4
+ *
5
+ * Query account balance from the Aether blockchain — real HTTP RPC calls,
6
+ * real data. No stubs, no mocks.
7
+ *
8
+ * Usage:
9
+ * aether balance <address> Query balance for an address
10
+ * aether balance Query default wallet balance
11
+ * aether balance --json JSON output for scripting
12
+ * aether balance --rpc <url> Query a specific RPC endpoint
13
+ * aether balance --lamports Show balance in lamports (not AETH)
14
+ *
15
+ * Requires AETHER_RPC env var (default: http://127.0.0.1:8899)
16
+ * SDK: @jellylegsai/aether-sdk — makes REAL HTTP RPC calls to the chain
17
+ */
18
+
19
+ const os = require('os');
20
+ const path = require('path');
21
+
22
+ // ANSI colours
23
+ const C = {
24
+ reset: '\x1b[0m',
25
+ bright: '\x1b[1m',
26
+ dim: '\x1b[2m',
27
+ red: '\x1b[31m',
28
+ green: '\x1b[32m',
29
+ yellow: '\x1b[33m',
30
+ cyan: '\x1b[36m',
31
+ magenta: '\x1b[35m',
32
+ };
33
+
34
+ // Import SDK — REAL blockchain RPC calls to http://127.0.0.1:8899
35
+ const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
36
+ const aether = require(sdkPath);
37
+
38
+ const DEFAULT_RPC = process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
39
+ const CLI_VERSION = '1.0.0';
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // Helpers
43
+ // ---------------------------------------------------------------------------
44
+
45
+ function getDefaultRpc() {
46
+ return process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
47
+ }
48
+
49
+ function createClient(rpc) {
50
+ return new aether.AetherClient({ rpcUrl: rpc });
51
+ }
52
+
53
+ function lamportsToAeth(lamports) {
54
+ return (Number(lamports) / 1e9).toFixed(6);
55
+ }
56
+
57
+ function loadConfig() {
58
+ const fs = require('fs');
59
+ const aetherDir = path.join(os.homedir(), '.aether');
60
+ const cfgPath = path.join(aetherDir, 'config.json');
61
+ if (!fs.existsSync(cfgPath)) return { defaultWallet: null };
62
+ try { return JSON.parse(fs.readFileSync(cfgPath, 'utf8')); }
63
+ catch { return { defaultWallet: null }; }
64
+ }
65
+
66
+ function shortenAddress(addr) {
67
+ if (!addr) return 'unknown';
68
+ if (addr.length <= 10) return addr;
69
+ return `${addr.substring(0, 6)}...${addr.substring(addr.length - 4)}`;
70
+ }
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // Argument parsing
74
+ // ---------------------------------------------------------------------------
75
+
76
+ function parseArgs() {
77
+ const args = process.argv.slice(2);
78
+ const options = {
79
+ rpc: getDefaultRpc(),
80
+ address: null,
81
+ asJson: false,
82
+ showLamports: false,
83
+ };
84
+
85
+ for (let i = 0; i < args.length; i++) {
86
+ const arg = args[i];
87
+ if (arg === '--rpc' || arg === '-r') {
88
+ options.rpc = args[++i];
89
+ } else if (arg === '--json' || arg === '-j') {
90
+ options.asJson = true;
91
+ } else if (arg === '--lamports' || arg === '-l') {
92
+ options.showLamports = true;
93
+ } else if (arg === '--help' || arg === '-h') {
94
+ showHelp();
95
+ process.exit(0);
96
+ } else if (!arg.startsWith('-') && !options.address) {
97
+ options.address = arg;
98
+ }
99
+ }
100
+
101
+ // If no address provided, try to load default wallet
102
+ if (!options.address) {
103
+ const config = loadConfig();
104
+ if (config.defaultWallet) {
105
+ options.address = config.defaultWallet;
106
+ }
107
+ }
108
+
109
+ return options;
110
+ }
111
+
112
+ function showHelp() {
113
+ console.log(`
114
+ ${C.bright}${C.cyan}aether-cli balance${C.reset} - Query Account Balance
115
+
116
+ ${C.bright}Usage:${C.reset}
117
+ aether balance [address] [options]
118
+
119
+ ${C.bright}Arguments:${C.reset}
120
+ address Account public key (base58). If omitted, uses default wallet.
121
+
122
+ ${C.bright}Options:${C.reset}
123
+ --rpc <url> Query a specific RPC endpoint (default: ${DEFAULT_RPC})
124
+ --json, -j Output raw JSON for scripting
125
+ --lamports, -l Show balance in lamports (not AETH)
126
+ --help, -h Show this help message
127
+
128
+ ${C.bright}Description:${C.reset}
129
+ Queries the Aether blockchain for an account's balance.
130
+ Every call makes a REAL HTTP RPC request to the configured
131
+ Aether node — no caching, no stubs, no mocks.
132
+
133
+ RPC Endpoint: GET /v1/account/<address>
134
+
135
+ ${C.bright}Examples:${C.reset}
136
+ aether balance # Default wallet
137
+ aether balance 8xPt...3nQ # Specific address
138
+ aether balance --json # JSON output
139
+ aether balance --lamports # Show lamports
140
+ AETHER_RPC=https://my-node:8899 aether balance
141
+ `);
142
+ }
143
+
144
+ // ---------------------------------------------------------------------------
145
+ // Balance query - REAL RPC call via SDK
146
+ // ---------------------------------------------------------------------------
147
+
148
+ async function fetchBalance(rpc, address) {
149
+ const client = createClient(rpc);
150
+
151
+ // Real RPC call: GET /v1/account/<address>
152
+ const account = await client.getAccountInfo(address);
153
+
154
+ return {
155
+ address,
156
+ lamports: account.lamports !== undefined ? account.lamports : 0,
157
+ owner: account.owner || null,
158
+ executable: account.executable || false,
159
+ rentEpoch: account.rent_epoch !== undefined ? account.rent_epoch : null,
160
+ };
161
+ }
162
+
163
+ // ---------------------------------------------------------------------------
164
+ // Output formatters
165
+ // ---------------------------------------------------------------------------
166
+
167
+ function printBalance(data, options) {
168
+ const { address, lamports, owner, executable, rentEpoch } = data;
169
+ const { showLamports, rpc } = options;
170
+
171
+ const aeth = lamportsToAeth(lamports);
172
+
173
+ console.log(`\n${C.bright}${C.cyan}── Aether Account Balance ───────────────────────────────${C.reset}\n`);
174
+ console.log(` ${C.bright}Address:${C.reset} ${C.magenta}${address}${C.reset}`);
175
+
176
+ if (showLamports) {
177
+ console.log(` ${C.bright}Balance:${C.reset} ${C.green}${lamports.toLocaleString()} lamports${C.reset}`);
178
+ } else {
179
+ console.log(` ${C.bright}Balance:${C.reset} ${C.green}${aeth} AETH${C.reset}`);
180
+ console.log(` ${C.dim} (${lamports.toLocaleString()} lamports)${C.reset}`);
181
+ }
182
+
183
+ if (owner) {
184
+ console.log(` ${C.bright}Owner:${C.reset} ${C.dim}${shortenAddress(owner)}${C.reset}`);
185
+ }
186
+
187
+ if (executable !== undefined) {
188
+ const execStr = executable ? `${C.yellow}yes${C.reset}` : `${C.dim}no${C.reset}`;
189
+ console.log(` ${C.bright}Executable:${C.reset} ${execStr}`);
190
+ }
191
+
192
+ if (rentEpoch !== null) {
193
+ console.log(` ${C.bright}Rent Epoch:${C.reset} ${C.dim}${rentEpoch}${C.reset}`);
194
+ }
195
+
196
+ console.log(` ${C.dim}RPC: ${rpc}${C.reset}\n`);
197
+ }
198
+
199
+ function printJson(data, rpc) {
200
+ console.log(JSON.stringify({
201
+ address: data.address,
202
+ lamports: data.lamports,
203
+ aeth: lamportsToAeth(data.lamports),
204
+ owner: data.owner,
205
+ executable: data.executable,
206
+ rentEpoch: data.rentEpoch,
207
+ rpc,
208
+ cli_version: CLI_VERSION,
209
+ timestamp: new Date().toISOString(),
210
+ }, null, 2));
211
+ }
212
+
213
+ // ---------------------------------------------------------------------------
214
+ // Main
215
+ // ---------------------------------------------------------------------------
216
+
217
+ async function balanceCommand() {
218
+ const options = parseArgs();
219
+ const { rpc, address, asJson } = options;
220
+
221
+ // Validate address
222
+ if (!address) {
223
+ if (asJson) {
224
+ console.log(JSON.stringify({
225
+ error: 'No address provided and no default wallet configured',
226
+ hint: 'Run "aether init" to set up a wallet, or provide an address',
227
+ }));
228
+ } else {
229
+ console.log(`\n${C.red}✗ No address provided${C.reset}`);
230
+ console.log(` ${C.dim}Provide an address or run "aether init" to set up a default wallet.${C.reset}`);
231
+ console.log(` ${C.dim}Usage: aether balance <address>${C.reset}\n`);
232
+ }
233
+ process.exit(1);
234
+ }
235
+
236
+ if (!asJson) {
237
+ console.log(`${C.dim}Querying balance for ${C.cyan}${shortenAddress(address)}${C.dim}...${C.reset}`);
238
+ }
239
+
240
+ try {
241
+ // Real blockchain RPC call
242
+ const data = await fetchBalance(rpc, address);
243
+
244
+ if (asJson) {
245
+ printJson(data, rpc);
246
+ } else {
247
+ printBalance(data, options);
248
+ }
249
+ } catch (err) {
250
+ if (asJson) {
251
+ console.log(JSON.stringify({
252
+ error: err.message,
253
+ address,
254
+ rpc,
255
+ }));
256
+ } else {
257
+ console.log(`\n${C.red}✗ Failed to fetch balance: ${err.message}${C.reset}`);
258
+ console.log(` ${C.dim}Address: ${address}${C.reset}`);
259
+ console.log(` ${C.dim}RPC: ${rpc}${C.reset}\n`);
260
+ }
261
+ process.exit(1);
262
+ }
263
+ }
264
+
265
+ // ---------------------------------------------------------------------------
266
+ // Exports
267
+ // ---------------------------------------------------------------------------
268
+
269
+ module.exports = { balanceCommand };
270
+
271
+ if (require.main === module) {
272
+ balanceCommand().catch(err => {
273
+ console.error(`${C.red}✗ Balance command failed: ${err.message}${C.reset}`);
274
+ process.exit(1);
275
+ });
276
+ }
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * aether-cli blockhash
4
+ *
5
+ * Fetches the latest recent blockhash from the Aether blockchain — required
6
+ * as a prerequisite for signing and submitting any transaction.
7
+ *
8
+ * Usage:
9
+ * aether-cli blockhash Show latest blockhash
10
+ * aether-cli blockhash --json JSON output for scripting
11
+ * aether-cli blockhash --rpc <url> Query a specific RPC endpoint
12
+ * aether-cli blockhash --watch Poll every 5 seconds
13
+ *
14
+ * Requires AETHER_RPC env var (default: http://127.0.0.1:8899)
15
+ * SDK: @jellylegsai/aether-sdk — makes REAL HTTP RPC calls to the chain
16
+ */
17
+
18
+ const path = require('path');
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
+ // Import SDK — REAL blockchain RPC calls to http://127.0.0.1:8899
33
+ const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
34
+ const aether = require(sdkPath);
35
+
36
+ const DEFAULT_RPC = process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // Argument parsing
40
+ // ---------------------------------------------------------------------------
41
+
42
+ function parseArgs() {
43
+ const args = process.argv.slice(2);
44
+ const options = {
45
+ rpc: DEFAULT_RPC,
46
+ asJson: false,
47
+ watch: false,
48
+ };
49
+
50
+ for (let i = 0; i < args.length; i++) {
51
+ if (args[i] === '--rpc' || args[i] === '-r') {
52
+ options.rpc = args[++i];
53
+ } else if (args[i] === '--json' || args[i] === '-j') {
54
+ options.asJson = true;
55
+ } else if (args[i] === '--watch' || args[i] === '-w') {
56
+ options.watch = true;
57
+ } else if (args[i] === '--help' || args[i] === '-h') {
58
+ showHelp();
59
+ process.exit(0);
60
+ }
61
+ }
62
+
63
+ return options;
64
+ }
65
+
66
+ function showHelp() {
67
+ console.log(`
68
+ ${C.bright}${C.cyan}aether-cli blockhash${C.reset} - Get Latest Blockhash
69
+
70
+ ${C.bright}Usage:${C.reset}
71
+ aether-cli blockhash [options]
72
+
73
+ ${C.bright}Options:${C.reset}
74
+ --rpc <url> Query a specific RPC endpoint (default: http://127.0.0.1:8899)
75
+ --json, -j Output raw JSON for scripting
76
+ --watch, -w Poll every 5 seconds
77
+ --help, -h Show this help message
78
+
79
+ ${C.bright}Description:${C.reset}
80
+ Fetches the most recent blockhash from the Aether blockchain.
81
+ Blockhashes are required as a recent-ref entry when building
82
+ signed transactions (Transfer, Stake, Unstake, etc.).
83
+
84
+ Every call makes a REAL HTTP RPC request to the configured
85
+ Aether node — no caching, no stubs.
86
+
87
+ ${C.bright}Examples:${C.reset}
88
+ aether blockhash
89
+ aether blockhash --json
90
+ aether blockhash --watch
91
+ AETHER_RPC=https://my-node:8899 aether blockhash
92
+ `);
93
+ }
94
+
95
+ // ---------------------------------------------------------------------------
96
+ // Fetch and display blockhash from REAL chain via SDK
97
+ // ---------------------------------------------------------------------------
98
+
99
+ async function fetchBlockhash(rpc) {
100
+ const client = new aether.AetherClient({ rpcUrl: rpc });
101
+ const result = await client.getRecentBlockhash();
102
+ return result;
103
+ }
104
+
105
+ function printBlockhash(result, rpc) {
106
+ const { blockhash, lastValidBlockHeight } = result;
107
+
108
+ console.log(`\n${C.bright}${C.cyan}── Aether Blockhash ─────────────────────────────────────${C.reset}\n`);
109
+ console.log(` ${C.bright}Blockhash:${C.reset} ${C.magenta}${blockhash}${C.reset}`);
110
+ if (lastValidBlockHeight !== undefined) {
111
+ console.log(` ${C.bright}Last Valid Block Height:${C.reset} ${C.cyan}${lastValidBlockHeight.toLocaleString()}${C.reset}`);
112
+ }
113
+ console.log(` ${C.dim}RPC: ${rpc}${C.reset}\n`);
114
+ }
115
+
116
+ function printJson(result, rpc) {
117
+ console.log(JSON.stringify({
118
+ blockhash: result.blockhash,
119
+ lastValidBlockHeight: result.lastValidBlockHeight,
120
+ rpc,
121
+ cli_version: '1.0.0',
122
+ timestamp: new Date().toISOString(),
123
+ }));
124
+ }
125
+
126
+ // ---------------------------------------------------------------------------
127
+ // Main
128
+ // ---------------------------------------------------------------------------
129
+
130
+ async function main() {
131
+ const opts = parseArgs();
132
+ const { rpc, asJson, watch } = opts;
133
+
134
+ if (watch && !asJson) {
135
+ console.log(`${C.dim}Watching for new blockhashes every 5s (Ctrl+C to stop)…${C.reset}`);
136
+ console.log();
137
+ }
138
+
139
+ async function tick() {
140
+ try {
141
+ const result = await fetchBlockhash(rpc);
142
+ if (asJson) {
143
+ printJson(result, rpc);
144
+ } else if (!watch) {
145
+ printBlockhash(result, rpc);
146
+ } else {
147
+ process.stdout.write(`\r${C.dim}[${new Date().toISOString()}]${C.reset} blockhash: ${C.magenta}${result.blockhash}${C.reset} `);
148
+ }
149
+ } catch (err) {
150
+ if (asJson) {
151
+ console.log(JSON.stringify({ error: err.message, rpc }));
152
+ } else if (!watch) {
153
+ console.log(`${C.red}✗ Failed to fetch blockhash: ${err.message}${C.reset}`);
154
+ } else {
155
+ process.stdout.write(`\r${C.red}✗ ${err.message}${C.reset} `);
156
+ }
157
+ }
158
+ }
159
+
160
+ if (watch) {
161
+ await tick(); // immediate first run
162
+ // eslint-disable-next-line no-unmodified-loop-condition
163
+ while (true) {
164
+ await new Promise(r => setTimeout(r, 5000));
165
+ await tick();
166
+ }
167
+ } else {
168
+ await tick();
169
+ }
170
+ }
171
+
172
+ main().catch(err => {
173
+ console.error(`${C.red}✗ Blockhash command failed:${C.reset} ${err.message}`);
174
+ process.exit(1);
175
+ });
176
+
177
+ module.exports = { blockhashCommand: main };
178
+
179
+ if (require.main === module) {
180
+ main();
181
+ }