@alchemy/cli 0.3.1 → 0.4.0

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,7 +10,6 @@ import {
10
10
  errAccessDenied,
11
11
  errAccessKeyRequired,
12
12
  errAdminAPI,
13
- errAppRequired,
14
13
  errAuthRequired,
15
14
  errInvalidAPIKey,
16
15
  errInvalidAccessKey,
@@ -43,7 +42,7 @@ import {
43
42
  verbose,
44
43
  withSpinner,
45
44
  yellow
46
- } from "./chunk-6XTLILDF.js";
45
+ } from "./chunk-TH75DFAY.js";
47
46
 
48
47
  // src/lib/client-utils.ts
49
48
  function isLocalhost(hostname) {
@@ -383,6 +382,21 @@ async function readStdinArg(name) {
383
382
  }
384
383
  return data;
385
384
  }
385
+ async function readStdinLines(name) {
386
+ if (process.stdin.isTTY) {
387
+ throw errInvalidArgs(`Missing <${name}>. Provide it as an argument or pipe via stdin.`);
388
+ }
389
+ process.stdin.setEncoding("utf-8");
390
+ let input = "";
391
+ for await (const chunk of process.stdin) {
392
+ input += chunk;
393
+ }
394
+ const lines = input.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
395
+ if (lines.length === 0) {
396
+ throw errInvalidArgs(`No <${name}> received on stdin.`);
397
+ }
398
+ return lines;
399
+ }
386
400
  function validateAddress(address) {
387
401
  if (!ADDRESS_RE.test(address)) {
388
402
  throw errInvalidArgs(
@@ -1201,26 +1215,6 @@ function clientFromFlags(program, opts) {
1201
1215
  if (!apiKey) throw errAuthRequired();
1202
1216
  return new Client(apiKey, network);
1203
1217
  }
1204
- function appNetworkToSlug(rpcUrl) {
1205
- let parsed;
1206
- try {
1207
- parsed = new URL(rpcUrl);
1208
- } catch {
1209
- return null;
1210
- }
1211
- const suffix = ".g.alchemy.com";
1212
- if (!parsed.hostname.endsWith(suffix)) return null;
1213
- const slug = parsed.hostname.slice(0, -suffix.length);
1214
- return slug || null;
1215
- }
1216
- async function resolveConfiguredNetworkSlugs(program, appIdOverride) {
1217
- const appId = appIdOverride || resolveAppId(program);
1218
- if (!appId) throw errAppRequired();
1219
- const admin = adminClientFromFlags(program);
1220
- const app = await admin.getApp(appId);
1221
- const slugs = app.chainNetworks.map((network) => appNetworkToSlug(network.rpcUrl)).filter((slug) => Boolean(slug));
1222
- return Array.from(new Set(slugs)).sort((a, b) => a.localeCompare(b));
1223
- }
1224
1218
 
1225
1219
  // src/commands/wallet.ts
1226
1220
  var WALLET_KEYS_DIR = "wallet-keys";
@@ -1316,6 +1310,7 @@ export {
1316
1310
  AdminClient,
1317
1311
  splitCommaList,
1318
1312
  readStdinArg,
1313
+ readStdinLines,
1319
1314
  validateAddress,
1320
1315
  resolveAddress,
1321
1316
  validateTxHash,
@@ -1326,7 +1321,6 @@ export {
1326
1321
  resolveAppId,
1327
1322
  adminClientFromFlags,
1328
1323
  clientFromFlags,
1329
- resolveConfiguredNetworkSlugs,
1330
1324
  generateAndPersistWallet,
1331
1325
  importAndPersistWallet,
1332
1326
  registerWallet
@@ -198,7 +198,7 @@ function setFlags(opts) {
198
198
  timeout = opts.timeout;
199
199
  }
200
200
  function isRevealMode() {
201
- return reveal && Boolean(process.stdout.isTTY);
201
+ return reveal;
202
202
  }
203
203
  function isJSONMode() {
204
204
  if (forceJSON) return true;
@@ -1114,7 +1114,7 @@ function semverLT(a, b) {
1114
1114
  return false;
1115
1115
  }
1116
1116
  function currentVersion() {
1117
- return true ? "0.3.1" : "0.0.0";
1117
+ return true ? "0.4.0" : "0.0.0";
1118
1118
  }
1119
1119
  function toUpdateStatus(latestVersion, checkedAt) {
1120
1120
  const current = currentVersion();
@@ -2,7 +2,7 @@
2
2
  if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
3
  import {
4
4
  isInteractiveAllowed
5
- } from "./chunk-6XTLILDF.js";
5
+ } from "./chunk-TH75DFAY.js";
6
6
 
7
7
  // src/lib/networks.ts
8
8
  var TESTNET_TOKEN_RE = /(testnet|sepolia|holesky|hoodi|devnet|minato|amoy|fuji|saigon|cardona|aeneid|curtis|chiado|cassiopeia|blaze|ropsten|signet|mocha|fam|bepolia)$/i;
package/dist/index.js CHANGED
@@ -5,24 +5,24 @@ import {
5
5
  clientFromFlags,
6
6
  fetchWithTimeout,
7
7
  readStdinArg,
8
+ readStdinLines,
8
9
  registerConfig,
9
10
  registerWallet,
10
11
  resolveAPIKey,
11
12
  resolveAddress,
12
13
  resolveAppId,
13
- resolveConfiguredNetworkSlugs,
14
14
  resolveNetwork,
15
15
  splitCommaList,
16
16
  validateAddress,
17
17
  validateTxHash
18
- } from "./chunk-J6RZM4CJ.js";
18
+ } from "./chunk-PIWNNNMZ.js";
19
19
  import {
20
20
  getRPCNetworks,
21
21
  getSetupStatus,
22
22
  isSetupComplete,
23
23
  nativeTokenSymbol,
24
24
  shouldRunOnboarding
25
- } from "./chunk-QDDJ3OYO.js";
25
+ } from "./chunk-Z3LXQFIY.js";
26
26
  import {
27
27
  EXIT_CODES,
28
28
  ErrorCode,
@@ -32,6 +32,7 @@ import {
32
32
  debug,
33
33
  dim,
34
34
  emptyState,
35
+ errAppRequired,
35
36
  errAuthRequired,
36
37
  errInvalidAPIKey,
37
38
  errInvalidArgs,
@@ -70,7 +71,7 @@ import {
70
71
  verbose,
71
72
  weiToEth,
72
73
  withSpinner
73
- } from "./chunk-6XTLILDF.js";
74
+ } from "./chunk-TH75DFAY.js";
74
75
 
75
76
  // src/index.ts
76
77
  import { Command, Help } from "commander";
@@ -108,6 +109,54 @@ Examples:
108
109
  }
109
110
 
110
111
  // src/commands/balance.ts
112
+ async function fetchBalance(program2, addressInput, blockParam) {
113
+ const client = clientFromFlags(program2);
114
+ const address = await resolveAddress(addressInput, client);
115
+ const result = await withSpinner(
116
+ "Fetching balance\u2026",
117
+ "Balance fetched",
118
+ () => client.call("eth_getBalance", [address, blockParam])
119
+ );
120
+ const wei = BigInt(result);
121
+ const network = resolveNetwork(program2);
122
+ const symbol = nativeTokenSymbol(network);
123
+ if (isJSONMode()) {
124
+ printJSON({
125
+ address,
126
+ wei: wei.toString(),
127
+ balance: weiToEth(wei),
128
+ symbol,
129
+ network
130
+ });
131
+ } else {
132
+ printKeyValueBox([
133
+ ["Address", address],
134
+ ["Balance", green(`${weiToEth(wei)} ${symbol}`)],
135
+ ["Network", network]
136
+ ]);
137
+ if (verbose) {
138
+ console.log("");
139
+ printJSON({
140
+ rpcMethod: "eth_getBalance",
141
+ rpcParams: [address, blockParam],
142
+ rpcResult: result
143
+ });
144
+ }
145
+ }
146
+ }
147
+ function resolveBlockParam(block) {
148
+ let blockParam = block ?? "latest";
149
+ if (blockParam !== "latest" && blockParam !== "earliest" && blockParam !== "pending") {
150
+ if (!blockParam.startsWith("0x")) {
151
+ const num = parseInt(blockParam, 10);
152
+ if (isNaN(num) || num < 0) {
153
+ throw errInvalidArgs("Block must be a number, hex, or tag (latest, earliest, pending).");
154
+ }
155
+ blockParam = `0x${num.toString(16)}`;
156
+ }
157
+ }
158
+ return blockParam;
159
+ }
111
160
  function registerBalance(program2) {
112
161
  program2.command("balance").argument("[address]", "Wallet address (0x...) or ENS name, or pipe via stdin").alias("bal").description("Get the native token balance of an address").addHelpText(
113
162
  "after",
@@ -117,51 +166,21 @@ Examples:
117
166
  alchemy balance 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 -n polygon-mainnet
118
167
  echo 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 | alchemy balance
119
168
  alchemy balance 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --block 15537393
120
- alchemy balance vitalik.eth`
169
+ alchemy balance vitalik.eth
170
+ cat addresses.txt | alchemy balance`
121
171
  ).option("--block <block>", "Block number, hex, or tag (default: latest)").action(async (addressArg, opts) => {
122
172
  try {
123
- const addressInput = addressArg ?? await readStdinArg("address");
124
- const client = clientFromFlags(program2);
125
- const address = await resolveAddress(addressInput, client);
126
- let blockParam = opts?.block ?? "latest";
127
- if (blockParam !== "latest" && blockParam !== "earliest" && blockParam !== "pending") {
128
- if (!blockParam.startsWith("0x")) {
129
- const num = parseInt(blockParam, 10);
130
- if (isNaN(num) || num < 0) {
131
- throw errInvalidArgs("Block must be a number, hex, or tag (latest, earliest, pending).");
132
- }
133
- blockParam = `0x${num.toString(16)}`;
134
- }
173
+ const blockParam = resolveBlockParam(opts?.block);
174
+ if (addressArg) {
175
+ await fetchBalance(program2, addressArg, blockParam);
176
+ return;
135
177
  }
136
- const result = await withSpinner(
137
- "Fetching balance\u2026",
138
- "Balance fetched",
139
- () => client.call("eth_getBalance", [address, blockParam])
140
- );
141
- const wei = BigInt(result);
142
- const network = resolveNetwork(program2);
143
- const symbol = nativeTokenSymbol(network);
144
- if (isJSONMode()) {
145
- printJSON({
146
- address,
147
- wei: wei.toString(),
148
- balance: weiToEth(wei),
149
- symbol,
150
- network
151
- });
152
- } else {
153
- printKeyValueBox([
154
- ["Address", address],
155
- ["Balance", green(`${weiToEth(wei)} ${symbol}`)],
156
- ["Network", network]
157
- ]);
158
- if (verbose) {
159
- console.log("");
160
- printJSON({
161
- rpcMethod: "eth_getBalance",
162
- rpcParams: [address, "latest"],
163
- rpcResult: result
164
- });
178
+ const lines = await readStdinLines("address");
179
+ for (const line of lines) {
180
+ try {
181
+ await fetchBalance(program2, line, blockParam);
182
+ } catch (err) {
183
+ exitWithError(err);
165
184
  }
166
185
  }
167
186
  } catch (err) {
@@ -239,29 +258,28 @@ function formatGasSummary(gasUsed, gasLimit, options) {
239
258
 
240
259
  // src/commands/tx.ts
241
260
  function registerTx(program2) {
242
- program2.command("tx").argument("[hash]", "Transaction hash (0x...) or pipe via stdin").description("Get transaction details by hash").addHelpText(
261
+ program2.command("tx").argument("[hash]", "Transaction hash (0x...) or pipe via stdin").description("Get transaction details by hash (use 'receipt' for receipt data)").addHelpText(
243
262
  "after",
244
263
  `
245
264
  Examples:
246
265
  alchemy tx 0xabc123...
247
- echo 0xabc123... | alchemy tx`
266
+ echo 0xabc123... | alchemy tx
267
+
268
+ Tip: use 'alchemy receipt <hash>' to get the transaction receipt (status, gas used, logs).`
248
269
  ).action(async (hashArg) => {
249
270
  try {
250
271
  const hash = hashArg ?? await readStdinArg("hash");
251
272
  validateTxHash(hash);
252
273
  const client = clientFromFlags(program2);
253
- const [tx, receipt] = await withSpinner("Fetching transaction\u2026", "Transaction fetched", async () => {
274
+ const tx = await withSpinner("Fetching transaction\u2026", "Transaction fetched", async () => {
254
275
  const t = await client.call("eth_getTransactionByHash", [
255
276
  hash
256
277
  ]);
257
278
  if (!t) throw errNotFound(`transaction ${hash}`);
258
- const r = await client.call("eth_getTransactionReceipt", [
259
- hash
260
- ]);
261
- return [t, r];
279
+ return t;
262
280
  });
263
281
  if (isJSONMode()) {
264
- printJSON({ transaction: tx, receipt });
282
+ printJSON(tx);
265
283
  return;
266
284
  }
267
285
  const network = resolveNetwork(program2);
@@ -277,20 +295,13 @@ Examples:
277
295
  const formatted = formatHexWithRaw(tx.blockNumber);
278
296
  pairs.push(["Block", formatted ?? String(tx.blockNumber)]);
279
297
  }
280
- if (receipt) {
281
- if (receipt.status === "0x1") {
282
- pairs.push(["Status", `${successBadge()} Success`]);
283
- } else if (receipt.status) {
284
- pairs.push(["Status", `${failBadge()} Failed`]);
285
- }
286
- if (receipt.gasUsed) {
287
- const formatted = formatHexWithRaw(receipt.gasUsed);
288
- pairs.push(["Gas Used", formatted ?? String(receipt.gasUsed)]);
289
- }
290
- if (receipt.effectiveGasPrice) {
291
- const formatted = formatGweiWithRaw(receipt.effectiveGasPrice);
292
- pairs.push(["Gas Price", formatted ?? String(receipt.effectiveGasPrice)]);
293
- }
298
+ if (tx.nonce) {
299
+ const formatted = formatHexWithRaw(tx.nonce);
300
+ pairs.push(["Nonce", formatted ?? String(tx.nonce)]);
301
+ }
302
+ if (tx.gasPrice) {
303
+ const formatted = formatGweiWithRaw(tx.gasPrice);
304
+ pairs.push(["Gas Price", formatted ?? String(tx.gasPrice)]);
294
305
  }
295
306
  const explorerURL = etherscanTxURL(hash, network);
296
307
  if (explorerURL) {
@@ -299,7 +310,7 @@ Examples:
299
310
  printKeyValueBox(pairs);
300
311
  if (verbose) {
301
312
  console.log("");
302
- printJSON({ transaction: tx, receipt });
313
+ printJSON(tx);
303
314
  }
304
315
  } catch (err) {
305
316
  exitWithError(err);
@@ -314,7 +325,9 @@ function registerReceipt(program2) {
314
325
  `
315
326
  Examples:
316
327
  alchemy receipt 0xabc123...
317
- echo 0xabc123... | alchemy receipt`
328
+ echo 0xabc123... | alchemy receipt
329
+
330
+ Tip: use 'alchemy tx <hash>' for transaction details (value, block, nonce). Receipt provides execution results (status, gas used, logs).`
318
331
  ).action(async (hashArg) => {
319
332
  try {
320
333
  const hash = hashArg ?? await readStdinArg("hash");
@@ -598,11 +611,13 @@ async function promptTokensPagination() {
598
611
  if (action === null) return "stop";
599
612
  return action;
600
613
  }
601
- function formatTokenRows(balances) {
602
- const nonZero = balances.filter(
614
+ function filterNonZero(balances) {
615
+ return balances.filter(
603
616
  (tb) => tb.tokenBalance !== "0x0" && tb.tokenBalance !== "0x0000000000000000000000000000000000000000000000000000000000000000"
604
617
  );
605
- return nonZero.map((tb) => {
618
+ }
619
+ function formatTokenRows(balances) {
620
+ return filterNonZero(balances).map((tb) => {
606
621
  let decimalBalance = dim("unparseable");
607
622
  try {
608
623
  decimalBalance = BigInt(tb.tokenBalance).toString();
@@ -611,15 +626,72 @@ function formatTokenRows(balances) {
611
626
  return [tb.contractAddress, decimalBalance, tb.tokenBalance];
612
627
  });
613
628
  }
629
+ function formatWithDecimals(rawBalance, decimals) {
630
+ if (decimals === null || decimals === 0) {
631
+ try {
632
+ return BigInt(rawBalance).toString();
633
+ } catch {
634
+ return rawBalance;
635
+ }
636
+ }
637
+ try {
638
+ const raw = BigInt(rawBalance);
639
+ const divisor = 10n ** BigInt(decimals);
640
+ const whole = raw / divisor;
641
+ const remainder = raw % divisor;
642
+ if (remainder === 0n) return whole.toString();
643
+ const fracStr = remainder.toString().padStart(decimals, "0").replace(/0+$/, "");
644
+ return `${whole}.${fracStr}`;
645
+ } catch {
646
+ return rawBalance;
647
+ }
648
+ }
649
+ async function resolveMetadata(client, balances) {
650
+ const nonZero = filterNonZero(balances);
651
+ const results = await Promise.all(
652
+ nonZero.map(async (tb) => {
653
+ try {
654
+ const meta = await client.call("alchemy_getTokenMetadata", [tb.contractAddress]);
655
+ return [tb.contractAddress, meta];
656
+ } catch {
657
+ return [tb.contractAddress, { name: null, symbol: null, decimals: null, logo: null }];
658
+ }
659
+ })
660
+ );
661
+ return new Map(results);
662
+ }
663
+ function formatResolvedRows(balances, metadata) {
664
+ return filterNonZero(balances).map((tb) => {
665
+ const meta = metadata.get(tb.contractAddress);
666
+ const symbol = meta?.symbol ?? "???";
667
+ const formatted = formatWithDecimals(tb.tokenBalance, meta?.decimals ?? null);
668
+ return [tb.contractAddress, symbol, `${formatted} ${symbol}`];
669
+ });
670
+ }
671
+ function formatResolvedJSON(balances, metadata) {
672
+ return filterNonZero(balances).map((tb) => {
673
+ const meta = metadata.get(tb.contractAddress);
674
+ return {
675
+ contractAddress: tb.contractAddress,
676
+ tokenBalance: tb.tokenBalance,
677
+ ...meta?.symbol && { symbol: meta.symbol },
678
+ ...meta?.name && { name: meta.name },
679
+ ...meta?.decimals !== null && meta?.decimals !== void 0 && { decimals: meta.decimals },
680
+ ...meta?.decimals !== null && meta?.decimals !== void 0 && {
681
+ formattedBalance: formatWithDecimals(tb.tokenBalance, meta.decimals)
682
+ }
683
+ };
684
+ });
685
+ }
614
686
  function registerTokens(program2) {
615
- const cmd = program2.command("tokens").description("Token API wrappers").argument("[address]", "Wallet address or ENS name (default action: list balances)").option("--page-key <key>", "Pagination key from a previous response").addHelpText(
687
+ const cmd = program2.command("tokens").description("Token API wrappers");
688
+ cmd.command("balances").argument("[address]", "Wallet address or ENS name, or pipe via stdin").description("Get ERC-20 token balances for an address").option("--page-key <key>", "Pagination key from a previous response").option("--metadata", "Fetch token metadata (symbol, decimals) and show formatted balances").addHelpText(
616
689
  "after",
617
690
  `
618
691
  Examples:
619
- alchemy tokens 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
620
- alchemy tokens metadata 0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eB48
621
- alchemy tokens allowance --owner 0x... --spender 0x... --contract 0x...
622
- echo 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 | alchemy tokens`
692
+ alchemy tokens balances 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
693
+ alchemy tokens balances 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --metadata
694
+ echo 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 | alchemy tokens balances`
623
695
  ).action(async (addressArg, opts) => {
624
696
  try {
625
697
  const addressInput = addressArg ?? await readStdinArg("address");
@@ -634,24 +706,50 @@ Examples:
634
706
  "Token balances fetched",
635
707
  () => client.call("alchemy_getTokenBalances", params)
636
708
  );
637
- if (isJSONMode()) {
638
- printJSON(result);
709
+ const nonZero = filterNonZero(result.tokenBalances);
710
+ if (nonZero.length === 0) {
711
+ if (isJSONMode()) {
712
+ printJSON(result);
713
+ } else {
714
+ emptyState("No token balances found.");
715
+ }
639
716
  return;
640
717
  }
641
- const rows = formatTokenRows(result.tokenBalances);
642
- if (rows.length === 0) {
643
- emptyState("No token balances found.");
718
+ const metadata = opts.metadata ? await withSpinner(
719
+ `Resolving metadata for ${nonZero.length} tokens\u2026`,
720
+ "Metadata resolved",
721
+ () => resolveMetadata(client, result.tokenBalances)
722
+ ) : null;
723
+ if (isJSONMode()) {
724
+ if (metadata) {
725
+ printJSON({
726
+ address: result.address,
727
+ tokenBalances: formatResolvedJSON(result.tokenBalances, metadata),
728
+ ...result.pageKey && { pageKey: result.pageKey }
729
+ });
730
+ } else {
731
+ printJSON(result);
732
+ }
644
733
  return;
645
734
  }
646
- let totalShown = rows.length;
735
+ let totalShown = nonZero.length;
647
736
  printKeyValueBox([
648
737
  ["Address", address],
649
738
  ["Network", client.network],
650
739
  ["Tokens", String(totalShown)]
651
740
  ]);
652
- printTable(["Contract", "Balance (base units)", "Raw (hex)"], rows);
741
+ if (metadata) {
742
+ const rows = formatResolvedRows(result.tokenBalances, metadata);
743
+ printTable(["Contract", "Symbol", "Balance"], rows);
744
+ } else {
745
+ const rows = formatTokenRows(result.tokenBalances);
746
+ printTable(["Contract", "Balance (base units)", "Raw (hex)"], rows);
747
+ }
653
748
  console.log(`
654
749
  ${dim(`${totalShown} tokens (zero balances hidden).`)}`);
750
+ if (!metadata) {
751
+ console.log(` ${dim("Tip: use --metadata to fetch token symbols, decimals, and show formatted balances.")}`);
752
+ }
655
753
  if (verbose) {
656
754
  console.log("");
657
755
  printJSON(result);
@@ -674,10 +772,21 @@ Examples:
674
772
  printJSON(nextResult);
675
773
  return;
676
774
  }
677
- const nextRows = formatTokenRows(nextResult.tokenBalances);
678
- totalShown += nextRows.length;
679
- if (nextRows.length > 0) {
680
- printTable(["Contract", "Balance (base units)", "Raw (hex)"], nextRows);
775
+ const nextNonZero = filterNonZero(nextResult.tokenBalances);
776
+ totalShown += nextNonZero.length;
777
+ if (nextNonZero.length > 0) {
778
+ if (metadata) {
779
+ const nextMeta = await withSpinner(
780
+ `Resolving metadata for ${nextNonZero.length} tokens\u2026`,
781
+ "Metadata resolved",
782
+ () => resolveMetadata(client, nextResult.tokenBalances)
783
+ );
784
+ const rows = formatResolvedRows(nextResult.tokenBalances, nextMeta);
785
+ printTable(["Contract", "Symbol", "Balance"], rows);
786
+ } else {
787
+ const rows = formatTokenRows(nextResult.tokenBalances);
788
+ printTable(["Contract", "Balance (base units)", "Raw (hex)"], rows);
789
+ }
681
790
  }
682
791
  console.log(`
683
792
  ${dim(`${totalShown} tokens total (zero balances hidden).`)}`);
@@ -691,7 +800,12 @@ Examples:
691
800
  exitWithError(err);
692
801
  }
693
802
  });
694
- cmd.command("metadata <contract>").description("Get ERC-20 token metadata").action(async (contract) => {
803
+ cmd.command("metadata <contract>").description("Get ERC-20 token metadata (name, symbol, decimals, logo)").addHelpText(
804
+ "after",
805
+ `
806
+ Examples:
807
+ alchemy tokens metadata 0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eB48`
808
+ ).action(async (contract) => {
695
809
  try {
696
810
  validateAddress(contract);
697
811
  const client = clientFromFlags(program2);
@@ -732,34 +846,22 @@ Examples:
732
846
  // src/commands/network.ts
733
847
  function registerNetwork(program2) {
734
848
  const cmd = program2.command("network").description("Manage networks");
735
- cmd.command("list").description("List RPC network IDs for use with --network (e.g. eth-mainnet)").option(
736
- "--configured",
737
- "List only configured app RPC networks (requires access key and app context)"
738
- ).option(
739
- "--app-id <id>",
740
- "App ID for configured network lookups (overrides saved app)"
741
- ).action(async (opts) => {
849
+ cmd.command("list").description("List RPC network IDs for use with --network (e.g. eth-mainnet)").option("--mainnet-only", "Show only mainnet networks").option("--testnet-only", "Show only testnet networks").option("--search <term>", "Filter networks by name or ID").action(async (opts) => {
742
850
  try {
743
- const supported = getRPCNetworks();
851
+ let display = getRPCNetworks();
744
852
  const current = resolveNetwork(program2);
745
- const configured = opts.configured ? await withSpinner(
746
- "Fetching configured networks\u2026",
747
- "Configured networks fetched",
748
- () => resolveConfiguredNetworkSlugs(program2, opts.appId)
749
- ) : null;
750
- const configuredSet = new Set(configured ?? []);
751
- const appId = opts.configured ? opts.appId || resolveAppId(program2) : void 0;
752
- const display = configured ? supported.filter((network) => configuredSet.has(network.id)) : supported;
853
+ if (opts.mainnetOnly) {
854
+ display = display.filter((n) => !n.isTestnet);
855
+ } else if (opts.testnetOnly) {
856
+ display = display.filter((n) => n.isTestnet);
857
+ }
858
+ if (opts.search) {
859
+ const term = opts.search.toLowerCase();
860
+ display = display.filter(
861
+ (n) => n.id.toLowerCase().includes(term) || n.name.toLowerCase().includes(term) || n.family.toLowerCase().includes(term)
862
+ );
863
+ }
753
864
  if (isJSONMode()) {
754
- if (configured) {
755
- printJSON({
756
- mode: "configured",
757
- appId,
758
- configuredNetworkIds: configured,
759
- networks: display
760
- });
761
- return;
762
- }
763
865
  printJSON(display);
764
866
  return;
765
867
  }
@@ -771,23 +873,18 @@ function registerNetwork(program2) {
771
873
  return [idCell, nameCell, network.family, testnetCell];
772
874
  });
773
875
  printTable(["Network ID", "Name", "Family", "Testnet"], rows);
774
- if (configured) {
775
- console.log(
776
- `
777
- ${dim(`Configured networks for app ${appId}: ${display.length}`)}`
778
- );
779
- }
780
876
  console.log(`
781
877
  Current: ${green(current)}`);
782
878
  console.log(
783
879
  ` ${dim("Need Admin API chain identifiers (e.g. ETH_MAINNET)? See: apps chains")}`
784
880
  );
881
+ console.log(
882
+ ` ${dim("Need configured networks for an app? See: apps networks")}`
883
+ );
785
884
  if (verbose) {
786
885
  console.log("");
787
886
  printJSON({
788
- mode: configured ? "configured" : "all",
789
- appId: appId ?? null,
790
- configuredNetworkIds: configured ?? null,
887
+ mode: "all",
791
888
  networks: display,
792
889
  currentNetwork: current
793
890
  });
@@ -1182,6 +1279,37 @@ function registerApps(program2) {
1182
1279
  exitWithError(err);
1183
1280
  }
1184
1281
  });
1282
+ cmd.command("configured-networks").description("List RPC network slugs configured for an app").option("--app-id <id>", "App ID (overrides saved app)").action(async (opts) => {
1283
+ try {
1284
+ const admin = adminClientFromFlags(program2);
1285
+ const appId = opts.appId || resolveAppId(program2);
1286
+ if (!appId) throw errAppRequired();
1287
+ const app = await withSpinner(
1288
+ "Fetching app\u2026",
1289
+ "App fetched",
1290
+ () => admin.getApp(appId)
1291
+ );
1292
+ const slugs = app.chainNetworks.map((n) => {
1293
+ const match = n.rpcUrl?.match(/^https:\/\/([^.]+)\.g\.alchemy\.com(?:\/|$)/);
1294
+ return match ? match[1] : null;
1295
+ }).filter((s) => Boolean(s));
1296
+ const uniqueSlugs = Array.from(new Set(slugs)).sort();
1297
+ if (isJSONMode()) {
1298
+ printJSON({ appId: app.id, appName: app.name, networks: uniqueSlugs });
1299
+ return;
1300
+ }
1301
+ if (uniqueSlugs.length === 0) {
1302
+ emptyState(`No RPC networks configured for ${app.name}.`);
1303
+ return;
1304
+ }
1305
+ const rows = uniqueSlugs.map((slug) => [slug]);
1306
+ printTable(["Network ID"], rows);
1307
+ console.log(`
1308
+ ${dim(`${uniqueSlugs.length} networks configured for ${app.name} (${app.id})`)}`);
1309
+ } catch (err) {
1310
+ exitWithError(err);
1311
+ }
1312
+ });
1185
1313
  cmd.command("chains").description("List Admin API chain identifiers for app configuration (e.g. ETH_MAINNET)").action(async () => {
1186
1314
  try {
1187
1315
  const admin = adminClientFromFlags(program2);
@@ -1534,7 +1662,13 @@ function registerPrices(program2) {
1534
1662
  exitWithError(err);
1535
1663
  }
1536
1664
  });
1537
- cmd.command("historical").description("Get historical prices").requiredOption("--body <json>", "JSON request payload").action(async (opts) => {
1665
+ cmd.command("historical").description("Get historical prices").requiredOption("--body <json>", "JSON request payload").addHelpText(
1666
+ "after",
1667
+ `
1668
+ Examples:
1669
+ alchemy prices historical --body '{"symbol":"ETH","startTime":"2024-01-01T00:00:00Z","endTime":"2024-01-02T00:00:00Z","interval":"1h"}'
1670
+ alchemy prices historical --body '{"address":"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48","network":"eth-mainnet","startTime":"2024-06-01","endTime":"2024-06-07","interval":"1d"}'`
1671
+ ).action(async (opts) => {
1538
1672
  try {
1539
1673
  const apiKey = resolveAPIKey(program2);
1540
1674
  const payload = JSON.parse(opts.body);
@@ -1621,21 +1755,6 @@ function registerPortfolio(program2) {
1621
1755
  exitWithError(err);
1622
1756
  }
1623
1757
  });
1624
- cmd.command("transactions").description("Get transaction history by address/network pairs").requiredOption("--body <json>", "JSON body for /transactions/history/by-address").action(async (opts) => {
1625
- try {
1626
- const apiKey = resolveAPIKey(program2);
1627
- const result = await runDataCall(
1628
- apiKey,
1629
- "transaction history",
1630
- "/transactions/history/by-address",
1631
- JSON.parse(opts.body)
1632
- );
1633
- if (isJSONMode()) printJSON(result);
1634
- else printSyntaxJSON(result);
1635
- } catch (err) {
1636
- exitWithError(err);
1637
- }
1638
- });
1639
1758
  }
1640
1759
 
1641
1760
  // src/commands/simulate.ts
@@ -1714,15 +1833,24 @@ function registerWebhooks(program2) {
1714
1833
  exitWithError(err);
1715
1834
  }
1716
1835
  });
1717
- cmd.command("create").description("Create webhook").requiredOption("--body <json>", "Create webhook JSON payload").action(async (opts) => {
1836
+ cmd.command("create").description("Create webhook").requiredOption("--body <json>", "Create webhook JSON payload").option("--dry-run", "Preview without executing").action(async (opts) => {
1718
1837
  try {
1838
+ const payload = parseRequiredJSON(opts.body, "--body");
1839
+ if (opts.dryRun) {
1840
+ if (isJSONMode()) printJSON({ dryRun: true, action: "create-webhook", payload });
1841
+ else {
1842
+ console.log(` ${dim("Dry run:")} Would create webhook`);
1843
+ printSyntaxJSON(payload);
1844
+ }
1845
+ return;
1846
+ }
1719
1847
  const token = resolveWebhookApiKey(cmd.opts());
1720
1848
  const result = await withSpinner(
1721
1849
  "Creating webhook\u2026",
1722
1850
  "Webhook created",
1723
1851
  () => callNotify(token, "/create-webhook", {
1724
1852
  method: "POST",
1725
- body: parseRequiredJSON(opts.body, "--body")
1853
+ body: payload
1726
1854
  })
1727
1855
  );
1728
1856
  if (isJSONMode()) printJSON(result);
@@ -1731,15 +1859,24 @@ function registerWebhooks(program2) {
1731
1859
  exitWithError(err);
1732
1860
  }
1733
1861
  });
1734
- cmd.command("update").description("Update webhook").requiredOption("--body <json>", "Update webhook JSON payload").action(async (opts) => {
1862
+ cmd.command("update").description("Update webhook").requiredOption("--body <json>", "Update webhook JSON payload").option("--dry-run", "Preview without executing").action(async (opts) => {
1735
1863
  try {
1864
+ const payload = parseRequiredJSON(opts.body, "--body");
1865
+ if (opts.dryRun) {
1866
+ if (isJSONMode()) printJSON({ dryRun: true, action: "update-webhook", payload });
1867
+ else {
1868
+ console.log(` ${dim("Dry run:")} Would update webhook`);
1869
+ printSyntaxJSON(payload);
1870
+ }
1871
+ return;
1872
+ }
1736
1873
  const token = resolveWebhookApiKey(cmd.opts());
1737
1874
  const result = await withSpinner(
1738
1875
  "Updating webhook\u2026",
1739
1876
  "Webhook updated",
1740
1877
  () => callNotify(token, "/update-webhook", {
1741
1878
  method: "PUT",
1742
- body: parseRequiredJSON(opts.body, "--body")
1879
+ body: payload
1743
1880
  })
1744
1881
  );
1745
1882
  if (isJSONMode()) printJSON(result);
@@ -1748,8 +1885,13 @@ function registerWebhooks(program2) {
1748
1885
  exitWithError(err);
1749
1886
  }
1750
1887
  });
1751
- cmd.command("delete <webhookId>").description("Delete webhook").option("-y, --yes", "Skip confirmation prompt").action(async (webhookId, opts) => {
1888
+ cmd.command("delete <webhookId>").description("Delete webhook").option("--dry-run", "Preview without executing").option("-y, --yes", "Skip confirmation prompt").action(async (webhookId, opts) => {
1752
1889
  try {
1890
+ if (opts.dryRun) {
1891
+ if (isJSONMode()) printJSON({ dryRun: true, action: "delete-webhook", payload: { webhookId } });
1892
+ else console.log(` ${dim("Dry run:")} Would delete webhook ${webhookId}`);
1893
+ return;
1894
+ }
1753
1895
  if (!opts.yes && isInteractiveAllowed(program2)) {
1754
1896
  const proceed = await promptConfirm({
1755
1897
  message: `Delete webhook ${webhookId}?`,
@@ -2413,8 +2555,12 @@ function formatAsSystemPrompt(payload) {
2413
2555
  return lines.join("\n");
2414
2556
  }
2415
2557
  function registerAgentPrompt(program2) {
2416
- program2.command("agent-prompt").description("Emit complete agent/automation usage instructions").action(() => {
2558
+ program2.command("agent-prompt").description("Emit complete agent/automation usage instructions").option("--commands <list>", "Filter to specific commands in JSON output (requires --json). Comma-separated (e.g. balance,tokens,gas)").action((opts) => {
2417
2559
  const payload = buildAgentPrompt(program2);
2560
+ if (opts.commands) {
2561
+ const filter = new Set(opts.commands.split(",").map((s) => s.trim().toLowerCase()));
2562
+ payload.commands = payload.commands.filter((cmd) => filter.has(cmd.name.toLowerCase()));
2563
+ }
2418
2564
  printHuman(formatAsSystemPrompt(payload), payload);
2419
2565
  });
2420
2566
  }
@@ -2526,10 +2672,10 @@ function resetUpdateNoticeState() {
2526
2672
  }
2527
2673
  program.name("alchemy").description(
2528
2674
  "The Alchemy CLI lets you query blockchain data, call JSON-RPC methods, and manage your Alchemy configuration."
2529
- ).version("0.3.1", "-v, --version", "display CLI version").option("--api-key <key>", "Alchemy API key (env: ALCHEMY_API_KEY)").option("--access-key <key>", "Alchemy access key (env: ALCHEMY_ACCESS_KEY)").option(
2675
+ ).version("0.4.0", "-v, --version", "display CLI version").option("--api-key <key>", "Alchemy API key (env: ALCHEMY_API_KEY)").option("--access-key <key>", "Alchemy access key (env: ALCHEMY_ACCESS_KEY)").option(
2530
2676
  "-n, --network <network>",
2531
2677
  "Target network (default: eth-mainnet) (env: ALCHEMY_NETWORK)"
2532
- ).option("--x402", "Use x402 wallet-based gateway auth").option("--wallet-key-file <path>", "Path to wallet private key file for x402").option("--json", "Force JSON output (auto-enabled when piped)").option("-q, --quiet", "Suppress non-essential output").option("--verbose", "Enable verbose output").option("--no-color", "Disable color output").option("--reveal", "Show secrets in plain text (TTY only)").option("--timeout <ms>", "Request timeout in milliseconds", parseInt).option("--debug", "Enable debug diagnostics").option("--no-interactive", "Disable REPL and prompt-driven interactions").addHelpCommand(false).allowExcessArguments(true).exitOverride((err) => {
2678
+ ).option("--x402", "Use x402 wallet-based gateway auth").option("--wallet-key-file <path>", "Path to wallet private key file for x402").option("--json", "Force JSON output (auto-enabled when piped)").option("-q, --quiet", "Suppress non-essential output").option("--verbose", "Enable verbose output").option("--no-color", "Disable color output").option("--reveal", "Show secrets in plain text").option("--timeout <ms>", "Request timeout in milliseconds (default: none)", parseInt).option("--debug", "Enable debug diagnostics").option("--no-interactive", "Disable REPL and prompt-driven interactions").addHelpCommand(false).allowExcessArguments(true).exitOverride((err) => {
2533
2679
  if (err.code === "commander.help" || err.code === "commander.helpDisplayed" || err.code === "commander.version") {
2534
2680
  process.exit(0);
2535
2681
  }
@@ -2706,7 +2852,7 @@ ${styledLine}`;
2706
2852
  if (isInteractiveAllowed(program)) {
2707
2853
  let latestForInteractiveStartup = null;
2708
2854
  if (shouldRunOnboarding(program, cfg)) {
2709
- const { runOnboarding } = await import("./onboarding-XNAWN5BR.js");
2855
+ const { runOnboarding } = await import("./onboarding-WQ2TWDM3.js");
2710
2856
  const latest = getAvailableUpdateOnce();
2711
2857
  const completed = await runOnboarding(program, latest);
2712
2858
  updateShownDuringInteractiveStartup = Boolean(latest);
@@ -2718,7 +2864,7 @@ ${styledLine}`;
2718
2864
  latestForInteractiveStartup = getAvailableUpdateOnce();
2719
2865
  updateShownDuringInteractiveStartup = Boolean(latestForInteractiveStartup);
2720
2866
  }
2721
- const { startREPL } = await import("./interactive-CLPT5QDZ.js");
2867
+ const { startREPL } = await import("./interactive-NASSNJHQ.js");
2722
2868
  program.exitOverride();
2723
2869
  program.configureOutput({
2724
2870
  writeErr: () => {
@@ -3,7 +3,7 @@ if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
3
  import {
4
4
  getRPCNetworkIds,
5
5
  getSetupMethod
6
- } from "./chunk-QDDJ3OYO.js";
6
+ } from "./chunk-Z3LXQFIY.js";
7
7
  import {
8
8
  bgRgb,
9
9
  bold,
@@ -19,7 +19,7 @@ import {
19
19
  rgb,
20
20
  setBrandedHelpSuppressed,
21
21
  setReplMode
22
- } from "./chunk-6XTLILDF.js";
22
+ } from "./chunk-TH75DFAY.js";
23
23
 
24
24
  // src/commands/interactive.ts
25
25
  import * as readline from "readline";
@@ -5,7 +5,7 @@ import {
5
5
  generateAndPersistWallet,
6
6
  importAndPersistWallet,
7
7
  selectOrCreateApp
8
- } from "./chunk-J6RZM4CJ.js";
8
+ } from "./chunk-PIWNNNMZ.js";
9
9
  import {
10
10
  bold,
11
11
  brand,
@@ -19,7 +19,7 @@ import {
19
19
  promptSelect,
20
20
  promptText,
21
21
  save
22
- } from "./chunk-6XTLILDF.js";
22
+ } from "./chunk-TH75DFAY.js";
23
23
 
24
24
  // src/commands/onboarding.ts
25
25
  function printNextSteps(method) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alchemy/cli",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Alchemy CLI — interact with blockchain data",
5
5
  "type": "module",
6
6
  "bin": {