@jellylegsai/aether-cli 1.9.2 → 2.0.1

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.
@@ -0,0 +1,275 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * aether-cli token-accounts
4
+ *
5
+ * Get SPL token accounts for a wallet address.
6
+ * Uses @jellylegsai/aether-sdk for REAL HTTP RPC calls.
7
+ *
8
+ * Usage:
9
+ * aether token-accounts <address> List token accounts
10
+ * aether token-accounts --address <addr> List token accounts
11
+ * aether token-accounts --json JSON output
12
+ * aether token-accounts --rpc <url> Custom RPC endpoint
13
+ *
14
+ * RPC Endpoint: GET /v1/tokens/<address>
15
+ * SDK Function: sdk.getTokenAccounts()
16
+ */
17
+
18
+ const path = require('path');
19
+ const fs = require('fs');
20
+ const os = require('os');
21
+
22
+ // Import UI framework for consistent branding
23
+ const { BRANDING, C, indicators, startSpinner, stopSpinner, drawBox, drawTable,
24
+ success, error, warning, info, code, highlight, key, value } = require('../lib/ui');
25
+
26
+ const CLI_VERSION = '1.0.0';
27
+
28
+ // Import SDK — makes REAL HTTP RPC calls to the blockchain
29
+ const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
30
+ const aether = require(sdkPath);
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Helpers
34
+ // ---------------------------------------------------------------------------
35
+
36
+ function getDefaultRpc() {
37
+ return process.env.AETHER_RPC || aether.DEFAULT_RPC_URL || 'http://127.0.0.1:8899';
38
+ }
39
+
40
+ function createClient(rpc) {
41
+ return new aether.AetherClient({ rpcUrl: rpc });
42
+ }
43
+
44
+ function loadConfig() {
45
+ const aetherDir = path.join(os.homedir(), '.aether');
46
+ const cfgPath = path.join(aetherDir, 'config.json');
47
+ if (!fs.existsSync(cfgPath)) return { defaultWallet: null };
48
+ try {
49
+ return JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
50
+ } catch {
51
+ return { defaultWallet: null };
52
+ }
53
+ }
54
+
55
+ function shortenAddress(addr) {
56
+ if (!addr) return 'unknown';
57
+ if (addr.length <= 16) return addr;
58
+ return `${addr.slice(0, 8)}...${addr.slice(-8)}`;
59
+ }
60
+
61
+ function formatTokenAmount(amount, decimals = 9) {
62
+ const val = Number(amount) / Math.pow(10, decimals);
63
+ return val.toFixed(decimals > 6 ? 6 : decimals).replace(/\.?0+$/, '');
64
+ }
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // Argument parsing
68
+ // ---------------------------------------------------------------------------
69
+
70
+ function parseArgs() {
71
+ const args = process.argv.slice(2);
72
+ const options = {
73
+ rpc: getDefaultRpc(),
74
+ address: null,
75
+ asJson: false,
76
+ };
77
+
78
+ for (let i = 0; i < args.length; i++) {
79
+ if (args[i] === '--json' || args[i] === '-j') {
80
+ options.asJson = true;
81
+ } else if (args[i] === '--rpc' || args[i] === '-r') {
82
+ options.rpc = args[++i];
83
+ } else if (args[i] === '--address' || args[i] === '-a') {
84
+ options.address = args[++i];
85
+ } else if (args[i] === '--help' || args[i] === '-h') {
86
+ showHelp();
87
+ process.exit(0);
88
+ } else if (!args[i].startsWith('-') && !options.address) {
89
+ options.address = args[i];
90
+ }
91
+ }
92
+
93
+ // Try default wallet if no address
94
+ if (!options.address) {
95
+ const config = loadConfig();
96
+ if (config.defaultWallet) {
97
+ options.address = config.defaultWallet;
98
+ }
99
+ }
100
+
101
+ return options;
102
+ }
103
+
104
+ function showHelp() {
105
+ console.log(BRANDING.logoCompact);
106
+ console.log(`
107
+ ${C.bright}${C.cyan}aether-cli token-accounts${C.reset} — Get SPL token accounts
108
+
109
+ ${C.bright}USAGE${C.reset}
110
+ aether token-accounts [address] [options]
111
+ aether token-accounts --address <addr> [options]
112
+
113
+ ${C.bright}OPTIONS${C.reset}
114
+ -a, --address <addr> Wallet address (default: configured default)
115
+ -r, --rpc <url> RPC endpoint (default: ${getDefaultRpc()})
116
+ -j, --json Output as JSON
117
+ -h, --help Show this help
118
+
119
+ ${C.bright}DESCRIPTION${C.reset}
120
+ Queries the Aether blockchain for all SPL token accounts
121
+ associated with a wallet address.
122
+
123
+ ${C.bright}SDK METHOD${C.reset}
124
+ client.getTokenAccounts(address) → GET /v1/tokens/<address>
125
+
126
+ ${C.bright}EXAMPLES${C.reset}
127
+ aether token-accounts ATHxxx... # List token accounts
128
+ aether token-accounts --json # JSON output
129
+ aether token-accounts --rpc https://custom-rpc:8899
130
+ `);
131
+ }
132
+
133
+ // ---------------------------------------------------------------------------
134
+ // Token accounts query - REAL RPC call via SDK
135
+ // ---------------------------------------------------------------------------
136
+
137
+ async function fetchTokenAccounts(rpc, address) {
138
+ const client = createClient(rpc);
139
+ const rawAddr = address.startsWith('ATH') ? address.slice(3) : address;
140
+
141
+ // Real RPC call: GET /v1/tokens/<address>
142
+ const tokens = await client.getTokenAccounts(rawAddr);
143
+
144
+ return {
145
+ address,
146
+ tokens: tokens || [],
147
+ rpc,
148
+ timestamp: new Date().toISOString(),
149
+ };
150
+ }
151
+
152
+ // ---------------------------------------------------------------------------
153
+ // Output formatters
154
+ // ---------------------------------------------------------------------------
155
+
156
+ function printTokenAccounts(data) {
157
+ const { address, tokens, rpc } = data;
158
+
159
+ console.log();
160
+ console.log(BRANDING.commandBanner('token-accounts', 'SPL Token Accounts'));
161
+
162
+ console.log(`\n ${key('Wallet:')} ${highlight(address)}`);
163
+ console.log(` ${key('RPC:')} ${C.dim}${rpc}${C.reset}\n`);
164
+
165
+ if (tokens.length === 0) {
166
+ console.log(` ${warning('No token accounts found for this wallet.')}\n`);
167
+ return;
168
+ }
169
+
170
+ // Build table rows
171
+ const rows = tokens.map((t, i) => {
172
+ const mint = shortenAddress(t.mint || t.token_mint || 'unknown');
173
+ const amount = formatTokenAmount(t.amount || t.balance || 0, t.decimals || 9);
174
+ const decimals = t.decimals || 9;
175
+ const isFrozen = t.is_frozen || t.frozen ? indicators.warning : indicators.success;
176
+
177
+ return [
178
+ `${i + 1}`,
179
+ mint,
180
+ `${amount}`,
181
+ `${decimals}`,
182
+ isFrozen,
183
+ ];
184
+ });
185
+
186
+ console.log(drawTable(
187
+ ['#', 'Mint', 'Balance', 'Decimals', 'Status'],
188
+ rows,
189
+ { headerColor: C.cyan + C.bright, borderColor: C.dim }
190
+ ));
191
+
192
+ console.log(`\n ${key('Total Accounts:')} ${value(tokens.length)}`);
193
+ console.log(` ${C.dim}SDK: getTokenAccounts()${C.reset}\n`);
194
+ }
195
+
196
+ function printJson(data) {
197
+ console.log(JSON.stringify({
198
+ address: data.address,
199
+ tokens: data.tokens,
200
+ total_accounts: data.tokens.length,
201
+ rpc: data.rpc,
202
+ timestamp: data.timestamp,
203
+ cli_version: CLI_VERSION,
204
+ sdk: '@jellylegsai/aether-sdk',
205
+ rpc_endpoint: `GET /v1/tokens/${data.address}`,
206
+ }, null, 2));
207
+ }
208
+
209
+ // ---------------------------------------------------------------------------
210
+ // Main command
211
+ // ---------------------------------------------------------------------------
212
+
213
+ async function tokenAccountsCommand() {
214
+ const options = parseArgs();
215
+ const { rpc, address, asJson } = options;
216
+
217
+ if (!address) {
218
+ console.log(`\n${indicators.error} ${error('No address provided.')}`);
219
+ console.log(` ${C.dim}Usage: aether token-accounts <address>${C.reset}`);
220
+ console.log(` ${C.dim} aether token-accounts --address <address>${C.reset}`);
221
+ console.log(` ${C.dim} aether token-accounts --help for more info${C.reset}\n`);
222
+ process.exit(1);
223
+ }
224
+
225
+ if (!asJson) {
226
+ startSpinner('Fetching token accounts');
227
+ }
228
+
229
+ try {
230
+ // Real blockchain RPC call via SDK
231
+ const data = await fetchTokenAccounts(rpc, address);
232
+
233
+ if (!asJson) {
234
+ stopSpinner(true, 'Token accounts retrieved');
235
+ }
236
+
237
+ if (asJson) {
238
+ printJson(data);
239
+ } else {
240
+ printTokenAccounts(data);
241
+ }
242
+ } catch (err) {
243
+ if (!asJson) {
244
+ stopSpinner(false, 'Failed');
245
+ }
246
+
247
+ if (asJson) {
248
+ console.log(JSON.stringify({
249
+ error: err.message,
250
+ address,
251
+ rpc,
252
+ timestamp: new Date().toISOString(),
253
+ }, null, 2));
254
+ } else {
255
+ console.log(`\n${indicators.error} ${error('Failed to fetch token accounts:')} ${err.message}`);
256
+ console.log(` ${C.dim}Address: ${address}${C.reset}`);
257
+ console.log(` ${C.dim}RPC: ${rpc}${C.reset}`);
258
+ console.log(` ${C.dim}Make sure the Aether node is running${C.reset}\n`);
259
+ }
260
+ process.exit(1);
261
+ }
262
+ }
263
+
264
+ // ---------------------------------------------------------------------------
265
+ // Exports
266
+ // ---------------------------------------------------------------------------
267
+
268
+ module.exports = { tokenAccountsCommand: tokenAccountsCommand };
269
+
270
+ if (require.main === module) {
271
+ tokenAccountsCommand().catch(err => {
272
+ console.error(`${indicators.error} ${error('Token-accounts command failed:')} ${err.message}`);
273
+ process.exit(1);
274
+ });
275
+ }
@@ -18,17 +18,9 @@ const os = require('os');
18
18
  const path = require('path');
19
19
  const readline = require('readline');
20
20
 
21
- // ANSI colours
22
- const C = {
23
- reset: '\x1b[0m',
24
- bright: '\x1b[1m',
25
- dim: '\x1b[2m',
26
- red: '\x1b[31m',
27
- green: '\x1b[32m',
28
- yellow: '\x1b[33m',
29
- cyan: '\x1b[36m',
30
- magenta: '\x1b[35m',
31
- };
21
+ // Import UI framework for consistent branding
22
+ const { BRANDING, C, indicators, startSpinner, stopSpinner, drawBox,
23
+ success, error, warning, info, code, highlight } = require('../lib/ui');
32
24
 
33
25
  // Import SDK — REAL blockchain RPC calls to http://127.0.0.1:8899
34
26
  const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
@@ -29,17 +29,9 @@ const bip39 = require('bip39');
29
29
  const sdkPath = path.join(__dirname, '..', 'sdk', 'index.js');
30
30
  const aether = require(sdkPath);
31
31
 
32
- // ANSI colours
33
- const C = {
34
- reset: '\x1b[0m',
35
- bright: '\x1b[1m',
36
- dim: '\x1b[2m',
37
- red: '\x1b[31m',
38
- green: '\x1b[32m',
39
- yellow: '\x1b[33m',
40
- cyan: '\x1b[36m',
41
- magenta: '\x1b[35m',
42
- };
32
+ // Import UI framework for consistent branding
33
+ const { BRANDING, C, indicators, startSpinner, stopSpinner, drawBox, drawTable,
34
+ success, error, warning, info, code, highlight, value } = require('../lib/ui');
43
35
 
44
36
  const CLI_VERSION = '1.0.0';
45
37
  const DERIVATION_PATH = "m/44'/7777777'/0'/0'";