@alchemy/cli 0.3.1 → 0.5.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.
package/dist/index.js CHANGED
@@ -1,37 +1,66 @@
1
1
  #!/usr/bin/env node
2
2
  if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
3
  import {
4
- adminClientFromFlags,
5
- clientFromFlags,
6
- fetchWithTimeout,
4
+ registerAuth
5
+ } from "./chunk-LYUW7O6X.js";
6
+ import "./chunk-IGD4NIK7.js";
7
+ import {
7
8
  readStdinArg,
9
+ readStdinLines,
8
10
  registerConfig,
9
11
  registerWallet,
10
- resolveAPIKey,
11
12
  resolveAddress,
12
- resolveAppId,
13
- resolveConfiguredNetworkSlugs,
14
- resolveNetwork,
15
13
  splitCommaList,
16
14
  validateAddress,
17
15
  validateTxHash
18
- } from "./chunk-J6RZM4CJ.js";
16
+ } from "./chunk-44OGGLN4.js";
19
17
  import {
20
18
  getRPCNetworks,
21
19
  getSetupStatus,
22
20
  isSetupComplete,
23
21
  nativeTokenSymbol,
24
22
  shouldRunOnboarding
25
- } from "./chunk-QDDJ3OYO.js";
23
+ } from "./chunk-5X6YRTPU.js";
26
24
  import {
27
- EXIT_CODES,
28
- ErrorCode,
25
+ getAvailableUpdate,
26
+ getUpdateStatus,
27
+ printUpdateNotice
28
+ } from "./chunk-DUQFOLLZ.js";
29
+ import {
30
+ adminClientFromFlags,
29
31
  bold,
30
32
  brand,
31
33
  brandedHelp,
32
- debug,
34
+ clientFromFlags,
33
35
  dim,
34
36
  emptyState,
37
+ etherscanTxURL,
38
+ failBadge,
39
+ green,
40
+ isInteractiveAllowed,
41
+ load,
42
+ maskIf,
43
+ printKeyValueBox,
44
+ printSyntaxJSON,
45
+ printTable,
46
+ promptConfirm,
47
+ promptSelect,
48
+ red,
49
+ resolveAPIKey,
50
+ resolveAppId,
51
+ resolveNetwork,
52
+ resolveX402Client,
53
+ save,
54
+ successBadge,
55
+ timeAgo,
56
+ weiToEth,
57
+ withSpinner
58
+ } from "./chunk-T2XSNZE3.js";
59
+ import {
60
+ EXIT_CODES,
61
+ ErrorCode,
62
+ debug,
63
+ errAppRequired,
35
64
  errAuthRequired,
36
65
  errInvalidAPIKey,
37
66
  errInvalidArgs,
@@ -40,37 +69,20 @@ import {
40
69
  errRateLimited,
41
70
  errSetupRequired,
42
71
  esc,
43
- etherscanTxURL,
44
72
  exitWithError,
45
- failBadge,
73
+ fetchWithTimeout,
46
74
  formatCommanderError,
47
- getAvailableUpdate,
48
- getUpdateStatus,
49
- green,
75
+ getBaseDomain,
50
76
  identity,
51
- isInteractiveAllowed,
52
77
  isJSONMode,
53
- load,
54
- maskIf,
55
78
  noColor,
56
79
  printHuman,
57
80
  printJSON,
58
- printKeyValueBox,
59
- printSyntaxJSON,
60
- printTable,
61
- printUpdateNotice,
62
- promptConfirm,
63
- promptSelect,
64
81
  quiet,
65
- red,
66
82
  setFlags,
67
83
  setNoColor,
68
- successBadge,
69
- timeAgo,
70
- verbose,
71
- weiToEth,
72
- withSpinner
73
- } from "./chunk-6XTLILDF.js";
84
+ verbose
85
+ } from "./chunk-56ZVYB4G.js";
74
86
 
75
87
  // src/index.ts
76
88
  import { Command, Help } from "commander";
@@ -108,6 +120,54 @@ Examples:
108
120
  }
109
121
 
110
122
  // src/commands/balance.ts
123
+ async function fetchBalance(program2, addressInput, blockParam) {
124
+ const client = clientFromFlags(program2);
125
+ const address = await resolveAddress(addressInput, client);
126
+ const result = await withSpinner(
127
+ "Fetching balance\u2026",
128
+ "Balance fetched",
129
+ () => client.call("eth_getBalance", [address, blockParam])
130
+ );
131
+ const wei = BigInt(result);
132
+ const network = resolveNetwork(program2);
133
+ const symbol = nativeTokenSymbol(network);
134
+ if (isJSONMode()) {
135
+ printJSON({
136
+ address,
137
+ wei: wei.toString(),
138
+ balance: weiToEth(wei),
139
+ symbol,
140
+ network
141
+ });
142
+ } else {
143
+ printKeyValueBox([
144
+ ["Address", address],
145
+ ["Balance", green(`${weiToEth(wei)} ${symbol}`)],
146
+ ["Network", network]
147
+ ]);
148
+ if (verbose) {
149
+ console.log("");
150
+ printJSON({
151
+ rpcMethod: "eth_getBalance",
152
+ rpcParams: [address, blockParam],
153
+ rpcResult: result
154
+ });
155
+ }
156
+ }
157
+ }
158
+ function resolveBlockParam(block) {
159
+ let blockParam = block ?? "latest";
160
+ if (blockParam !== "latest" && blockParam !== "earliest" && blockParam !== "pending") {
161
+ if (!blockParam.startsWith("0x")) {
162
+ const num = parseInt(blockParam, 10);
163
+ if (isNaN(num) || num < 0) {
164
+ throw errInvalidArgs("Block must be a number, hex, or tag (latest, earliest, pending).");
165
+ }
166
+ blockParam = `0x${num.toString(16)}`;
167
+ }
168
+ }
169
+ return blockParam;
170
+ }
111
171
  function registerBalance(program2) {
112
172
  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
173
  "after",
@@ -117,51 +177,21 @@ Examples:
117
177
  alchemy balance 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 -n polygon-mainnet
118
178
  echo 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 | alchemy balance
119
179
  alchemy balance 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --block 15537393
120
- alchemy balance vitalik.eth`
180
+ alchemy balance vitalik.eth
181
+ cat addresses.txt | alchemy balance`
121
182
  ).option("--block <block>", "Block number, hex, or tag (default: latest)").action(async (addressArg, opts) => {
122
183
  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
- }
184
+ const blockParam = resolveBlockParam(opts?.block);
185
+ if (addressArg) {
186
+ await fetchBalance(program2, addressArg, blockParam);
187
+ return;
135
188
  }
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
- });
189
+ const lines = await readStdinLines("address");
190
+ for (const line of lines) {
191
+ try {
192
+ await fetchBalance(program2, line, blockParam);
193
+ } catch (err) {
194
+ exitWithError(err);
165
195
  }
166
196
  }
167
197
  } catch (err) {
@@ -239,29 +269,28 @@ function formatGasSummary(gasUsed, gasLimit, options) {
239
269
 
240
270
  // src/commands/tx.ts
241
271
  function registerTx(program2) {
242
- program2.command("tx").argument("[hash]", "Transaction hash (0x...) or pipe via stdin").description("Get transaction details by hash").addHelpText(
272
+ 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
273
  "after",
244
274
  `
245
275
  Examples:
246
276
  alchemy tx 0xabc123...
247
- echo 0xabc123... | alchemy tx`
277
+ echo 0xabc123... | alchemy tx
278
+
279
+ Tip: use 'alchemy receipt <hash>' to get the transaction receipt (status, gas used, logs).`
248
280
  ).action(async (hashArg) => {
249
281
  try {
250
282
  const hash = hashArg ?? await readStdinArg("hash");
251
283
  validateTxHash(hash);
252
284
  const client = clientFromFlags(program2);
253
- const [tx, receipt] = await withSpinner("Fetching transaction\u2026", "Transaction fetched", async () => {
285
+ const tx = await withSpinner("Fetching transaction\u2026", "Transaction fetched", async () => {
254
286
  const t = await client.call("eth_getTransactionByHash", [
255
287
  hash
256
288
  ]);
257
289
  if (!t) throw errNotFound(`transaction ${hash}`);
258
- const r = await client.call("eth_getTransactionReceipt", [
259
- hash
260
- ]);
261
- return [t, r];
290
+ return t;
262
291
  });
263
292
  if (isJSONMode()) {
264
- printJSON({ transaction: tx, receipt });
293
+ printJSON(tx);
265
294
  return;
266
295
  }
267
296
  const network = resolveNetwork(program2);
@@ -277,20 +306,13 @@ Examples:
277
306
  const formatted = formatHexWithRaw(tx.blockNumber);
278
307
  pairs.push(["Block", formatted ?? String(tx.blockNumber)]);
279
308
  }
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
- }
309
+ if (tx.nonce) {
310
+ const formatted = formatHexWithRaw(tx.nonce);
311
+ pairs.push(["Nonce", formatted ?? String(tx.nonce)]);
312
+ }
313
+ if (tx.gasPrice) {
314
+ const formatted = formatGweiWithRaw(tx.gasPrice);
315
+ pairs.push(["Gas Price", formatted ?? String(tx.gasPrice)]);
294
316
  }
295
317
  const explorerURL = etherscanTxURL(hash, network);
296
318
  if (explorerURL) {
@@ -299,7 +321,7 @@ Examples:
299
321
  printKeyValueBox(pairs);
300
322
  if (verbose) {
301
323
  console.log("");
302
- printJSON({ transaction: tx, receipt });
324
+ printJSON(tx);
303
325
  }
304
326
  } catch (err) {
305
327
  exitWithError(err);
@@ -314,7 +336,9 @@ function registerReceipt(program2) {
314
336
  `
315
337
  Examples:
316
338
  alchemy receipt 0xabc123...
317
- echo 0xabc123... | alchemy receipt`
339
+ echo 0xabc123... | alchemy receipt
340
+
341
+ Tip: use 'alchemy tx <hash>' for transaction details (value, block, nonce). Receipt provides execution results (status, gas used, logs).`
318
342
  ).action(async (hashArg) => {
319
343
  try {
320
344
  const hash = hashArg ?? await readStdinArg("hash");
@@ -598,11 +622,13 @@ async function promptTokensPagination() {
598
622
  if (action === null) return "stop";
599
623
  return action;
600
624
  }
601
- function formatTokenRows(balances) {
602
- const nonZero = balances.filter(
625
+ function filterNonZero(balances) {
626
+ return balances.filter(
603
627
  (tb) => tb.tokenBalance !== "0x0" && tb.tokenBalance !== "0x0000000000000000000000000000000000000000000000000000000000000000"
604
628
  );
605
- return nonZero.map((tb) => {
629
+ }
630
+ function formatTokenRows(balances) {
631
+ return filterNonZero(balances).map((tb) => {
606
632
  let decimalBalance = dim("unparseable");
607
633
  try {
608
634
  decimalBalance = BigInt(tb.tokenBalance).toString();
@@ -611,15 +637,72 @@ function formatTokenRows(balances) {
611
637
  return [tb.contractAddress, decimalBalance, tb.tokenBalance];
612
638
  });
613
639
  }
640
+ function formatWithDecimals(rawBalance, decimals) {
641
+ if (decimals === null || decimals === 0) {
642
+ try {
643
+ return BigInt(rawBalance).toString();
644
+ } catch {
645
+ return rawBalance;
646
+ }
647
+ }
648
+ try {
649
+ const raw = BigInt(rawBalance);
650
+ const divisor = 10n ** BigInt(decimals);
651
+ const whole = raw / divisor;
652
+ const remainder = raw % divisor;
653
+ if (remainder === 0n) return whole.toString();
654
+ const fracStr = remainder.toString().padStart(decimals, "0").replace(/0+$/, "");
655
+ return `${whole}.${fracStr}`;
656
+ } catch {
657
+ return rawBalance;
658
+ }
659
+ }
660
+ async function resolveMetadata(client, balances) {
661
+ const nonZero = filterNonZero(balances);
662
+ const results = await Promise.all(
663
+ nonZero.map(async (tb) => {
664
+ try {
665
+ const meta = await client.call("alchemy_getTokenMetadata", [tb.contractAddress]);
666
+ return [tb.contractAddress, meta];
667
+ } catch {
668
+ return [tb.contractAddress, { name: null, symbol: null, decimals: null, logo: null }];
669
+ }
670
+ })
671
+ );
672
+ return new Map(results);
673
+ }
674
+ function formatResolvedRows(balances, metadata) {
675
+ return filterNonZero(balances).map((tb) => {
676
+ const meta = metadata.get(tb.contractAddress);
677
+ const symbol = meta?.symbol ?? "???";
678
+ const formatted = formatWithDecimals(tb.tokenBalance, meta?.decimals ?? null);
679
+ return [tb.contractAddress, symbol, `${formatted} ${symbol}`];
680
+ });
681
+ }
682
+ function formatResolvedJSON(balances, metadata) {
683
+ return filterNonZero(balances).map((tb) => {
684
+ const meta = metadata.get(tb.contractAddress);
685
+ return {
686
+ contractAddress: tb.contractAddress,
687
+ tokenBalance: tb.tokenBalance,
688
+ ...meta?.symbol && { symbol: meta.symbol },
689
+ ...meta?.name && { name: meta.name },
690
+ ...meta?.decimals !== null && meta?.decimals !== void 0 && { decimals: meta.decimals },
691
+ ...meta?.decimals !== null && meta?.decimals !== void 0 && {
692
+ formattedBalance: formatWithDecimals(tb.tokenBalance, meta.decimals)
693
+ }
694
+ };
695
+ });
696
+ }
614
697
  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(
698
+ const cmd = program2.command("tokens").description("Token API wrappers");
699
+ 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
700
  "after",
617
701
  `
618
702
  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`
703
+ alchemy tokens balances 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
704
+ alchemy tokens balances 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --metadata
705
+ echo 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 | alchemy tokens balances`
623
706
  ).action(async (addressArg, opts) => {
624
707
  try {
625
708
  const addressInput = addressArg ?? await readStdinArg("address");
@@ -634,24 +717,50 @@ Examples:
634
717
  "Token balances fetched",
635
718
  () => client.call("alchemy_getTokenBalances", params)
636
719
  );
637
- if (isJSONMode()) {
638
- printJSON(result);
720
+ const nonZero = filterNonZero(result.tokenBalances);
721
+ if (nonZero.length === 0) {
722
+ if (isJSONMode()) {
723
+ printJSON(result);
724
+ } else {
725
+ emptyState("No token balances found.");
726
+ }
639
727
  return;
640
728
  }
641
- const rows = formatTokenRows(result.tokenBalances);
642
- if (rows.length === 0) {
643
- emptyState("No token balances found.");
729
+ const metadata = opts.metadata ? await withSpinner(
730
+ `Resolving metadata for ${nonZero.length} tokens\u2026`,
731
+ "Metadata resolved",
732
+ () => resolveMetadata(client, result.tokenBalances)
733
+ ) : null;
734
+ if (isJSONMode()) {
735
+ if (metadata) {
736
+ printJSON({
737
+ address: result.address,
738
+ tokenBalances: formatResolvedJSON(result.tokenBalances, metadata),
739
+ ...result.pageKey && { pageKey: result.pageKey }
740
+ });
741
+ } else {
742
+ printJSON(result);
743
+ }
644
744
  return;
645
745
  }
646
- let totalShown = rows.length;
746
+ let totalShown = nonZero.length;
647
747
  printKeyValueBox([
648
748
  ["Address", address],
649
749
  ["Network", client.network],
650
750
  ["Tokens", String(totalShown)]
651
751
  ]);
652
- printTable(["Contract", "Balance (base units)", "Raw (hex)"], rows);
752
+ if (metadata) {
753
+ const rows = formatResolvedRows(result.tokenBalances, metadata);
754
+ printTable(["Contract", "Symbol", "Balance"], rows);
755
+ } else {
756
+ const rows = formatTokenRows(result.tokenBalances);
757
+ printTable(["Contract", "Balance (base units)", "Raw (hex)"], rows);
758
+ }
653
759
  console.log(`
654
760
  ${dim(`${totalShown} tokens (zero balances hidden).`)}`);
761
+ if (!metadata) {
762
+ console.log(` ${dim("Tip: use --metadata to fetch token symbols, decimals, and show formatted balances.")}`);
763
+ }
655
764
  if (verbose) {
656
765
  console.log("");
657
766
  printJSON(result);
@@ -674,10 +783,21 @@ Examples:
674
783
  printJSON(nextResult);
675
784
  return;
676
785
  }
677
- const nextRows = formatTokenRows(nextResult.tokenBalances);
678
- totalShown += nextRows.length;
679
- if (nextRows.length > 0) {
680
- printTable(["Contract", "Balance (base units)", "Raw (hex)"], nextRows);
786
+ const nextNonZero = filterNonZero(nextResult.tokenBalances);
787
+ totalShown += nextNonZero.length;
788
+ if (nextNonZero.length > 0) {
789
+ if (metadata) {
790
+ const nextMeta = await withSpinner(
791
+ `Resolving metadata for ${nextNonZero.length} tokens\u2026`,
792
+ "Metadata resolved",
793
+ () => resolveMetadata(client, nextResult.tokenBalances)
794
+ );
795
+ const rows = formatResolvedRows(nextResult.tokenBalances, nextMeta);
796
+ printTable(["Contract", "Symbol", "Balance"], rows);
797
+ } else {
798
+ const rows = formatTokenRows(nextResult.tokenBalances);
799
+ printTable(["Contract", "Balance (base units)", "Raw (hex)"], rows);
800
+ }
681
801
  }
682
802
  console.log(`
683
803
  ${dim(`${totalShown} tokens total (zero balances hidden).`)}`);
@@ -691,7 +811,12 @@ Examples:
691
811
  exitWithError(err);
692
812
  }
693
813
  });
694
- cmd.command("metadata <contract>").description("Get ERC-20 token metadata").action(async (contract) => {
814
+ cmd.command("metadata <contract>").description("Get ERC-20 token metadata (name, symbol, decimals, logo)").addHelpText(
815
+ "after",
816
+ `
817
+ Examples:
818
+ alchemy tokens metadata 0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eB48`
819
+ ).action(async (contract) => {
695
820
  try {
696
821
  validateAddress(contract);
697
822
  const client = clientFromFlags(program2);
@@ -715,11 +840,11 @@ Examples:
715
840
  const result = await withSpinner(
716
841
  "Fetching token allowance\u2026",
717
842
  "Token allowance fetched",
718
- () => client.call("alchemy_getTokenAllowance", [
719
- opts.owner,
720
- opts.spender,
721
- opts.contract
722
- ])
843
+ () => client.call("alchemy_getTokenAllowance", [{
844
+ owner: opts.owner,
845
+ spender: opts.spender,
846
+ contract: opts.contract
847
+ }])
723
848
  );
724
849
  if (isJSONMode()) printJSON(result);
725
850
  else printSyntaxJSON(result);
@@ -732,34 +857,22 @@ Examples:
732
857
  // src/commands/network.ts
733
858
  function registerNetwork(program2) {
734
859
  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) => {
860
+ 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
861
  try {
743
- const supported = getRPCNetworks();
862
+ let display = getRPCNetworks();
744
863
  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;
864
+ if (opts.mainnetOnly) {
865
+ display = display.filter((n) => !n.isTestnet);
866
+ } else if (opts.testnetOnly) {
867
+ display = display.filter((n) => n.isTestnet);
868
+ }
869
+ if (opts.search) {
870
+ const term = opts.search.toLowerCase();
871
+ display = display.filter(
872
+ (n) => n.id.toLowerCase().includes(term) || n.name.toLowerCase().includes(term) || n.family.toLowerCase().includes(term)
873
+ );
874
+ }
753
875
  if (isJSONMode()) {
754
- if (configured) {
755
- printJSON({
756
- mode: "configured",
757
- appId,
758
- configuredNetworkIds: configured,
759
- networks: display
760
- });
761
- return;
762
- }
763
876
  printJSON(display);
764
877
  return;
765
878
  }
@@ -771,23 +884,18 @@ function registerNetwork(program2) {
771
884
  return [idCell, nameCell, network.family, testnetCell];
772
885
  });
773
886
  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
887
  console.log(`
781
888
  Current: ${green(current)}`);
782
889
  console.log(
783
890
  ` ${dim("Need Admin API chain identifiers (e.g. ETH_MAINNET)? See: apps chains")}`
784
891
  );
892
+ console.log(
893
+ ` ${dim("Need configured networks for an app? See: apps networks")}`
894
+ );
785
895
  if (verbose) {
786
896
  console.log("");
787
897
  printJSON({
788
- mode: configured ? "configured" : "all",
789
- appId: appId ?? null,
790
- configuredNetworkIds: configured ?? null,
898
+ mode: "all",
791
899
  networks: display,
792
900
  currentNetwork: current
793
901
  });
@@ -1182,6 +1290,95 @@ function registerApps(program2) {
1182
1290
  exitWithError(err);
1183
1291
  }
1184
1292
  });
1293
+ 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) => {
1294
+ try {
1295
+ const admin = adminClientFromFlags(program2);
1296
+ const appId = opts.appId || resolveAppId(program2);
1297
+ if (!appId) throw errAppRequired();
1298
+ const app = await withSpinner(
1299
+ "Fetching app\u2026",
1300
+ "App fetched",
1301
+ () => admin.getApp(appId)
1302
+ );
1303
+ const slugs = app.chainNetworks.map((n) => {
1304
+ const match = n.rpcUrl?.match(/^https:\/\/([^.]+)\.g\.alchemy\.com(?:\/|$)/);
1305
+ return match ? match[1] : null;
1306
+ }).filter((s) => Boolean(s));
1307
+ const uniqueSlugs = Array.from(new Set(slugs)).sort();
1308
+ if (isJSONMode()) {
1309
+ printJSON({ appId: app.id, appName: app.name, networks: uniqueSlugs });
1310
+ return;
1311
+ }
1312
+ if (uniqueSlugs.length === 0) {
1313
+ emptyState(`No RPC networks configured for ${app.name}.`);
1314
+ return;
1315
+ }
1316
+ const rows = uniqueSlugs.map((slug) => [slug]);
1317
+ printTable(["Network ID"], rows);
1318
+ console.log(`
1319
+ ${dim(`${uniqueSlugs.length} networks configured for ${app.name} (${app.id})`)}`);
1320
+ } catch (err) {
1321
+ exitWithError(err);
1322
+ }
1323
+ });
1324
+ cmd.command("select [id]").description("Select an app to use as the default").action(async (id) => {
1325
+ try {
1326
+ const admin = adminClientFromFlags(program2);
1327
+ let selected;
1328
+ if (id) {
1329
+ selected = await withSpinner(
1330
+ "Fetching app\u2026",
1331
+ "App fetched",
1332
+ () => admin.getApp(id)
1333
+ );
1334
+ } else {
1335
+ const result = await withSpinner(
1336
+ "Fetching apps\u2026",
1337
+ "Apps fetched",
1338
+ () => admin.listAllApps()
1339
+ );
1340
+ if (result.apps.length === 0) {
1341
+ emptyState("No apps found.");
1342
+ return;
1343
+ }
1344
+ if (isJSONMode()) {
1345
+ printJSON({ apps: result.apps.map(maskAppSecrets) });
1346
+ return;
1347
+ }
1348
+ const appId = await promptSelect({
1349
+ message: "Select an app",
1350
+ options: result.apps.map((app) => ({
1351
+ value: app.id,
1352
+ label: app.name,
1353
+ hint: `${app.chainNetworks.length} networks`
1354
+ })),
1355
+ cancelMessage: "Cancelled app selection."
1356
+ });
1357
+ if (!appId) return;
1358
+ const found = result.apps.find((a) => a.id === appId);
1359
+ if (!found) return;
1360
+ selected = found;
1361
+ }
1362
+ const cfg = load();
1363
+ save({
1364
+ ...cfg,
1365
+ app: {
1366
+ id: selected.id,
1367
+ name: selected.name,
1368
+ apiKey: selected.apiKey,
1369
+ webhookApiKey: selected.webhookApiKey
1370
+ }
1371
+ });
1372
+ if (isJSONMode()) {
1373
+ printJSON(maskAppSecrets(selected));
1374
+ return;
1375
+ }
1376
+ console.log(` ${green("\u2713")} Selected app: ${selected.name}`);
1377
+ console.log(` ${dim("Saved to config")}`);
1378
+ } catch (err) {
1379
+ exitWithError(err);
1380
+ }
1381
+ });
1185
1382
  cmd.command("chains").description("List Admin API chain identifiers for app configuration (e.g. ETH_MAINNET)").action(async () => {
1186
1383
  try {
1187
1384
  const admin = adminClientFromFlags(program2);
@@ -1471,13 +1668,13 @@ async function requestJSON(url, options) {
1471
1668
  }
1472
1669
  async function callApiData(apiKey, path, options = {}) {
1473
1670
  if (!apiKey) throw errAuthRequired();
1474
- const base = new URL(`https://api.g.alchemy.com/data/v1/${apiKey}/`);
1671
+ const base = new URL(`https://api.g.${getBaseDomain()}/data/v1/${apiKey}/`);
1475
1672
  const url = withQuery(new URL(path.replace(/^\//, ""), base), options.query);
1476
1673
  return requestJSON(url, { ...options, path });
1477
1674
  }
1478
1675
  async function callApiPrices(apiKey, path, options = {}) {
1479
1676
  if (!apiKey) throw errAuthRequired();
1480
- const base = new URL(`https://api.g.alchemy.com/prices/v1/${apiKey}/`);
1677
+ const base = new URL(`https://api.g.${getBaseDomain()}/prices/v1/${apiKey}/`);
1481
1678
  const url = withQuery(new URL(path.replace(/^\//, ""), base), options.query);
1482
1679
  return requestJSON(url, { ...options, path });
1483
1680
  }
@@ -1487,7 +1684,7 @@ async function callNotify(token, path, options = {}) {
1487
1684
  "Webhook API key required. Set ALCHEMY_WEBHOOK_API_KEY (or ALCHEMY_NOTIFY_AUTH_TOKEN) or pass --webhook-api-key."
1488
1685
  );
1489
1686
  }
1490
- const base = new URL("https://dashboard.alchemy.com/api/");
1687
+ const base = new URL(`https://dashboard.${getBaseDomain()}/api/`);
1491
1688
  const url = withQuery(new URL(path.replace(/^\//, ""), base), options.query);
1492
1689
  return requestJSON(url, {
1493
1690
  ...options,
@@ -1504,14 +1701,14 @@ function registerPrices(program2) {
1504
1701
  const cmd = program2.command("prices").description("Prices API wrappers");
1505
1702
  cmd.command("symbol <symbols>").description("Get spot prices by symbol (comma-separated)").action(async (symbols) => {
1506
1703
  try {
1507
- const apiKey = resolveAPIKey(program2);
1508
1704
  const values = splitCommaList(symbols);
1509
1705
  const query = new URLSearchParams();
1510
1706
  for (const symbol of values) query.append("symbols", symbol);
1707
+ const x402 = resolveX402Client(program2);
1511
1708
  const result = await withSpinner(
1512
1709
  "Fetching prices\u2026",
1513
1710
  "Prices fetched",
1514
- () => callApiPrices(apiKey, `/tokens/by-symbol?${query.toString()}`)
1711
+ () => x402 ? x402.callRest(`prices/v1/tokens/by-symbol?${query.toString()}`) : callApiPrices(resolveAPIKey(program2), `/tokens/by-symbol?${query.toString()}`)
1515
1712
  );
1516
1713
  if (isJSONMode()) printJSON(result);
1517
1714
  else printSyntaxJSON(result);
@@ -1521,12 +1718,12 @@ function registerPrices(program2) {
1521
1718
  });
1522
1719
  cmd.command("address").description("Get spot prices by token addresses").requiredOption("--addresses <json>", "JSON array of {network,address}").action(async (opts) => {
1523
1720
  try {
1524
- const apiKey = resolveAPIKey(program2);
1525
1721
  const body = { addresses: JSON.parse(opts.addresses) };
1722
+ const x402 = resolveX402Client(program2);
1526
1723
  const result = await withSpinner(
1527
1724
  "Fetching prices\u2026",
1528
1725
  "Prices fetched",
1529
- () => callApiPrices(apiKey, "/tokens/by-address", { method: "POST", body })
1726
+ () => x402 ? x402.callRest("prices/v1/tokens/by-address", { method: "POST", body }) : callApiPrices(resolveAPIKey(program2), "/tokens/by-address", { method: "POST", body })
1530
1727
  );
1531
1728
  if (isJSONMode()) printJSON(result);
1532
1729
  else printSyntaxJSON(result);
@@ -1534,14 +1731,20 @@ function registerPrices(program2) {
1534
1731
  exitWithError(err);
1535
1732
  }
1536
1733
  });
1537
- cmd.command("historical").description("Get historical prices").requiredOption("--body <json>", "JSON request payload").action(async (opts) => {
1734
+ cmd.command("historical").description("Get historical prices").requiredOption("--body <json>", "JSON request payload").addHelpText(
1735
+ "after",
1736
+ `
1737
+ Examples:
1738
+ alchemy prices historical --body '{"symbol":"ETH","startTime":"2024-01-01T00:00:00Z","endTime":"2024-01-02T00:00:00Z","interval":"1h"}'
1739
+ alchemy prices historical --body '{"address":"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48","network":"eth-mainnet","startTime":"2024-06-01","endTime":"2024-06-07","interval":"1d"}'`
1740
+ ).action(async (opts) => {
1538
1741
  try {
1539
- const apiKey = resolveAPIKey(program2);
1540
1742
  const payload = JSON.parse(opts.body);
1743
+ const x402 = resolveX402Client(program2);
1541
1744
  const result = await withSpinner(
1542
1745
  "Fetching historical prices\u2026",
1543
1746
  "Historical prices fetched",
1544
- () => callApiPrices(apiKey, "/tokens/historical", { method: "POST", body: payload })
1747
+ () => x402 ? x402.callRest("prices/v1/tokens/historical", { method: "POST", body: payload }) : callApiPrices(resolveAPIKey(program2), "/tokens/historical", { method: "POST", body: payload })
1545
1748
  );
1546
1749
  if (isJSONMode()) printJSON(result);
1547
1750
  else printSyntaxJSON(result);
@@ -1552,20 +1755,20 @@ function registerPrices(program2) {
1552
1755
  }
1553
1756
 
1554
1757
  // src/commands/portfolio.ts
1555
- async function runDataCall(apiKey, title, path, body) {
1758
+ async function runDataCall(program2, title, path, body) {
1759
+ const x402 = resolveX402Client(program2);
1556
1760
  return withSpinner(
1557
1761
  `Fetching ${title}\u2026`,
1558
1762
  `${title} fetched`,
1559
- () => callApiData(apiKey, path, { method: "POST", body })
1763
+ () => x402 ? x402.callRest(`data/v1${path}`, { method: "POST", body }) : callApiData(resolveAPIKey(program2), path, { method: "POST", body })
1560
1764
  );
1561
1765
  }
1562
1766
  function registerPortfolio(program2) {
1563
1767
  const cmd = program2.command("portfolio").description("Portfolio API wrappers");
1564
1768
  cmd.command("tokens").description("Get token portfolio by address/network pairs").requiredOption("--body <json>", "JSON body for /assets/tokens/by-address").action(async (opts) => {
1565
1769
  try {
1566
- const apiKey = resolveAPIKey(program2);
1567
1770
  const result = await runDataCall(
1568
- apiKey,
1771
+ program2,
1569
1772
  "token portfolio",
1570
1773
  "/assets/tokens/by-address",
1571
1774
  JSON.parse(opts.body)
@@ -1578,9 +1781,8 @@ function registerPortfolio(program2) {
1578
1781
  });
1579
1782
  cmd.command("token-balances").description("Get token balances by address/network pairs").requiredOption("--body <json>", "JSON body for /assets/tokens/balances/by-address").action(async (opts) => {
1580
1783
  try {
1581
- const apiKey = resolveAPIKey(program2);
1582
1784
  const result = await runDataCall(
1583
- apiKey,
1785
+ program2,
1584
1786
  "token balances",
1585
1787
  "/assets/tokens/balances/by-address",
1586
1788
  JSON.parse(opts.body)
@@ -1593,9 +1795,8 @@ function registerPortfolio(program2) {
1593
1795
  });
1594
1796
  cmd.command("nfts").description("Get NFT portfolio by address/network pairs").requiredOption("--body <json>", "JSON body for /assets/nfts/by-address").action(async (opts) => {
1595
1797
  try {
1596
- const apiKey = resolveAPIKey(program2);
1597
1798
  const result = await runDataCall(
1598
- apiKey,
1799
+ program2,
1599
1800
  "NFT portfolio",
1600
1801
  "/assets/nfts/by-address",
1601
1802
  JSON.parse(opts.body)
@@ -1608,9 +1809,8 @@ function registerPortfolio(program2) {
1608
1809
  });
1609
1810
  cmd.command("nft-contracts").description("Get NFT contracts by address/network pairs").requiredOption("--body <json>", "JSON body for /assets/nfts/contracts/by-address").action(async (opts) => {
1610
1811
  try {
1611
- const apiKey = resolveAPIKey(program2);
1612
1812
  const result = await runDataCall(
1613
- apiKey,
1813
+ program2,
1614
1814
  "NFT contracts",
1615
1815
  "/assets/nfts/contracts/by-address",
1616
1816
  JSON.parse(opts.body)
@@ -1621,21 +1821,6 @@ function registerPortfolio(program2) {
1621
1821
  exitWithError(err);
1622
1822
  }
1623
1823
  });
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
1824
  }
1640
1825
 
1641
1826
  // src/commands/simulate.ts
@@ -1714,15 +1899,24 @@ function registerWebhooks(program2) {
1714
1899
  exitWithError(err);
1715
1900
  }
1716
1901
  });
1717
- cmd.command("create").description("Create webhook").requiredOption("--body <json>", "Create webhook JSON payload").action(async (opts) => {
1902
+ cmd.command("create").description("Create webhook").requiredOption("--body <json>", "Create webhook JSON payload").option("--dry-run", "Preview without executing").action(async (opts) => {
1718
1903
  try {
1904
+ const payload = parseRequiredJSON(opts.body, "--body");
1905
+ if (opts.dryRun) {
1906
+ if (isJSONMode()) printJSON({ dryRun: true, action: "create-webhook", payload });
1907
+ else {
1908
+ console.log(` ${dim("Dry run:")} Would create webhook`);
1909
+ printSyntaxJSON(payload);
1910
+ }
1911
+ return;
1912
+ }
1719
1913
  const token = resolveWebhookApiKey(cmd.opts());
1720
1914
  const result = await withSpinner(
1721
1915
  "Creating webhook\u2026",
1722
1916
  "Webhook created",
1723
1917
  () => callNotify(token, "/create-webhook", {
1724
1918
  method: "POST",
1725
- body: parseRequiredJSON(opts.body, "--body")
1919
+ body: payload
1726
1920
  })
1727
1921
  );
1728
1922
  if (isJSONMode()) printJSON(result);
@@ -1731,15 +1925,24 @@ function registerWebhooks(program2) {
1731
1925
  exitWithError(err);
1732
1926
  }
1733
1927
  });
1734
- cmd.command("update").description("Update webhook").requiredOption("--body <json>", "Update webhook JSON payload").action(async (opts) => {
1928
+ cmd.command("update").description("Update webhook").requiredOption("--body <json>", "Update webhook JSON payload").option("--dry-run", "Preview without executing").action(async (opts) => {
1735
1929
  try {
1930
+ const payload = parseRequiredJSON(opts.body, "--body");
1931
+ if (opts.dryRun) {
1932
+ if (isJSONMode()) printJSON({ dryRun: true, action: "update-webhook", payload });
1933
+ else {
1934
+ console.log(` ${dim("Dry run:")} Would update webhook`);
1935
+ printSyntaxJSON(payload);
1936
+ }
1937
+ return;
1938
+ }
1736
1939
  const token = resolveWebhookApiKey(cmd.opts());
1737
1940
  const result = await withSpinner(
1738
1941
  "Updating webhook\u2026",
1739
1942
  "Webhook updated",
1740
1943
  () => callNotify(token, "/update-webhook", {
1741
1944
  method: "PUT",
1742
- body: parseRequiredJSON(opts.body, "--body")
1945
+ body: payload
1743
1946
  })
1744
1947
  );
1745
1948
  if (isJSONMode()) printJSON(result);
@@ -1748,8 +1951,13 @@ function registerWebhooks(program2) {
1748
1951
  exitWithError(err);
1749
1952
  }
1750
1953
  });
1751
- cmd.command("delete <webhookId>").description("Delete webhook").option("-y, --yes", "Skip confirmation prompt").action(async (webhookId, opts) => {
1954
+ cmd.command("delete <webhookId>").description("Delete webhook").option("--dry-run", "Preview without executing").option("-y, --yes", "Skip confirmation prompt").action(async (webhookId, opts) => {
1752
1955
  try {
1956
+ if (opts.dryRun) {
1957
+ if (isJSONMode()) printJSON({ dryRun: true, action: "delete-webhook", payload: { webhookId } });
1958
+ else console.log(` ${dim("Dry run:")} Would delete webhook ${webhookId}`);
1959
+ return;
1960
+ }
1753
1961
  if (!opts.yes && isInteractiveAllowed(program2)) {
1754
1962
  const proceed = await promptConfirm({
1755
1963
  message: `Delete webhook ${webhookId}?`,
@@ -2413,8 +2621,12 @@ function formatAsSystemPrompt(payload) {
2413
2621
  return lines.join("\n");
2414
2622
  }
2415
2623
  function registerAgentPrompt(program2) {
2416
- program2.command("agent-prompt").description("Emit complete agent/automation usage instructions").action(() => {
2624
+ 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
2625
  const payload = buildAgentPrompt(program2);
2626
+ if (opts.commands) {
2627
+ const filter = new Set(opts.commands.split(",").map((s) => s.trim().toLowerCase()));
2628
+ payload.commands = payload.commands.filter((cmd) => filter.has(cmd.name.toLowerCase()));
2629
+ }
2418
2630
  printHuman(formatAsSystemPrompt(payload), payload);
2419
2631
  });
2420
2632
  }
@@ -2477,7 +2689,7 @@ var ROOT_COMMAND_PILLARS = [
2477
2689
  },
2478
2690
  {
2479
2691
  label: "Admin",
2480
- commands: ["apps", "config", "setup", "completions", "agent-prompt", "update-check", "version", "help"]
2692
+ commands: ["apps", "auth", "config", "setup", "completions", "agent-prompt", "update-check", "version", "help"]
2481
2693
  }
2482
2694
  ];
2483
2695
  function formatCommandSignature(sub) {
@@ -2526,10 +2738,10 @@ function resetUpdateNoticeState() {
2526
2738
  }
2527
2739
  program.name("alchemy").description(
2528
2740
  "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(
2741
+ ).version("0.5.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
2742
  "-n, --network <network>",
2531
2743
  "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) => {
2744
+ ).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
2745
  if (err.code === "commander.help" || err.code === "commander.helpDisplayed" || err.code === "commander.version") {
2534
2746
  process.exit(0);
2535
2747
  }
@@ -2706,7 +2918,7 @@ ${styledLine}`;
2706
2918
  if (isInteractiveAllowed(program)) {
2707
2919
  let latestForInteractiveStartup = null;
2708
2920
  if (shouldRunOnboarding(program, cfg)) {
2709
- const { runOnboarding } = await import("./onboarding-XNAWN5BR.js");
2921
+ const { runOnboarding } = await import("./onboarding-F5PZMFZU.js");
2710
2922
  const latest = getAvailableUpdateOnce();
2711
2923
  const completed = await runOnboarding(program, latest);
2712
2924
  updateShownDuringInteractiveStartup = Boolean(latest);
@@ -2718,7 +2930,7 @@ ${styledLine}`;
2718
2930
  latestForInteractiveStartup = getAvailableUpdateOnce();
2719
2931
  updateShownDuringInteractiveStartup = Boolean(latestForInteractiveStartup);
2720
2932
  }
2721
- const { startREPL } = await import("./interactive-CLPT5QDZ.js");
2933
+ const { startREPL } = await import("./interactive-K7XOS6U6.js");
2722
2934
  program.exitOverride();
2723
2935
  program.configureOutput({
2724
2936
  writeErr: () => {
@@ -2750,6 +2962,7 @@ registerGasManager(program);
2750
2962
  registerWebhooks(program);
2751
2963
  registerNetwork(program);
2752
2964
  registerApps(program);
2965
+ registerAuth(program);
2753
2966
  registerSetup(program);
2754
2967
  registerConfig(program);
2755
2968
  registerSolana(program);