aether-hub 1.2.7 → 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.
@@ -1,280 +1,280 @@
1
- #!/usr/bin/env node
2
- /**
3
- * aether-cli account
4
- *
5
- * Query detailed on-chain account data for any address.
6
- * Shows lamports, owner, executable, rent epoch, and program-owned account keys.
7
- *
8
- * Usage:
9
- * aether account --address <addr> Full account dump
10
- * aether account --address <addr> --json JSON output for scripting
11
- * aether account --address <addr> --data Show raw account data as base64/hex
12
- *
13
- * Uses @jellylegsai/aether-sdk for real blockchain RPC calls to http://127.0.0.1:8899
14
- */
15
-
16
- const path = require('path');
17
- const bs58 = require('bs58').default;
18
-
19
- // ANSI colours
20
- const C = {
21
- reset: '\x1b[0m',
22
- bright: '\x1b[1m',
23
- dim: '\x1b[2m',
24
- red: '\x1b[31m',
25
- green: '\x1b[32m',
26
- yellow: '\x1b[33m',
27
- cyan: '\x1b[36m',
28
- magenta: '\x1b[35m',
29
- };
30
-
31
- // Import SDK for real blockchain RPC calls
32
- const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
33
- const aether = require(sdkPath);
34
-
35
- function getDefaultRpc() {
36
- return process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
37
- }
38
-
39
- function formatAether(lamports) {
40
- const aeth = lamports / 1e9;
41
- if (aeth === 0) return '0 AETH';
42
- return aeth.toFixed(4).replace(/\.?0+$/, '') + ' AETH';
43
- }
44
-
45
- // ---------------------------------------------------------------------------
46
- // Parse args
47
- // ---------------------------------------------------------------------------
48
-
49
- function parseArgs() {
50
- return process.argv.slice(2); // [node, account.js, ...]
51
- }
52
-
53
- function findArg(args, ...flags) {
54
- for (const flag of flags) {
55
- const idx = args.indexOf(flag);
56
- if (idx !== -1 && args[idx + 1] && !args[idx + 1].startsWith('-')) {
57
- return { value: args[idx + 1], idx };
58
- }
59
- }
60
- return null;
61
- }
62
-
63
- function parseBoolArg(args, ...flags) {
64
- return flags.some(f => args.includes(f));
65
- }
66
-
67
- // ---------------------------------------------------------------------------
68
- // Format helpers
69
- // ---------------------------------------------------------------------------
70
-
71
- function formatPubkey(key) {
72
- if (!key) return '—';
73
- if (Array.isArray(key)) {
74
- // base64-like or raw bytes → treat as public key bytes
75
- if (key.length === 32) return 'ATH' + bs58.encode(Buffer.from(key.slice(0, 32)));
76
- return 'ATH' + bs58.encode(Buffer.from(key));
77
- }
78
- if (typeof key === 'string') {
79
- if (key.startsWith('ATH')) return key;
80
- // Assume base58
81
- try { return 'ATH' + bs58.encode(bs58.decode(key)); }
82
- catch { return key.substring(0, 16) + '...'; }
83
- }
84
- return String(key).substring(0, 16);
85
- }
86
-
87
- function base64ToHex(b64) {
88
- try {
89
- const buf = Buffer.from(b64, 'base64');
90
- return buf.toString('hex');
91
- } catch {
92
- return b64;
93
- }
94
- }
95
-
96
- function formatData(data) {
97
- if (!data) return null;
98
- if (typeof data === 'string') return data;
99
- if (Array.isArray(data)) {
100
- // raw bytes → hex
101
- return Buffer.from(data).toString('hex');
102
- }
103
- return JSON.stringify(data);
104
- }
105
-
106
- // ---------------------------------------------------------------------------
107
- // Main account query
108
- // ---------------------------------------------------------------------------
109
-
110
- async function accountCommand() {
111
- const args = parseArgs();
112
-
113
- // Parse flags
114
- const addrArg = findArg(args, '--address', '-a');
115
- const asJson = parseBoolArg(args, '--json', '-j');
116
- const showData = parseBoolArg(args, '--data', '-d');
117
- const rpcArg = findArg(args, '--rpc', '-r');
118
- const rpcUrl = rpcArg ? rpcArg.value : getDefaultRpc();
119
-
120
- if (!addrArg) {
121
- console.log(`\n ${C.red}✗ Missing --address flag.${C.reset}`);
122
- console.log(`\n ${C.cyan}Usage:${C.reset}`);
123
- console.log(` ${C.cyan}aether account --address <addr>${C.reset} Show account details`);
124
- console.log(` ${C.cyan}aether account --address <addr> --json${C.reset} JSON output`);
125
- console.log(` ${C.cyan}aether account --address <addr> --data${C.reset} Show raw account data`);
126
- console.log(` ${C.cyan}aether account --address <addr> --rpc <url>${C.reset} Use custom RPC\n`);
127
- process.exit(1);
128
- }
129
-
130
- const rawAddress = addrArg.value;
131
- const address = rawAddress.startsWith('ATH') ? rawAddress : rawAddress;
132
- const apiAddress = rawAddress.startsWith('ATH') ? rawAddress.slice(3) : rawAddress;
133
-
134
- if (!asJson) {
135
- console.log(`\n${C.bright}${C.cyan}── Account Info ──────────────────────────────────────────${C.reset}\n`);
136
- console.log(` ${C.green}★${C.reset} Address: ${C.bright}${address}${C.reset}`);
137
- console.log(` ${C.dim} RPC: ${rpcUrl}${C.reset}`);
138
- console.log();
139
- }
140
-
141
- try {
142
- // Use SDK for real blockchain RPC call
143
- const client = new aether.AetherClient({ rpcUrl: rpcUrl });
144
- const account = await client.getAccountInfo(apiAddress);
145
-
146
- if (!account || account.error) {
147
- if (asJson) {
148
- console.log(JSON.stringify({ address, error: account?.error || 'Account not found' }, null, 2));
149
- } else {
150
- console.log(` ${C.yellow}⚠ Account not found on chain or RPC error.${C.reset}`);
151
- console.log(` ${C.dim} This is normal for addresses with no on-chain account.${C.reset}`);
152
- console.log(` ${C.dim} RPC response: ${JSON.stringify(account?.error || account)}${C.reset}\n`);
153
- }
154
- process.exit(1);
155
- }
156
-
157
- const lamports = account.lamports || account.lamports === 0 ? account.lamports : 0;
158
- const owner = account.owner || null;
159
- const executable = account.executable || false;
160
- const rentEpoch = account.rent_epoch;
161
- const data = account.data;
162
- const dataLen = data ? (Array.isArray(data) ? data.length : typeof data === 'string' ? data.length : 0) : 0;
163
-
164
- if (asJson) {
165
- let ownerStr = null;
166
- if (owner) {
167
- if (Array.isArray(owner)) {
168
- ownerStr = 'ATH' + bs58.encode(Buffer.from(owner.slice(0, 32)));
169
- } else if (typeof owner === 'string') {
170
- ownerStr = owner.startsWith('ATH') ? owner : 'ATH' + bs58.encode(bs58.decode(owner));
171
- } else {
172
- ownerStr = String(owner);
173
- }
174
- }
175
-
176
- console.log(JSON.stringify({
177
- address,
178
- rpc: rpcUrl,
179
- lamports,
180
- lamports_formatted: formatAether(lamports),
181
- owner: ownerStr,
182
- executable,
183
- rent_epoch: rentEpoch,
184
- data_size: dataLen,
185
- data: showData ? formatData(data) : null,
186
- fetched_at: new Date().toISOString(),
187
- }, null, 2));
188
- return;
189
- }
190
-
191
- // Human-readable output
192
- console.log(` ${C.green}✓${C.reset} Found on chain`);
193
- console.log(` ${C.dim}────────────────────────────────────────${C.reset}`);
194
- console.log(` ${C.dim} Balance:${C.reset} ${C.bright}${formatAether(lamports)}${C.reset} (${lamports.toLocaleString()} lamports)`);
195
-
196
- if (owner) {
197
- let ownerStr;
198
- if (Array.isArray(owner)) {
199
- ownerStr = 'ATH' + bs58.encode(Buffer.from(owner.slice(0, 32)));
200
- } else if (typeof owner === 'string') {
201
- ownerStr = owner.startsWith('ATH') ? owner : 'ATH' + bs58.encode(bs58.decode(owner));
202
- } else {
203
- ownerStr = String(owner);
204
- }
205
- console.log(` ${C.dim} Owner:${C.reset} ${C.cyan}${ownerStr}${C.reset}`);
206
- }
207
-
208
- console.log(` ${C.dim} Executable:${C.reset} ${C.bright}${(executable ? 'Yes' : 'No')}${C.reset}`);
209
- if (rentEpoch !== undefined) {
210
- console.log(` ${C.dim} Rent epoch:${C.reset} ${rentEpoch}`);
211
- }
212
-
213
- if (dataLen > 0) {
214
- console.log(` ${C.dim} Data size:${C.reset} ${C.bright}${dataLen} bytes${C.reset}`);
215
- } else {
216
- console.log(` ${C.dim} Data size:${C.reset} 0 bytes`);
217
- }
218
-
219
- if (showData && data) {
220
- console.log();
221
- console.log(` ${C.dim} Raw data (hex):${C.reset}`);
222
- const hex = formatData(data);
223
- // Pretty-print in 32-byte chunks
224
- const chunks = hex.match(/.{1,64}/g) || [];
225
- for (let i = 0; i < chunks.length; i++) {
226
- const offset = (i * 32).toString(16).padStart(8, '0');
227
- console.log(` ${C.dim}${offset}:${C.reset} ${chunks[i]}`);
228
- }
229
- }
230
-
231
- console.log();
232
- console.log(` ${C.dim}────────────────────────────────────────${C.reset}`);
233
-
234
- // Additional context: try to determine account type
235
- let accountType = 'Unknown';
236
- if (owner) {
237
- const ownerStr = Array.isArray(owner)
238
- ? bs58.encode(Buffer.from(owner.slice(0, 32)))
239
- : (typeof owner === 'string' && !owner.startsWith('ATH') ? owner : '');
240
- // Known program IDs (these are common Aether program identifiers)
241
- const knownPrograms = {
242
- 'STAKE': 'Stake Program',
243
- 'SYSTEM': 'System Program',
244
- 'VOTE': 'Vote Program',
245
- 'TOKEN': 'Token Program',
246
- 'MEMO': 'Memo Program',
247
- };
248
- const prog = knownPrograms[ownerStr.toUpperCase()];
249
- if (prog) accountType = prog;
250
- else if (executable) accountType = 'Executable Program';
251
- else if (dataLen === 0) accountType = 'Empty Account (no data)';
252
- else accountType = 'Data Account';
253
- }
254
-
255
- console.log(` ${C.dim} Account type:${C.reset} ${C.bright}${accountType}${C.reset}`);
256
- console.log();
257
- console.log(` ${C.dim} Fetch via API:${C.reset} GET ${rpcUrl}/v1/account/${apiAddress}`);
258
- console.log();
259
-
260
- } catch (err) {
261
- if (asJson) {
262
- console.log(JSON.stringify({ address, error: err.message }, null, 2));
263
- } else {
264
- console.log(` ${C.red}✗ Failed to fetch account:${C.reset} ${err.message}`);
265
- console.log(` ${C.dim} Is your validator running? RPC: ${rpcUrl}${C.reset}`);
266
- console.log(` ${C.dim} Set custom RPC: AETHER_RPC=https://your-rpc-url${C.reset}\n`);
267
- }
268
- process.exit(1);
269
- }
270
- }
271
-
272
- // ---------------------------------------------------------------------------
273
- // Export & run
274
- // ---------------------------------------------------------------------------
275
-
276
- module.exports = { accountCommand };
277
-
278
- if (require.main === module) {
279
- accountCommand();
280
- }
1
+ #!/usr/bin/env node
2
+ /**
3
+ * aether-cli account
4
+ *
5
+ * Query detailed on-chain account data for any address.
6
+ * Shows lamports, owner, executable, rent epoch, and program-owned account keys.
7
+ *
8
+ * Usage:
9
+ * aether account --address <addr> Full account dump
10
+ * aether account --address <addr> --json JSON output for scripting
11
+ * aether account --address <addr> --data Show raw account data as base64/hex
12
+ *
13
+ * Uses @jellylegsai/aether-sdk for real blockchain RPC calls to http://127.0.0.1:8899
14
+ */
15
+
16
+ const path = require('path');
17
+ const bs58 = require('bs58').default;
18
+
19
+ // ANSI colours
20
+ const C = {
21
+ reset: '\x1b[0m',
22
+ bright: '\x1b[1m',
23
+ dim: '\x1b[2m',
24
+ red: '\x1b[31m',
25
+ green: '\x1b[32m',
26
+ yellow: '\x1b[33m',
27
+ cyan: '\x1b[36m',
28
+ magenta: '\x1b[35m',
29
+ };
30
+
31
+ // Import SDK for real blockchain RPC calls
32
+ const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
33
+ const aether = require(sdkPath);
34
+
35
+ function getDefaultRpc() {
36
+ return process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
37
+ }
38
+
39
+ function formatAether(lamports) {
40
+ const aeth = lamports / 1e9;
41
+ if (aeth === 0) return '0 AETH';
42
+ return aeth.toFixed(4).replace(/\.?0+$/, '') + ' AETH';
43
+ }
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Parse args
47
+ // ---------------------------------------------------------------------------
48
+
49
+ function parseArgs() {
50
+ return process.argv.slice(2); // [node, account.js, ...]
51
+ }
52
+
53
+ function findArg(args, ...flags) {
54
+ for (const flag of flags) {
55
+ const idx = args.indexOf(flag);
56
+ if (idx !== -1 && args[idx + 1] && !args[idx + 1].startsWith('-')) {
57
+ return { value: args[idx + 1], idx };
58
+ }
59
+ }
60
+ return null;
61
+ }
62
+
63
+ function parseBoolArg(args, ...flags) {
64
+ return flags.some(f => args.includes(f));
65
+ }
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // Format helpers
69
+ // ---------------------------------------------------------------------------
70
+
71
+ function formatPubkey(key) {
72
+ if (!key) return '—';
73
+ if (Array.isArray(key)) {
74
+ // base64-like or raw bytes → treat as public key bytes
75
+ if (key.length === 32) return 'ATH' + bs58.encode(Buffer.from(key.slice(0, 32)));
76
+ return 'ATH' + bs58.encode(Buffer.from(key));
77
+ }
78
+ if (typeof key === 'string') {
79
+ if (key.startsWith('ATH')) return key;
80
+ // Assume base58
81
+ try { return 'ATH' + bs58.encode(bs58.decode(key)); }
82
+ catch { return key.substring(0, 16) + '...'; }
83
+ }
84
+ return String(key).substring(0, 16);
85
+ }
86
+
87
+ function base64ToHex(b64) {
88
+ try {
89
+ const buf = Buffer.from(b64, 'base64');
90
+ return buf.toString('hex');
91
+ } catch {
92
+ return b64;
93
+ }
94
+ }
95
+
96
+ function formatData(data) {
97
+ if (!data) return null;
98
+ if (typeof data === 'string') return data;
99
+ if (Array.isArray(data)) {
100
+ // raw bytes → hex
101
+ return Buffer.from(data).toString('hex');
102
+ }
103
+ return JSON.stringify(data);
104
+ }
105
+
106
+ // ---------------------------------------------------------------------------
107
+ // Main account query
108
+ // ---------------------------------------------------------------------------
109
+
110
+ async function accountCommand() {
111
+ const args = parseArgs();
112
+
113
+ // Parse flags
114
+ const addrArg = findArg(args, '--address', '-a');
115
+ const asJson = parseBoolArg(args, '--json', '-j');
116
+ const showData = parseBoolArg(args, '--data', '-d');
117
+ const rpcArg = findArg(args, '--rpc', '-r');
118
+ const rpcUrl = rpcArg ? rpcArg.value : getDefaultRpc();
119
+
120
+ if (!addrArg) {
121
+ console.log(`\n ${C.red}✗ Missing --address flag.${C.reset}`);
122
+ console.log(`\n ${C.cyan}Usage:${C.reset}`);
123
+ console.log(` ${C.cyan}aether account --address <addr>${C.reset} Show account details`);
124
+ console.log(` ${C.cyan}aether account --address <addr> --json${C.reset} JSON output`);
125
+ console.log(` ${C.cyan}aether account --address <addr> --data${C.reset} Show raw account data`);
126
+ console.log(` ${C.cyan}aether account --address <addr> --rpc <url>${C.reset} Use custom RPC\n`);
127
+ process.exit(1);
128
+ }
129
+
130
+ const rawAddress = addrArg.value;
131
+ const address = rawAddress.startsWith('ATH') ? rawAddress : rawAddress;
132
+ const apiAddress = rawAddress.startsWith('ATH') ? rawAddress.slice(3) : rawAddress;
133
+
134
+ if (!asJson) {
135
+ console.log(`\n${C.bright}${C.cyan}── Account Info ──────────────────────────────────────────${C.reset}\n`);
136
+ console.log(` ${C.green}★${C.reset} Address: ${C.bright}${address}${C.reset}`);
137
+ console.log(` ${C.dim} RPC: ${rpcUrl}${C.reset}`);
138
+ console.log();
139
+ }
140
+
141
+ try {
142
+ // Use SDK for real blockchain RPC call
143
+ const client = new aether.AetherClient({ rpcUrl: rpcUrl });
144
+ const account = await client.getAccountInfo(apiAddress);
145
+
146
+ if (!account || account.error) {
147
+ if (asJson) {
148
+ console.log(JSON.stringify({ address, error: account?.error || 'Account not found' }, null, 2));
149
+ } else {
150
+ console.log(` ${C.yellow}⚠ Account not found on chain or RPC error.${C.reset}`);
151
+ console.log(` ${C.dim} This is normal for addresses with no on-chain account.${C.reset}`);
152
+ console.log(` ${C.dim} RPC response: ${JSON.stringify(account?.error || account)}${C.reset}\n`);
153
+ }
154
+ process.exit(1);
155
+ }
156
+
157
+ const lamports = account.lamports || account.lamports === 0 ? account.lamports : 0;
158
+ const owner = account.owner || null;
159
+ const executable = account.executable || false;
160
+ const rentEpoch = account.rent_epoch;
161
+ const data = account.data;
162
+ const dataLen = data ? (Array.isArray(data) ? data.length : typeof data === 'string' ? data.length : 0) : 0;
163
+
164
+ if (asJson) {
165
+ let ownerStr = null;
166
+ if (owner) {
167
+ if (Array.isArray(owner)) {
168
+ ownerStr = 'ATH' + bs58.encode(Buffer.from(owner.slice(0, 32)));
169
+ } else if (typeof owner === 'string') {
170
+ ownerStr = owner.startsWith('ATH') ? owner : 'ATH' + bs58.encode(bs58.decode(owner));
171
+ } else {
172
+ ownerStr = String(owner);
173
+ }
174
+ }
175
+
176
+ console.log(JSON.stringify({
177
+ address,
178
+ rpc: rpcUrl,
179
+ lamports,
180
+ lamports_formatted: formatAether(lamports),
181
+ owner: ownerStr,
182
+ executable,
183
+ rent_epoch: rentEpoch,
184
+ data_size: dataLen,
185
+ data: showData ? formatData(data) : null,
186
+ fetched_at: new Date().toISOString(),
187
+ }, null, 2));
188
+ return;
189
+ }
190
+
191
+ // Human-readable output
192
+ console.log(` ${C.green}✓${C.reset} Found on chain`);
193
+ console.log(` ${C.dim}────────────────────────────────────────${C.reset}`);
194
+ console.log(` ${C.dim} Balance:${C.reset} ${C.bright}${formatAether(lamports)}${C.reset} (${lamports.toLocaleString()} lamports)`);
195
+
196
+ if (owner) {
197
+ let ownerStr;
198
+ if (Array.isArray(owner)) {
199
+ ownerStr = 'ATH' + bs58.encode(Buffer.from(owner.slice(0, 32)));
200
+ } else if (typeof owner === 'string') {
201
+ ownerStr = owner.startsWith('ATH') ? owner : 'ATH' + bs58.encode(bs58.decode(owner));
202
+ } else {
203
+ ownerStr = String(owner);
204
+ }
205
+ console.log(` ${C.dim} Owner:${C.reset} ${C.cyan}${ownerStr}${C.reset}`);
206
+ }
207
+
208
+ console.log(` ${C.dim} Executable:${C.reset} ${C.bright}${(executable ? 'Yes' : 'No')}${C.reset}`);
209
+ if (rentEpoch !== undefined) {
210
+ console.log(` ${C.dim} Rent epoch:${C.reset} ${rentEpoch}`);
211
+ }
212
+
213
+ if (dataLen > 0) {
214
+ console.log(` ${C.dim} Data size:${C.reset} ${C.bright}${dataLen} bytes${C.reset}`);
215
+ } else {
216
+ console.log(` ${C.dim} Data size:${C.reset} 0 bytes`);
217
+ }
218
+
219
+ if (showData && data) {
220
+ console.log();
221
+ console.log(` ${C.dim} Raw data (hex):${C.reset}`);
222
+ const hex = formatData(data);
223
+ // Pretty-print in 32-byte chunks
224
+ const chunks = hex.match(/.{1,64}/g) || [];
225
+ for (let i = 0; i < chunks.length; i++) {
226
+ const offset = (i * 32).toString(16).padStart(8, '0');
227
+ console.log(` ${C.dim}${offset}:${C.reset} ${chunks[i]}`);
228
+ }
229
+ }
230
+
231
+ console.log();
232
+ console.log(` ${C.dim}────────────────────────────────────────${C.reset}`);
233
+
234
+ // Additional context: try to determine account type
235
+ let accountType = 'Unknown';
236
+ if (owner) {
237
+ const ownerStr = Array.isArray(owner)
238
+ ? bs58.encode(Buffer.from(owner.slice(0, 32)))
239
+ : (typeof owner === 'string' && !owner.startsWith('ATH') ? owner : '');
240
+ // Known program IDs (these are common Aether program identifiers)
241
+ const knownPrograms = {
242
+ 'STAKE': 'Stake Program',
243
+ 'SYSTEM': 'System Program',
244
+ 'VOTE': 'Vote Program',
245
+ 'TOKEN': 'Token Program',
246
+ 'MEMO': 'Memo Program',
247
+ };
248
+ const prog = knownPrograms[ownerStr.toUpperCase()];
249
+ if (prog) accountType = prog;
250
+ else if (executable) accountType = 'Executable Program';
251
+ else if (dataLen === 0) accountType = 'Empty Account (no data)';
252
+ else accountType = 'Data Account';
253
+ }
254
+
255
+ console.log(` ${C.dim} Account type:${C.reset} ${C.bright}${accountType}${C.reset}`);
256
+ console.log();
257
+ console.log(` ${C.dim} Fetch via API:${C.reset} GET ${rpcUrl}/v1/account/${apiAddress}`);
258
+ console.log();
259
+
260
+ } catch (err) {
261
+ if (asJson) {
262
+ console.log(JSON.stringify({ address, error: err.message }, null, 2));
263
+ } else {
264
+ console.log(` ${C.red}✗ Failed to fetch account:${C.reset} ${err.message}`);
265
+ console.log(` ${C.dim} Is your validator running? RPC: ${rpcUrl}${C.reset}`);
266
+ console.log(` ${C.dim} Set custom RPC: AETHER_RPC=https://your-rpc-url${C.reset}\n`);
267
+ }
268
+ process.exit(1);
269
+ }
270
+ }
271
+
272
+ // ---------------------------------------------------------------------------
273
+ // Export & run
274
+ // ---------------------------------------------------------------------------
275
+
276
+ module.exports = { accountCommand };
277
+
278
+ if (require.main === module) {
279
+ accountCommand();
280
+ }