@alchemy/cli 0.2.2 → 0.3.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
@@ -8,19 +8,21 @@ import {
8
8
  registerConfig,
9
9
  registerWallet,
10
10
  resolveAPIKey,
11
+ resolveAddress,
11
12
  resolveAppId,
12
13
  resolveConfiguredNetworkSlugs,
13
14
  resolveNetwork,
14
15
  splitCommaList,
15
16
  validateAddress,
16
17
  validateTxHash
17
- } from "./chunk-6KOJKH6N.js";
18
+ } from "./chunk-UPQTWEPP.js";
18
19
  import {
19
20
  getRPCNetworks,
20
21
  getSetupStatus,
21
22
  isSetupComplete,
23
+ nativeTokenSymbol,
22
24
  shouldRunOnboarding
23
- } from "./chunk-NA7MQB7X.js";
25
+ } from "./chunk-VYQ5V2ZR.js";
24
26
  import {
25
27
  EXIT_CODES,
26
28
  ErrorCode,
@@ -59,20 +61,21 @@ import {
59
61
  printUpdateNotice,
60
62
  promptSelect,
61
63
  quiet,
64
+ red,
62
65
  setFlags,
63
66
  successBadge,
64
67
  timeAgo,
65
68
  verbose,
66
69
  weiToEth,
67
70
  withSpinner
68
- } from "./chunk-2WI4JODY.js";
71
+ } from "./chunk-MF6DXNO7.js";
69
72
 
70
73
  // src/index.ts
71
74
  import { Command, Help } from "commander";
72
75
 
73
76
  // src/commands/rpc.ts
74
77
  function registerRPC(program2) {
75
- program2.command("rpc <method> [params...]").description("Make a raw JSON-RPC call").addHelpText(
78
+ program2.command("rpc").argument("<method>", "JSON-RPC method name (e.g. eth_blockNumber)").argument("[params...]", "Method parameters as JSON values").description("Make a raw JSON-RPC call").addHelpText(
76
79
  "after",
77
80
  `
78
81
  Examples:
@@ -104,36 +107,50 @@ Examples:
104
107
 
105
108
  // src/commands/balance.ts
106
109
  function registerBalance(program2) {
107
- program2.command("balance [address]").alias("bal").description("Get the ETH balance of an address").addHelpText(
110
+ 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(
108
111
  "after",
109
112
  `
110
113
  Examples:
111
114
  alchemy balance 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
112
115
  alchemy balance 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 -n polygon-mainnet
113
- echo 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 | alchemy balance`
114
- ).action(async (addressArg) => {
116
+ echo 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 | alchemy balance
117
+ alchemy balance 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --block 15537393
118
+ alchemy balance vitalik.eth`
119
+ ).option("--block <block>", "Block number, hex, or tag (default: latest)").action(async (addressArg, opts) => {
115
120
  try {
116
- const address = addressArg ?? await readStdinArg("address");
117
- validateAddress(address);
121
+ const addressInput = addressArg ?? await readStdinArg("address");
118
122
  const client = clientFromFlags(program2);
123
+ const address = await resolveAddress(addressInput, client);
124
+ let blockParam = opts?.block ?? "latest";
125
+ if (blockParam !== "latest" && blockParam !== "earliest" && blockParam !== "pending") {
126
+ if (!blockParam.startsWith("0x")) {
127
+ const num = parseInt(blockParam, 10);
128
+ if (isNaN(num) || num < 0) {
129
+ throw errInvalidArgs("Block must be a number, hex, or tag (latest, earliest, pending).");
130
+ }
131
+ blockParam = `0x${num.toString(16)}`;
132
+ }
133
+ }
119
134
  const result = await withSpinner(
120
135
  "Fetching balance\u2026",
121
136
  "Balance fetched",
122
- () => client.call("eth_getBalance", [address, "latest"])
137
+ () => client.call("eth_getBalance", [address, blockParam])
123
138
  );
124
139
  const wei = BigInt(result);
125
140
  const network = resolveNetwork(program2);
141
+ const symbol = nativeTokenSymbol(network);
126
142
  if (isJSONMode()) {
127
143
  printJSON({
128
144
  address,
129
145
  wei: wei.toString(),
130
- eth: weiToEth(wei),
146
+ balance: weiToEth(wei),
147
+ symbol,
131
148
  network
132
149
  });
133
150
  } else {
134
151
  printKeyValueBox([
135
152
  ["Address", address],
136
- ["Balance", green(weiToEth(wei) + " ETH")],
153
+ ["Balance", green(`${weiToEth(wei)} ${symbol}`)],
137
154
  ["Network", network]
138
155
  ]);
139
156
  if (verbose) {
@@ -151,9 +168,76 @@ Examples:
151
168
  });
152
169
  }
153
170
 
171
+ // src/lib/block-format.ts
172
+ function parseHexQuantity(value) {
173
+ if (typeof value !== "string" || !/^0x[0-9a-f]+$/i.test(value)) {
174
+ return void 0;
175
+ }
176
+ try {
177
+ return BigInt(value);
178
+ } catch {
179
+ return void 0;
180
+ }
181
+ }
182
+ function formatWithCommas(value) {
183
+ return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
184
+ }
185
+ function formatHexQuantity(value) {
186
+ const parsed = parseHexQuantity(value);
187
+ if (parsed === void 0) return void 0;
188
+ return formatWithCommas(parsed);
189
+ }
190
+ function formatBlockTimestamp(value) {
191
+ if (typeof value !== "string") return void 0;
192
+ const parsed = parseHexQuantity(value);
193
+ if (parsed === void 0) return void 0;
194
+ const millis = Number(parsed) * 1e3;
195
+ if (!Number.isFinite(millis)) return void 0;
196
+ const d = new Date(millis);
197
+ if (Number.isNaN(d.getTime())) return void 0;
198
+ const iso = d.toISOString().replace(".000Z", "Z");
199
+ return `${iso} ${dim("(" + timeAgo(value) + ")")}`;
200
+ }
201
+ function formatHexWithRaw(value) {
202
+ if (typeof value !== "string") return void 0;
203
+ const parsed = parseHexQuantity(value);
204
+ if (parsed === void 0) return void 0;
205
+ return `${formatWithCommas(parsed)} ${dim(`(${value})`)}`;
206
+ }
207
+ function formatWeiWithRaw(value, symbol = "ETH") {
208
+ if (typeof value !== "string") return void 0;
209
+ const parsed = parseHexQuantity(value);
210
+ if (parsed === void 0) return void 0;
211
+ return `${weiToEth(parsed)} ${symbol} ${dim(`(${value})`)}`;
212
+ }
213
+ function formatGwei(gwei) {
214
+ const fixed = gwei.toFixed(9);
215
+ return fixed.replace(/\.?0+$/, "") || "0";
216
+ }
217
+ function formatGweiWithRaw(value) {
218
+ if (typeof value !== "string") return void 0;
219
+ const parsed = parseHexQuantity(value);
220
+ if (parsed === void 0) return void 0;
221
+ const gwei = Number(parsed) / 1e9;
222
+ return `${formatGwei(gwei)} gwei ${dim(`(${value})`)}`;
223
+ }
224
+ function formatGasSummary(gasUsed, gasLimit, options) {
225
+ const used = parseHexQuantity(gasUsed);
226
+ const limit = parseHexQuantity(gasLimit);
227
+ if (used === void 0 || limit === void 0) return void 0;
228
+ const usedFormatted = formatWithCommas(used);
229
+ const limitFormatted = formatWithCommas(limit);
230
+ if (limit === 0n) return `${usedFormatted} / ${limitFormatted}`;
231
+ const bps = used * 10000n / limit;
232
+ const percent = Number(bps) / 100;
233
+ const percentText = `${percent.toFixed(2)}%`;
234
+ const percentDisplay = options?.colored ? dim(percentText) : percentText;
235
+ return `${usedFormatted} / ${limitFormatted} (${percentDisplay})`;
236
+ }
237
+
154
238
  // src/commands/tx.ts
155
239
  function registerTx(program2) {
156
- program2.command("tx [hash]").description("Get transaction details by hash").addHelpText(
240
+ program2.command("tx").argument("[hash]", "Transaction hash (0x...) or pipe via stdin").description("Get transaction details by hash").addHelpText(
157
241
  "after",
158
242
  `
159
243
  Examples:
@@ -178,23 +262,34 @@ Examples:
178
262
  printJSON({ transaction: tx, receipt });
179
263
  return;
180
264
  }
265
+ const network = resolveNetwork(program2);
266
+ const symbol = nativeTokenSymbol(network);
181
267
  const pairs = [["Hash", hash]];
182
268
  if (tx.from) pairs.push(["From", String(tx.from)]);
183
269
  if (tx.to) pairs.push(["To", String(tx.to)]);
184
270
  if (tx.value) {
185
- const wei = BigInt(tx.value);
186
- pairs.push(["Value", green(weiToEth(wei) + " ETH")]);
271
+ const formatted = formatWeiWithRaw(tx.value, symbol);
272
+ pairs.push(["Value", formatted ? green(formatted) : String(tx.value)]);
273
+ }
274
+ if (tx.blockNumber) {
275
+ const formatted = formatHexWithRaw(tx.blockNumber);
276
+ pairs.push(["Block", formatted ?? String(tx.blockNumber)]);
187
277
  }
188
- if (tx.blockNumber) pairs.push(["Block", String(tx.blockNumber)]);
189
278
  if (receipt) {
190
279
  if (receipt.status === "0x1") {
191
280
  pairs.push(["Status", `${successBadge()} Success`]);
192
281
  } else if (receipt.status) {
193
282
  pairs.push(["Status", `${failBadge()} Failed`]);
194
283
  }
195
- if (receipt.gasUsed) pairs.push(["Gas Used", String(receipt.gasUsed)]);
284
+ if (receipt.gasUsed) {
285
+ const formatted = formatHexWithRaw(receipt.gasUsed);
286
+ pairs.push(["Gas Used", formatted ?? String(receipt.gasUsed)]);
287
+ }
288
+ if (receipt.effectiveGasPrice) {
289
+ const formatted = formatGweiWithRaw(receipt.effectiveGasPrice);
290
+ pairs.push(["Gas Price", formatted ?? String(receipt.effectiveGasPrice)]);
291
+ }
196
292
  }
197
- const network = resolveNetwork(program2);
198
293
  const explorerURL = etherscanTxURL(hash, network);
199
294
  if (explorerURL) {
200
295
  pairs.push(["Explorer", explorerURL]);
@@ -210,53 +305,74 @@ Examples:
210
305
  });
211
306
  }
212
307
 
213
- // src/lib/block-format.ts
214
- function parseHexQuantity(value) {
215
- if (typeof value !== "string" || !/^0x[0-9a-f]+$/i.test(value)) {
216
- return void 0;
217
- }
218
- try {
219
- return BigInt(value);
220
- } catch {
221
- return void 0;
222
- }
223
- }
224
- function formatWithCommas(value) {
225
- return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
226
- }
227
- function formatHexQuantity(value) {
228
- const parsed = parseHexQuantity(value);
229
- if (parsed === void 0) return void 0;
230
- return formatWithCommas(parsed);
231
- }
232
- function formatBlockTimestamp(value) {
233
- if (typeof value !== "string") return void 0;
234
- const parsed = parseHexQuantity(value);
235
- if (parsed === void 0) return void 0;
236
- const millis = Number(parsed) * 1e3;
237
- if (!Number.isFinite(millis)) return void 0;
238
- const d = new Date(millis);
239
- if (Number.isNaN(d.getTime())) return void 0;
240
- const iso = d.toISOString().replace(".000Z", "Z");
241
- return `${iso} ${dim("(" + timeAgo(value) + ")")}`;
242
- }
243
- function formatGasSummary(gasUsed, gasLimit, options) {
244
- const used = parseHexQuantity(gasUsed);
245
- const limit = parseHexQuantity(gasLimit);
246
- if (used === void 0 || limit === void 0) return void 0;
247
- const usedFormatted = formatWithCommas(used);
248
- const limitFormatted = formatWithCommas(limit);
249
- if (limit === 0n) return `${usedFormatted} / ${limitFormatted}`;
250
- const bps = used * 10000n / limit;
251
- const percent = Number(bps) / 100;
252
- const percentText = `${percent.toFixed(2)}%`;
253
- const percentDisplay = options?.colored ? dim(percentText) : percentText;
254
- return `${usedFormatted} / ${limitFormatted} (${percentDisplay})`;
308
+ // src/commands/receipt.ts
309
+ function registerReceipt(program2) {
310
+ program2.command("receipt").argument("[hash]", "Transaction hash (0x...) or pipe via stdin").description("Get transaction receipt (status, gas used, logs)").addHelpText(
311
+ "after",
312
+ `
313
+ Examples:
314
+ alchemy receipt 0xabc123...
315
+ echo 0xabc123... | alchemy receipt`
316
+ ).action(async (hashArg) => {
317
+ try {
318
+ const hash = hashArg ?? await readStdinArg("hash");
319
+ validateTxHash(hash);
320
+ const client = clientFromFlags(program2);
321
+ const receipt = await withSpinner(
322
+ "Fetching receipt\u2026",
323
+ "Receipt fetched",
324
+ () => client.call("eth_getTransactionReceipt", [hash])
325
+ );
326
+ if (!receipt) throw errNotFound(`receipt for ${hash}`);
327
+ if (isJSONMode()) {
328
+ printJSON(receipt);
329
+ return;
330
+ }
331
+ const pairs = [["Hash", hash]];
332
+ if (receipt.status === "0x1") {
333
+ pairs.push(["Status", `${successBadge()} ${green("Success")}`]);
334
+ } else if (receipt.status) {
335
+ pairs.push(["Status", `${failBadge()} ${red("Failed")}`]);
336
+ }
337
+ if (receipt.from) pairs.push(["From", String(receipt.from)]);
338
+ if (receipt.to) pairs.push(["To", String(receipt.to)]);
339
+ if (receipt.contractAddress) {
340
+ pairs.push(["Contract Created", String(receipt.contractAddress)]);
341
+ }
342
+ if (receipt.blockNumber) {
343
+ const hex = String(receipt.blockNumber);
344
+ const decoded = formatHexQuantity(hex);
345
+ pairs.push(["Block", decoded ? `${decoded} ${dim(`(${hex})`)}` : hex]);
346
+ }
347
+ if (receipt.gasUsed) {
348
+ const hex = String(receipt.gasUsed);
349
+ const decoded = formatHexQuantity(hex);
350
+ pairs.push(["Gas Used", decoded ? `${decoded} ${dim(`(${hex})`)}` : hex]);
351
+ }
352
+ if (receipt.effectiveGasPrice) {
353
+ const hex = String(receipt.effectiveGasPrice);
354
+ const wei = BigInt(hex);
355
+ const gwei = Number(wei) / 1e9;
356
+ pairs.push(["Gas Price", `${formatGwei(gwei)} gwei ${dim(`(${hex})`)}`]);
357
+ }
358
+ if (Array.isArray(receipt.logs)) {
359
+ pairs.push(["Logs", `${receipt.logs.length} event${receipt.logs.length === 1 ? "" : "s"}`]);
360
+ }
361
+ const network = resolveNetwork(program2);
362
+ const explorerURL = etherscanTxURL(hash, network);
363
+ if (explorerURL) {
364
+ pairs.push(["Explorer", explorerURL]);
365
+ }
366
+ printKeyValueBox(pairs);
367
+ } catch (err) {
368
+ exitWithError(err);
369
+ }
370
+ });
255
371
  }
256
372
 
257
373
  // src/commands/block.ts
258
374
  function registerBlock(program2) {
259
- program2.command("block <number>").description("Get block details by number").addHelpText(
375
+ program2.command("block").argument("<number>", "Block number, hex (0x...), or tag (latest, earliest, pending)").description("Get block details by number").addHelpText(
260
376
  "after",
261
377
  `
262
378
  Examples:
@@ -295,7 +411,7 @@ Examples:
295
411
  }
296
412
  const pairs = [];
297
413
  if (block.number) {
298
- const formatted = formatHexQuantity(block.number);
414
+ const formatted = formatHexWithRaw(block.number);
299
415
  pairs.push(["Block", bold(formatted ?? String(block.number))]);
300
416
  }
301
417
  if (block.hash) pairs.push(["Hash", String(block.hash)]);
@@ -313,11 +429,11 @@ Examples:
313
429
  pairs.push(["Gas", gasSummary]);
314
430
  } else {
315
431
  if (block.gasUsed) {
316
- const formatted = formatHexQuantity(block.gasUsed);
432
+ const formatted = formatHexWithRaw(block.gasUsed);
317
433
  pairs.push(["Gas Used", formatted ?? String(block.gasUsed)]);
318
434
  }
319
435
  if (block.gasLimit) {
320
- const formatted = formatHexQuantity(block.gasLimit);
436
+ const formatted = formatHexWithRaw(block.gasLimit);
321
437
  pairs.push(["Gas Limit", formatted ?? String(block.gasLimit)]);
322
438
  }
323
439
  }
@@ -336,8 +452,28 @@ Examples:
336
452
  }
337
453
 
338
454
  // src/commands/nfts.ts
455
+ async function promptNFTPagination(shown, total) {
456
+ const action = await promptSelect({
457
+ message: `Showing ${shown} of ${total} NFTs`,
458
+ options: [
459
+ { label: "Load next page", value: "next" },
460
+ { label: "Stop here", value: "stop" }
461
+ ],
462
+ initialValue: "next",
463
+ cancelMessage: "Stopped pagination."
464
+ });
465
+ if (action === null) return "stop";
466
+ return action;
467
+ }
468
+ function formatNFTRows(nfts) {
469
+ return nfts.map((nft) => [
470
+ nft.contract.name || dim("unnamed"),
471
+ nft.name || `#${nft.tokenId}`,
472
+ nft.contract.address
473
+ ]);
474
+ }
339
475
  function registerNFTs(program2) {
340
- const cmd = program2.command("nfts").description("NFT API wrappers").argument("[address]", "Wallet address (default action: list owned NFTs)").option("--limit <n>", "Maximum number of NFTs to return", parseInt).option("--page-key <key>", "Pagination key from a previous response").addHelpText(
476
+ const cmd = program2.command("nfts").description("NFT API wrappers").argument("[address]", "Wallet address or ENS name (default action: list owned NFTs)").option("--limit <n>", "Maximum number of NFTs to return per page", parseInt).option("--page-key <key>", "Pagination key from a previous response").addHelpText(
341
477
  "after",
342
478
  `
343
479
  Examples:
@@ -347,15 +483,15 @@ Examples:
347
483
  echo 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 | alchemy nfts`
348
484
  ).action(async (addressArg, opts) => {
349
485
  try {
350
- const address = addressArg ?? await readStdinArg("address");
351
- validateAddress(address);
486
+ const addressInput = addressArg ?? await readStdinArg("address");
487
+ const client = clientFromFlags(program2);
488
+ const address = await resolveAddress(addressInput, client);
352
489
  const params = {
353
490
  owner: address,
354
491
  withMetadata: "true"
355
492
  };
356
493
  if (opts.limit) params.pageSize = String(opts.limit);
357
494
  if (opts.pageKey) params.pageKey = opts.pageKey;
358
- const client = clientFromFlags(program2);
359
495
  const result = await withSpinner(
360
496
  "Fetching NFTs\u2026",
361
497
  "NFTs fetched",
@@ -369,19 +505,41 @@ Examples:
369
505
  emptyState("No NFTs found.");
370
506
  return;
371
507
  }
372
- const rows = result.ownedNfts.map((nft) => [
373
- nft.contract.name || dim("unnamed"),
374
- nft.name || `#${nft.tokenId}`,
375
- nft.contract.address
376
- ]);
377
- printTable(["Collection", "Name", "Contract"], rows);
508
+ let shown = result.ownedNfts.length;
509
+ printTable(["Collection", "Name", "Contract"], formatNFTRows(result.ownedNfts));
378
510
  if (verbose) {
379
511
  console.log("");
380
512
  printJSON(result);
381
513
  }
382
- if (result.pageKey) {
514
+ const interactive = isInteractiveAllowed(program2);
515
+ let pageKey = result.pageKey;
516
+ while (pageKey && interactive) {
517
+ const action = await promptNFTPagination(shown, result.totalCount);
518
+ if (action === "stop") {
519
+ console.log(`
520
+ ${dim(`Next page key: ${pageKey}`)}`);
521
+ break;
522
+ }
523
+ const nextParams = {
524
+ owner: address,
525
+ withMetadata: "true",
526
+ pageKey
527
+ };
528
+ if (opts.limit) nextParams.pageSize = String(opts.limit);
529
+ const nextResult = await withSpinner(
530
+ "Fetching next page\u2026",
531
+ "Page fetched",
532
+ () => client.callEnhanced("getNFTsForOwner", nextParams)
533
+ );
534
+ if (nextResult.ownedNfts.length > 0) {
535
+ printTable(["Collection", "Name", "Contract"], formatNFTRows(nextResult.ownedNfts));
536
+ shown += nextResult.ownedNfts.length;
537
+ }
538
+ pageKey = nextResult.pageKey;
539
+ }
540
+ if (pageKey && !interactive) {
383
541
  console.log(`
384
- ${dim(`More results available. Use --page-key ${result.pageKey} to see the next page.`)}`);
542
+ ${dim(`More results available. Use --page-key ${pageKey} to see the next page.`)}`);
385
543
  }
386
544
  } catch (err) {
387
545
  exitWithError(err);
@@ -425,8 +583,34 @@ Examples:
425
583
  }
426
584
 
427
585
  // src/commands/tokens.ts
586
+ async function promptTokensPagination() {
587
+ const action = await promptSelect({
588
+ message: "More token balances available",
589
+ options: [
590
+ { label: "Load next page", value: "next" },
591
+ { label: "Stop here", value: "stop" }
592
+ ],
593
+ initialValue: "next",
594
+ cancelMessage: "Stopped pagination."
595
+ });
596
+ if (action === null) return "stop";
597
+ return action;
598
+ }
599
+ function formatTokenRows(balances) {
600
+ const nonZero = balances.filter(
601
+ (tb) => tb.tokenBalance !== "0x0" && tb.tokenBalance !== "0x0000000000000000000000000000000000000000000000000000000000000000"
602
+ );
603
+ return nonZero.map((tb) => {
604
+ let decimalBalance = dim("unparseable");
605
+ try {
606
+ decimalBalance = BigInt(tb.tokenBalance).toString();
607
+ } catch {
608
+ }
609
+ return [tb.contractAddress, decimalBalance, tb.tokenBalance];
610
+ });
611
+ }
428
612
  function registerTokens(program2) {
429
- const cmd = program2.command("tokens").description("Token API wrappers").argument("[address]", "Wallet address (default action: list balances)").option("--page-key <key>", "Pagination key from a previous response").addHelpText(
613
+ 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(
430
614
  "after",
431
615
  `
432
616
  Examples:
@@ -436,13 +620,13 @@ Examples:
436
620
  echo 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 | alchemy tokens`
437
621
  ).action(async (addressArg, opts) => {
438
622
  try {
439
- const address = addressArg ?? await readStdinArg("address");
440
- validateAddress(address);
623
+ const addressInput = addressArg ?? await readStdinArg("address");
624
+ const client = clientFromFlags(program2);
625
+ const address = await resolveAddress(addressInput, client);
441
626
  const params = [address];
442
627
  if (opts.pageKey) {
443
628
  params.push("erc20", { pageKey: opts.pageKey });
444
629
  }
445
- const client = clientFromFlags(program2);
446
630
  const result = await withSpinner(
447
631
  "Fetching token balances\u2026",
448
632
  "Token balances fetched",
@@ -452,36 +636,54 @@ Examples:
452
636
  printJSON(result);
453
637
  return;
454
638
  }
455
- const nonZero = result.tokenBalances.filter(
456
- (tb) => tb.tokenBalance !== "0x0" && tb.tokenBalance !== "0x0000000000000000000000000000000000000000000000000000000000000000"
457
- );
458
- if (nonZero.length === 0) {
639
+ const rows = formatTokenRows(result.tokenBalances);
640
+ if (rows.length === 0) {
459
641
  emptyState("No token balances found.");
460
642
  return;
461
643
  }
462
- const rows = nonZero.map((tb) => {
463
- let decimalBalance = dim("unparseable");
464
- try {
465
- decimalBalance = BigInt(tb.tokenBalance).toString();
466
- } catch {
467
- }
468
- return [tb.contractAddress, decimalBalance, tb.tokenBalance];
469
- });
644
+ let totalShown = rows.length;
470
645
  printKeyValueBox([
471
646
  ["Address", address],
472
647
  ["Network", client.network],
473
- ["Non-zero tokens", String(nonZero.length)]
648
+ ["Tokens", String(totalShown)]
474
649
  ]);
475
650
  printTable(["Contract", "Balance (base units)", "Raw (hex)"], rows);
476
651
  console.log(`
477
- ${dim(`Showing ${nonZero.length} of ${result.tokenBalances.length} contracts (non-zero only).`)}`);
652
+ ${dim(`${totalShown} tokens (zero balances hidden).`)}`);
478
653
  if (verbose) {
479
654
  console.log("");
480
655
  printJSON(result);
481
656
  }
482
- if (result.pageKey) {
657
+ const interactive = isInteractiveAllowed(program2);
658
+ let pageKey = result.pageKey;
659
+ while (pageKey && interactive) {
660
+ const action = await promptTokensPagination();
661
+ if (action === "stop") {
662
+ console.log(`
663
+ ${dim(`Next page key: ${pageKey}`)}`);
664
+ break;
665
+ }
666
+ const nextResult = await withSpinner(
667
+ "Fetching next page\u2026",
668
+ "Page fetched",
669
+ () => client.call("alchemy_getTokenBalances", [address, "erc20", { pageKey }])
670
+ );
671
+ if (isJSONMode()) {
672
+ printJSON(nextResult);
673
+ return;
674
+ }
675
+ const nextRows = formatTokenRows(nextResult.tokenBalances);
676
+ totalShown += nextRows.length;
677
+ if (nextRows.length > 0) {
678
+ printTable(["Contract", "Balance (base units)", "Raw (hex)"], nextRows);
679
+ }
483
680
  console.log(`
484
- ${dim(`More results available. Use --page-key ${result.pageKey} to see the next page.`)}`);
681
+ ${dim(`${totalShown} tokens total (zero balances hidden).`)}`);
682
+ pageKey = nextResult.pageKey;
683
+ }
684
+ if (pageKey && !interactive) {
685
+ console.log(`
686
+ ${dim(`More results available. Use --page-key ${pageKey} to see the next page.`)}`);
485
687
  }
486
688
  } catch (err) {
487
689
  exitWithError(err);
@@ -528,7 +730,7 @@ Examples:
528
730
  // src/commands/network.ts
529
731
  function registerNetwork(program2) {
530
732
  const cmd = program2.command("network").description("Manage networks");
531
- cmd.command("list").description("List supported RPC network slugs").option(
733
+ cmd.command("list").description("List RPC network IDs for use with --network (e.g. eth-mainnet)").option(
532
734
  "--configured",
533
735
  "List only configured app RPC networks (requires access key and app context)"
534
736
  ).option(
@@ -576,7 +778,7 @@ function registerNetwork(program2) {
576
778
  console.log(`
577
779
  Current: ${green(current)}`);
578
780
  console.log(
579
- ` ${dim("Need Admin API chain enums instead? Run: alchemy chains list")}`
781
+ ` ${dim("Need Admin API chain identifiers (e.g. ETH_MAINNET)? See: apps chains")}`
580
782
  );
581
783
  if (verbose) {
582
784
  console.log("");
@@ -603,43 +805,6 @@ function registerVersion(program2) {
603
805
  });
604
806
  }
605
807
 
606
- // src/commands/chains.ts
607
- function registerChains(program2) {
608
- const cmd = program2.command("chains").description("Manage Admin API chain enums");
609
- cmd.command("list").description("List available Admin API chain enums").action(async () => {
610
- try {
611
- const admin = adminClientFromFlags(program2);
612
- const chains = await withSpinner(
613
- "Fetching chains\u2026",
614
- "Chains fetched",
615
- () => admin.listChains()
616
- );
617
- if (isJSONMode()) {
618
- printJSON(chains);
619
- return;
620
- }
621
- if (chains.length === 0) {
622
- emptyState("No chain networks were returned.");
623
- return;
624
- }
625
- const rows = chains.map((c) => [
626
- c.id,
627
- c.name,
628
- c.isTestnet ? dim("yes") : "no",
629
- c.availability === "public" ? green(c.availability) : dim(c.availability),
630
- c.currency
631
- ]);
632
- printTable(["ID", "Name", "Testnet", "Availability", "Currency"], rows);
633
- if (verbose) {
634
- console.log("");
635
- printJSON(chains);
636
- }
637
- } catch (err) {
638
- exitWithError(err);
639
- }
640
- });
641
- }
642
-
643
808
  // src/commands/apps.ts
644
809
  function maskAppSecrets(app) {
645
810
  return {
@@ -658,19 +823,14 @@ async function promptPaginationAction() {
658
823
  message: "More apps available",
659
824
  options: [
660
825
  { label: "Load next page", value: "next" },
661
- { label: "Load all remaining pages", value: "all" },
826
+ { label: "Load next 5 pages", value: "next5" },
662
827
  { label: "Stop here", value: "stop" }
663
828
  ],
664
829
  initialValue: "next",
665
830
  cancelMessage: "Stopped pagination."
666
831
  });
667
- if (action === null) {
668
- return "stop";
669
- }
670
- if (action === "next" || action === "all" || action === "stop") {
671
- return action;
672
- }
673
- return "stop";
832
+ if (action === null) return "stop";
833
+ return action;
674
834
  }
675
835
  function matchesSearch(app, query) {
676
836
  const q = query.toLowerCase();
@@ -763,7 +923,7 @@ function registerApps(program2) {
763
923
  const interactivePagination = isInteractiveAllowed(program2) && !opts.all;
764
924
  if (interactivePagination) {
765
925
  let page = result;
766
- let autoFetchRemaining = false;
926
+ let batchRemaining = 0;
767
927
  let pagesFetched = 0;
768
928
  let appsFetched = 0;
769
929
  while (true) {
@@ -780,7 +940,7 @@ function registerApps(program2) {
780
940
  printFetchSummary(appsFetched, pagesFetched);
781
941
  return;
782
942
  }
783
- if (!autoFetchRemaining) {
943
+ if (batchRemaining <= 0) {
784
944
  printFetchSummary(appsFetched, pagesFetched, { suffix: "so far" });
785
945
  const action = await promptPaginationAction();
786
946
  if (action === "stop") {
@@ -789,9 +949,11 @@ function registerApps(program2) {
789
949
  printFetchSummary(appsFetched, pagesFetched);
790
950
  return;
791
951
  }
792
- if (action === "all") {
793
- autoFetchRemaining = true;
952
+ if (action === "next5") {
953
+ batchRemaining = 4;
794
954
  }
955
+ } else {
956
+ batchRemaining -= 1;
795
957
  }
796
958
  page = await withSpinner(
797
959
  "Fetching next page\u2026",
@@ -1006,6 +1168,45 @@ function registerApps(program2) {
1006
1168
  exitWithError(err);
1007
1169
  }
1008
1170
  });
1171
+ cmd.command("chains").description("List Admin API chain identifiers for app configuration (e.g. ETH_MAINNET)").action(async () => {
1172
+ try {
1173
+ const admin = adminClientFromFlags(program2);
1174
+ const chains = await withSpinner(
1175
+ "Fetching chains\u2026",
1176
+ "Chains fetched",
1177
+ () => admin.listChains()
1178
+ );
1179
+ if (isJSONMode()) {
1180
+ printJSON(chains);
1181
+ return;
1182
+ }
1183
+ if (chains.length === 0) {
1184
+ emptyState("No chain networks were returned.");
1185
+ return;
1186
+ }
1187
+ const formatChainId = (value) => {
1188
+ if (!value) return dim("\u2014");
1189
+ const num = parseInt(value, 10);
1190
+ if (isNaN(num)) return value;
1191
+ return `${num} (0x${num.toString(16)})`;
1192
+ };
1193
+ const rows = chains.map((c) => [
1194
+ c.id,
1195
+ c.name,
1196
+ formatChainId(c.networkChainId),
1197
+ c.isTestnet ? dim("yes") : "no",
1198
+ c.availability === "public" ? green(c.availability) : dim(c.availability),
1199
+ c.currency
1200
+ ]);
1201
+ printTable(["ID", "Name", "Chain ID", "Testnet", "Availability", "Currency"], rows);
1202
+ if (verbose) {
1203
+ console.log("");
1204
+ printJSON(chains);
1205
+ }
1206
+ } catch (err) {
1207
+ exitWithError(err);
1208
+ }
1209
+ });
1009
1210
  }
1010
1211
 
1011
1212
  // src/commands/setup.ts
@@ -1066,7 +1267,7 @@ function normalizeTraceMethod(method) {
1066
1267
  return `trace_${normalized}`;
1067
1268
  }
1068
1269
  function registerTrace(program2) {
1069
- program2.command("trace <method> [params...]").description("Call a trace_* method").action(async (method, params) => {
1270
+ program2.command("trace").argument("<method>", "Trace method name (e.g. trace_transaction)").argument("[params...]", "Method parameters as JSON values").description("Call a trace_* method").action(async (method, params) => {
1070
1271
  try {
1071
1272
  const client = clientFromFlags(program2);
1072
1273
  const rpcMethod = normalizeTraceMethod(method);
@@ -1088,7 +1289,7 @@ function normalizeDebugMethod(method) {
1088
1289
  return `debug_${method.replace(/-/g, "_")}`;
1089
1290
  }
1090
1291
  function registerDebug(program2) {
1091
- program2.command("debug <method> [params...]").description("Call a debug_* method").action(async (method, params) => {
1292
+ program2.command("debug").argument("<method>", "Debug method name (e.g. debug_traceTransaction)").argument("[params...]", "Method parameters as JSON values").description("Call a debug_* method").action(async (method, params) => {
1092
1293
  try {
1093
1294
  const client = clientFromFlags(program2);
1094
1295
  const rpcMethod = normalizeDebugMethod(method);
@@ -1105,29 +1306,72 @@ function registerDebug(program2) {
1105
1306
  }
1106
1307
 
1107
1308
  // src/commands/transfers.ts
1309
+ async function promptTransfersPagination(shown) {
1310
+ const action = await promptSelect({
1311
+ message: `${shown} transfers loaded \u2014 more available`,
1312
+ options: [
1313
+ { label: "Load next page", value: "next" },
1314
+ { label: "Stop here", value: "stop" }
1315
+ ],
1316
+ initialValue: "next",
1317
+ cancelMessage: "Stopped pagination."
1318
+ });
1319
+ if (action === null) return "stop";
1320
+ return action;
1321
+ }
1322
+ var TABLE_HEADERS = ["Block", "From", "To", "Value", "Asset", "Category"];
1323
+ function formatTransferRows(transfers) {
1324
+ return transfers.map((t) => {
1325
+ const block = t.blockNum ? String(parseInt(t.blockNum, 16)) : dim("\u2014");
1326
+ const from = t.from ? `${t.from.slice(0, 8)}\u2026${t.from.slice(-4)}` : dim("\u2014");
1327
+ const to = t.to ? `${t.to.slice(0, 8)}\u2026${t.to.slice(-4)}` : dim("contract creation");
1328
+ const value = t.value !== null && t.value !== void 0 ? String(t.value) : dim("\u2014");
1329
+ const asset = t.asset ?? dim("\u2014");
1330
+ const category = t.category;
1331
+ return [block, from, to, value, asset, category];
1332
+ });
1333
+ }
1108
1334
  function registerTransfers(program2) {
1109
- program2.command("transfers [address]").description("Get transfer history (alchemy_getAssetTransfers)").option("--from-address <address>", "Filter sender address").option("--to-address <address>", "Filter recipient address").option("--from-block <block>", "Start block (default: 0x0)").option("--to-block <block>", "End block (default: latest)").option("--category <list>", "Comma-separated categories (erc20,erc721,erc1155,external,internal,specialnft)").option("--max-count <hexOrDecimal>", "Max records to return").option("--page-key <key>", "Pagination key").action(async (addressArg, opts) => {
1335
+ program2.command("transfers").argument("[address]", "Wallet address or ENS name \u2014 queries outgoing transfers (use --to-address for incoming)").description("Get transfer history (alchemy_getAssetTransfers)").option("--from-address <address>", "Filter sender address").option("--to-address <address>", "Filter recipient address").option("--from-block <block>", "Start block (default: 0x0)").option("--to-block <block>", "End block (default: latest)").option("--category <list>", "Comma-separated categories (erc20,erc721,erc1155,external,internal,specialnft)").option("--max-count <hexOrDecimal>", "Max records to return per page").option("--page-key <key>", "Pagination key").addHelpText(
1336
+ "after",
1337
+ `
1338
+ Examples:
1339
+ # Outgoing transfers from an address
1340
+ alchemy transfers 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
1341
+
1342
+ # Incoming transfers to an address
1343
+ alchemy transfers --to-address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
1344
+
1345
+ # Outgoing ERC-20 transfers only
1346
+ alchemy transfers --from-address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --category erc20
1347
+
1348
+ # Transfers within a block range
1349
+ alchemy transfers 0xd8dA... --from-block 0x100000 --to-block latest`
1350
+ ).action(async (addressArg, opts) => {
1110
1351
  try {
1111
1352
  const client = clientFromFlags(program2);
1112
- const address = addressArg ?? void 0;
1113
- if (address) validateAddress(address);
1114
- if (opts.fromAddress) validateAddress(opts.fromAddress);
1115
- if (opts.toAddress) validateAddress(opts.toAddress);
1116
- const filter = {
1353
+ const address = addressArg ? await resolveAddress(addressArg, client) : void 0;
1354
+ if (opts.fromAddress) opts.fromAddress = await resolveAddress(opts.fromAddress, client);
1355
+ if (opts.toAddress) opts.toAddress = await resolveAddress(opts.toAddress, client);
1356
+ const baseFilter = {
1117
1357
  fromBlock: opts.fromBlock ?? "0x0",
1118
1358
  toBlock: opts.toBlock ?? "latest",
1119
- withMetadata: true
1359
+ withMetadata: true,
1360
+ category: opts.category ? splitCommaList(opts.category) : ["external", "internal", "erc20", "erc721", "erc1155", "specialnft"]
1120
1361
  };
1362
+ if (opts.maxCount) {
1363
+ baseFilter.maxCount = opts.maxCount.startsWith("0x") ? opts.maxCount : `0x${Number.parseInt(opts.maxCount, 10).toString(16)}`;
1364
+ } else if (!isJSONMode()) {
1365
+ baseFilter.maxCount = "0x19";
1366
+ }
1367
+ if (opts.pageKey) baseFilter.pageKey = opts.pageKey;
1368
+ const filter = { ...baseFilter };
1121
1369
  if (address && !opts.fromAddress && !opts.toAddress) {
1122
1370
  filter.fromAddress = address;
1123
- filter.toAddress = address;
1124
1371
  } else {
1125
1372
  if (opts.fromAddress) filter.fromAddress = opts.fromAddress;
1126
1373
  if (opts.toAddress) filter.toAddress = opts.toAddress;
1127
1374
  }
1128
- if (opts.category) filter.category = splitCommaList(opts.category);
1129
- if (opts.maxCount) filter.maxCount = opts.maxCount.startsWith("0x") ? opts.maxCount : `0x${Number.parseInt(opts.maxCount, 10).toString(16)}`;
1130
- if (opts.pageKey) filter.pageKey = opts.pageKey;
1131
1375
  const result = await withSpinner(
1132
1376
  "Fetching transfers\u2026",
1133
1377
  "Transfers fetched",
@@ -1135,8 +1379,43 @@ function registerTransfers(program2) {
1135
1379
  );
1136
1380
  if (isJSONMode()) {
1137
1381
  printJSON(result);
1138
- } else {
1139
- printSyntaxJSON(result);
1382
+ return;
1383
+ }
1384
+ let totalShown = result.transfers.length;
1385
+ if (totalShown === 0) {
1386
+ console.log(dim("No transfers found."));
1387
+ return;
1388
+ }
1389
+ console.log(`${totalShown} transfer${totalShown === 1 ? "" : "s"}
1390
+ `);
1391
+ printTable(TABLE_HEADERS, formatTransferRows(result.transfers));
1392
+ const interactive = isInteractiveAllowed(program2);
1393
+ let pageKey = result.pageKey;
1394
+ while (pageKey && interactive) {
1395
+ const action = await promptTransfersPagination(totalShown);
1396
+ if (action === "stop") {
1397
+ console.log(`
1398
+ ${dim(`Next page key: ${pageKey}`)}`);
1399
+ break;
1400
+ }
1401
+ filter.pageKey = pageKey;
1402
+ const nextResult = await withSpinner(
1403
+ "Fetching next page\u2026",
1404
+ "Page fetched",
1405
+ () => client.call("alchemy_getAssetTransfers", [filter])
1406
+ );
1407
+ if (nextResult.transfers.length > 0) {
1408
+ totalShown += nextResult.transfers.length;
1409
+ console.log(`
1410
+ ${totalShown} transfers total
1411
+ `);
1412
+ printTable(TABLE_HEADERS, formatTransferRows(nextResult.transfers));
1413
+ }
1414
+ pageKey = nextResult.pageKey;
1415
+ }
1416
+ if (pageKey && !interactive) {
1417
+ console.log(`
1418
+ ${dim(`More results available. Use --page-key ${pageKey} to see the next page.`)}`);
1140
1419
  }
1141
1420
  } catch (err) {
1142
1421
  exitWithError(err);
@@ -1590,7 +1869,7 @@ function registerSolana(program2) {
1590
1869
  const cmd = program2.command("solana").description("Solana RPC and DAS wrappers");
1591
1870
  cmd.command("rpc <method> [params...]").description("Call a Solana JSON-RPC method").action(async (method, params) => {
1592
1871
  try {
1593
- const client = clientFromFlags(program2);
1872
+ const client = clientFromFlags(program2, { defaultNetwork: "solana-mainnet" });
1594
1873
  const result = await withSpinner(
1595
1874
  `Calling ${method}\u2026`,
1596
1875
  `Called ${method}`,
@@ -1601,14 +1880,15 @@ function registerSolana(program2) {
1601
1880
  exitWithError(err);
1602
1881
  }
1603
1882
  });
1604
- const das = cmd.command("das").description("Solana DAS (Digital Asset Standard) wrappers");
1605
- das.command("<method> [params...]").description("Call DAS method (e.g. getAssetsByOwner)").action(async (method, params) => {
1883
+ cmd.command("das <method> [params...]").description("Call a Solana DAS method (e.g. getAssetsByOwner)").action(async (method, params) => {
1606
1884
  try {
1607
- const client = clientFromFlags(program2);
1885
+ const client = clientFromFlags(program2, { defaultNetwork: "solana-mainnet" });
1886
+ const parsed = parseCLIParams(params);
1887
+ const rpcParams = parsed.length === 1 && typeof parsed[0] === "object" && parsed[0] !== null && !Array.isArray(parsed[0]) ? parsed[0] : parsed;
1608
1888
  const result = await withSpinner(
1609
1889
  `Calling ${method}\u2026`,
1610
1890
  `Called ${method}`,
1611
- () => client.call(method, parseCLIParams(params))
1891
+ () => client.call(method, rpcParams)
1612
1892
  );
1613
1893
  printSyntaxJSON(result);
1614
1894
  } catch (err) {
@@ -1617,6 +1897,311 @@ function registerSolana(program2) {
1617
1897
  });
1618
1898
  }
1619
1899
 
1900
+ // src/commands/gas.ts
1901
+ function registerGas(program2) {
1902
+ program2.command("gas").description("Get current gas prices (base fee + priority fee)").addHelpText(
1903
+ "after",
1904
+ `
1905
+ Examples:
1906
+ alchemy gas
1907
+ alchemy gas -n polygon-mainnet
1908
+ alchemy gas --json`
1909
+ ).action(async () => {
1910
+ try {
1911
+ const client = clientFromFlags(program2);
1912
+ const [gasPrice, maxPriorityFee] = await withSpinner(
1913
+ "Fetching gas prices\u2026",
1914
+ "Gas prices fetched",
1915
+ async () => {
1916
+ const [gp, mpf] = await Promise.all([
1917
+ client.call("eth_gasPrice", []),
1918
+ client.call("eth_maxPriorityFeePerGas", []).catch(() => null)
1919
+ ]);
1920
+ return [gp, mpf];
1921
+ }
1922
+ );
1923
+ const network = resolveNetwork(program2);
1924
+ const gasPriceWei = BigInt(gasPrice);
1925
+ const gasPriceGwei = Number(gasPriceWei) / 1e9;
1926
+ let priorityFeeGwei = null;
1927
+ let priorityFeeWei = null;
1928
+ if (maxPriorityFee) {
1929
+ priorityFeeWei = BigInt(maxPriorityFee);
1930
+ priorityFeeGwei = Number(priorityFeeWei) / 1e9;
1931
+ }
1932
+ if (isJSONMode()) {
1933
+ const json = {
1934
+ gasPrice,
1935
+ gasPriceGwei: formatGwei(gasPriceGwei),
1936
+ network
1937
+ };
1938
+ if (maxPriorityFee) {
1939
+ json.maxPriorityFeePerGas = maxPriorityFee;
1940
+ json.maxPriorityFeePerGasGwei = formatGwei(priorityFeeGwei);
1941
+ }
1942
+ printJSON(json);
1943
+ } else {
1944
+ const pairs = [];
1945
+ const gasPriceFormatted = formatGweiWithRaw(gasPrice);
1946
+ pairs.push(["Gas Price", gasPriceFormatted ?? `${formatGwei(gasPriceGwei)} gwei`]);
1947
+ if (maxPriorityFee && priorityFeeGwei !== null) {
1948
+ const priorityFormatted = formatGweiWithRaw(maxPriorityFee);
1949
+ pairs.push(["Priority Fee", priorityFormatted ?? `${formatGwei(priorityFeeGwei)} gwei`]);
1950
+ }
1951
+ pairs.push(["Network", network]);
1952
+ printKeyValueBox(pairs);
1953
+ if (verbose) {
1954
+ console.log("");
1955
+ const verboseData = {
1956
+ rpcMethod: "eth_gasPrice",
1957
+ rpcResult: gasPrice
1958
+ };
1959
+ if (maxPriorityFee) {
1960
+ verboseData.rpcMethod2 = "eth_maxPriorityFeePerGas";
1961
+ verboseData.rpcResult2 = maxPriorityFee;
1962
+ }
1963
+ printJSON(verboseData);
1964
+ }
1965
+ }
1966
+ } catch (err) {
1967
+ exitWithError(err);
1968
+ }
1969
+ });
1970
+ }
1971
+
1972
+ // src/commands/logs.ts
1973
+ var PAGE_SIZE = 25;
1974
+ var LARGE_PAGE_SIZE = 100;
1975
+ function normalizeBlockParam(value) {
1976
+ if (value === "latest" || value === "earliest" || value === "pending") {
1977
+ return value;
1978
+ }
1979
+ if (value.startsWith("0x")) return value;
1980
+ const num = parseInt(value, 10);
1981
+ if (isNaN(num) || num < 0) {
1982
+ throw errInvalidArgs("Block must be a number, hex, or tag (latest, earliest, pending).");
1983
+ }
1984
+ return `0x${num.toString(16)}`;
1985
+ }
1986
+ function formatLogRows(logs) {
1987
+ return logs.map((log) => {
1988
+ const block = formatHexQuantity(log.blockNumber) ?? log.blockNumber;
1989
+ const idx = formatHexQuantity(log.logIndex) ?? log.logIndex;
1990
+ const topic0 = log.topics[0] ? `${log.topics[0].slice(0, 10)}\u2026` : dim("none");
1991
+ const addr = log.address;
1992
+ const txHash = `${log.transactionHash.slice(0, 10)}\u2026`;
1993
+ return [block, idx, addr, topic0, txHash];
1994
+ });
1995
+ }
1996
+ var TABLE_HEADERS2 = ["Block", "Index", "Address", "Topic0", "Tx Hash"];
1997
+ async function promptPaginationAction2(shown, total) {
1998
+ const remaining = total - shown;
1999
+ const options = [
2000
+ { label: `Show next ${Math.min(PAGE_SIZE, remaining)}`, value: "next" }
2001
+ ];
2002
+ if (remaining > PAGE_SIZE) {
2003
+ options.push({ label: `Show next ${Math.min(LARGE_PAGE_SIZE, remaining)}`, value: "next-large" });
2004
+ }
2005
+ options.push({ label: "Stop here", value: "stop" });
2006
+ const action = await promptSelect({
2007
+ message: `Showing ${shown} of ${total} logs (${remaining} remaining)`,
2008
+ options,
2009
+ initialValue: "next",
2010
+ cancelMessage: "Stopped pagination."
2011
+ });
2012
+ if (action === null) return "stop";
2013
+ return action;
2014
+ }
2015
+ function registerLogs(program2) {
2016
+ program2.command("logs").description("Query event logs (eth_getLogs)").addHelpText(
2017
+ "after",
2018
+ `
2019
+ Examples:
2020
+ alchemy logs --from-block 18000000 --to-block 18000010
2021
+ alchemy logs --address 0xdAC17F958D2ee523a2206206994597C13D831ec7 --from-block 18000000 --to-block 18000010
2022
+ alchemy logs --address 0xdAC17F958D2ee523a2206206994597C13D831ec7 --topic 0xddf252ad...
2023
+ alchemy logs --from-block latest --json`
2024
+ ).option("--address <address>", "Contract address to filter logs").option("--topic <topic...>", "Event topic(s) to filter (topic0, topic1, ...)").option("--from-block <block>", "Start block (number, hex, or tag)", "latest").option("--to-block <block>", "End block (number, hex, or tag)", "latest").action(async (opts) => {
2025
+ try {
2026
+ const filter = {
2027
+ fromBlock: normalizeBlockParam(opts.fromBlock),
2028
+ toBlock: normalizeBlockParam(opts.toBlock)
2029
+ };
2030
+ if (opts.address) {
2031
+ filter.address = opts.address;
2032
+ }
2033
+ if (opts.topic && opts.topic.length > 0) {
2034
+ filter.topics = opts.topic;
2035
+ }
2036
+ const client = clientFromFlags(program2);
2037
+ const logs = await withSpinner(
2038
+ "Fetching logs\u2026",
2039
+ "Logs fetched",
2040
+ () => client.call("eth_getLogs", [filter])
2041
+ );
2042
+ const network = resolveNetwork(program2);
2043
+ if (isJSONMode()) {
2044
+ printJSON({ logs, count: logs.length, network });
2045
+ return;
2046
+ }
2047
+ if (logs.length === 0) {
2048
+ console.log(dim("No logs found for the given filter."));
2049
+ return;
2050
+ }
2051
+ const total = logs.length;
2052
+ const interactive = isInteractiveAllowed(program2);
2053
+ if (!interactive) {
2054
+ console.log(`Found ${total} log${total === 1 ? "" : "s"} on ${network}
2055
+ `);
2056
+ printTable(TABLE_HEADERS2, formatLogRows(logs));
2057
+ } else {
2058
+ let offset = 0;
2059
+ const firstPage = logs.slice(0, PAGE_SIZE);
2060
+ console.log(`Found ${total} log${total === 1 ? "" : "s"} on ${network}
2061
+ `);
2062
+ printTable(TABLE_HEADERS2, formatLogRows(firstPage));
2063
+ offset = firstPage.length;
2064
+ while (offset < total) {
2065
+ const action = await promptPaginationAction2(offset, total);
2066
+ if (action === "stop") break;
2067
+ const size = action === "next-large" ? LARGE_PAGE_SIZE : PAGE_SIZE;
2068
+ const page = logs.slice(offset, offset + size);
2069
+ console.log("");
2070
+ printTable(TABLE_HEADERS2, formatLogRows(page));
2071
+ offset += page.length;
2072
+ }
2073
+ }
2074
+ if (total >= 1e4) {
2075
+ console.log("");
2076
+ console.log(dim("\u26A0 RPC result limit reached (10,000). Narrow your block range for complete results."));
2077
+ }
2078
+ } catch (err) {
2079
+ exitWithError(err);
2080
+ }
2081
+ });
2082
+ }
2083
+
2084
+ // src/commands/completions.ts
2085
+ function getDescription(cmd) {
2086
+ try {
2087
+ return typeof cmd.description === "function" ? cmd.description() : String(cmd.description || "");
2088
+ } catch {
2089
+ return "";
2090
+ }
2091
+ }
2092
+ function collectCommands(cmd, prefix = []) {
2093
+ const results = [];
2094
+ for (const sub of cmd.commands) {
2095
+ const path = [...prefix, sub.name()];
2096
+ results.push({ path, description: getDescription(sub) });
2097
+ results.push(...collectCommands(sub, path));
2098
+ }
2099
+ return results;
2100
+ }
2101
+ function generateBash(program2) {
2102
+ const commands = collectCommands(program2);
2103
+ const topLevel = program2.commands.map((c) => c.name()).join(" ");
2104
+ const cases = commands.filter((c) => c.path.length === 1).map((c) => {
2105
+ const subs = commands.filter((s) => s.path.length === 2 && s.path[0] === c.path[0]).map((s) => s.path[1]);
2106
+ if (subs.length === 0) return "";
2107
+ return ` ${c.path[0]}) COMPREPLY=($(compgen -W "${subs.join(" ")}" -- "$cur")) ;;`;
2108
+ }).filter(Boolean).join("\n");
2109
+ return `# bash completion for alchemy CLI
2110
+ # Add to ~/.bashrc: eval "$(alchemy completions bash)"
2111
+ _alchemy_completions() {
2112
+ local cur prev
2113
+ cur="\${COMP_WORDS[COMP_CWORD]}"
2114
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
2115
+
2116
+ if [[ \${COMP_CWORD} -eq 1 ]]; then
2117
+ COMPREPLY=($(compgen -W "${topLevel}" -- "$cur"))
2118
+ return
2119
+ fi
2120
+
2121
+ case "$prev" in
2122
+ ${cases}
2123
+ -n|--network) COMPREPLY=() ;;
2124
+ *) COMPREPLY=() ;;
2125
+ esac
2126
+ }
2127
+ complete -F _alchemy_completions alchemy
2128
+ `;
2129
+ }
2130
+ function generateZsh(program2) {
2131
+ const commands = collectCommands(program2);
2132
+ const topLevel = program2.commands.map((c) => `'${c.name()}:${getDescription(c).replace(/'/g, "'\\''")}'`).join("\n ");
2133
+ const subcommandCases = commands.filter((c) => c.path.length === 1).map((c) => {
2134
+ const subs = commands.filter((s) => s.path.length === 2 && s.path[0] === c.path[0]).map((s) => `'${s.path[1]}:${s.description.replace(/'/g, "'\\''")}'`);
2135
+ if (subs.length === 0) return "";
2136
+ return ` ${c.path[0]})
2137
+ _values 'subcommand' \\
2138
+ ${subs.join(" \\\n ")}
2139
+ ;;`;
2140
+ }).filter(Boolean).join("\n");
2141
+ return `#compdef alchemy
2142
+ # zsh completion for alchemy CLI
2143
+ # Add to ~/.zshrc: eval "$(alchemy completions zsh)"
2144
+ _alchemy() {
2145
+ local -a commands
2146
+ if (( CURRENT == 2 )); then
2147
+ commands=(
2148
+ ${topLevel}
2149
+ )
2150
+ _describe 'command' commands
2151
+ return
2152
+ fi
2153
+
2154
+ case "$words[2]" in
2155
+ ${subcommandCases}
2156
+ esac
2157
+ }
2158
+ _alchemy "$@"
2159
+ compdef _alchemy alchemy
2160
+ `;
2161
+ }
2162
+ function generateFish(program2) {
2163
+ const commands = collectCommands(program2);
2164
+ const lines = commands.map((c) => {
2165
+ const desc = c.description.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
2166
+ if (c.path.length === 1) {
2167
+ return `complete -c alchemy -n '__fish_use_subcommand' -a '${c.path[0]}' -d '${desc}'`;
2168
+ }
2169
+ if (c.path.length === 2) {
2170
+ return `complete -c alchemy -n '__fish_seen_subcommand_from ${c.path[0]}' -a '${c.path[1]}' -d '${desc}'`;
2171
+ }
2172
+ return "";
2173
+ }).filter(Boolean);
2174
+ return `# fish completion for alchemy CLI
2175
+ # Add to ~/.config/fish/completions/alchemy.fish
2176
+ ${lines.join("\n")}
2177
+ `;
2178
+ }
2179
+ function registerCompletions(program2) {
2180
+ program2.command("completions").argument("<shell>", "Shell type: bash, zsh, or fish").description("Generate shell completion scripts").addHelpText(
2181
+ "after",
2182
+ `
2183
+ Examples:
2184
+ alchemy completions bash >> ~/.bashrc
2185
+ eval "$(alchemy completions zsh)"
2186
+ alchemy completions fish > ~/.config/fish/completions/alchemy.fish`
2187
+ ).action((shell) => {
2188
+ switch (shell.toLowerCase()) {
2189
+ case "bash":
2190
+ process.stdout.write(generateBash(program2));
2191
+ break;
2192
+ case "zsh":
2193
+ process.stdout.write(generateZsh(program2));
2194
+ break;
2195
+ case "fish":
2196
+ process.stdout.write(generateFish(program2));
2197
+ break;
2198
+ default:
2199
+ console.error(`Unknown shell: ${shell}. Supported: bash, zsh, fish`);
2200
+ process.exit(2);
2201
+ }
2202
+ });
2203
+ }
2204
+
1620
2205
  // src/commands/agent-prompt.ts
1621
2206
  var RETRYABLE_CODES = /* @__PURE__ */ new Set([
1622
2207
  ErrorCode.RATE_LIMITED,
@@ -1718,7 +2303,7 @@ function buildAgentPrompt(program2) {
1718
2303
  envVar: "ALCHEMY_ACCESS_KEY",
1719
2304
  flag: "--access-key <key>",
1720
2305
  configKey: "access-key",
1721
- commandFamilies: ["apps", "chains", "network list --configured"]
2306
+ commandFamilies: ["apps", "network list --configured"]
1722
2307
  },
1723
2308
  {
1724
2309
  method: "Webhook API key",
@@ -1850,7 +2435,7 @@ var ROOT_OPTION_GROUPS = [
1850
2435
  var ROOT_COMMAND_PILLARS = [
1851
2436
  {
1852
2437
  label: "Node",
1853
- commands: ["balance", "tx", "block", "rpc", "trace", "debug"]
2438
+ commands: ["balance", "tx", "block", "rpc", "trace", "debug", "gas", "logs"]
1854
2439
  },
1855
2440
  {
1856
2441
  label: "Data",
@@ -1862,11 +2447,11 @@ var ROOT_COMMAND_PILLARS = [
1862
2447
  },
1863
2448
  {
1864
2449
  label: "Chains",
1865
- commands: ["network", "chains", "solana"]
2450
+ commands: ["network", "solana"]
1866
2451
  },
1867
2452
  {
1868
2453
  label: "Admin",
1869
- commands: ["apps", "config", "setup", "agent-prompt", "update-check", "version", "help"]
2454
+ commands: ["apps", "config", "setup", "completions", "agent-prompt", "update-check", "version", "help"]
1870
2455
  }
1871
2456
  ];
1872
2457
  function formatCommandSignature(sub) {
@@ -1915,10 +2500,10 @@ function resetUpdateNoticeState() {
1915
2500
  }
1916
2501
  program.name("alchemy").description(
1917
2502
  "The Alchemy CLI lets you query blockchain data, call JSON-RPC methods, and manage your Alchemy configuration."
1918
- ).version("0.2.2", "-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(
2503
+ ).version("0.3.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(
1919
2504
  "-n, --network <network>",
1920
2505
  "Target network (default: eth-mainnet) (env: ALCHEMY_NETWORK)"
1921
- ).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").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) => {
2506
+ ).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) => {
1922
2507
  if (err.code === "commander.help" || err.code === "commander.helpDisplayed" || err.code === "commander.version") {
1923
2508
  process.exit(0);
1924
2509
  }
@@ -2031,6 +2616,14 @@ ${styledLine}`;
2031
2616
  }).addHelpText("beforeAll", () => isHelpInvocation ? brandedHelp() : "").addHelpText("after", () => {
2032
2617
  if (isJSONMode()) return "";
2033
2618
  return [
2619
+ "",
2620
+ `${hBrand("\u25C6")} ${hBold("Quick Start")}`,
2621
+ ` ${hDim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}`,
2622
+ ` ${hBrand("alchemy")} ${hDim("Interactive mode with guided setup")}`,
2623
+ ` ${hBrand("alchemy balance")} ${hDim("<address>")} ${hDim("Get native token balance")}`,
2624
+ ` ${hBrand("alchemy block latest")} ${hDim("Latest block summary")}`,
2625
+ ` ${hBrand("alchemy rpc eth_chainId")} ${hDim("Raw JSON-RPC call")}`,
2626
+ ` ${hBrand("alchemy config list")} ${hDim("View current configuration")}`,
2034
2627
  "",
2035
2628
  `${hBrand("\u25C6")} ${hBold("Exit Codes")}`,
2036
2629
  ` ${hDim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}`,
@@ -2086,7 +2679,7 @@ ${styledLine}`;
2086
2679
  if (isInteractiveAllowed(program)) {
2087
2680
  let latestForInteractiveStartup = null;
2088
2681
  if (shouldRunOnboarding(program, cfg)) {
2089
- const { runOnboarding } = await import("./onboarding-SJ2BRK5I.js");
2682
+ const { runOnboarding } = await import("./onboarding-MUJF5QIE.js");
2090
2683
  const latest = getAvailableUpdateOnce();
2091
2684
  const completed = await runOnboarding(program, latest);
2092
2685
  updateShownDuringInteractiveStartup = Boolean(latest);
@@ -2098,7 +2691,7 @@ ${styledLine}`;
2098
2691
  latestForInteractiveStartup = getAvailableUpdateOnce();
2099
2692
  updateShownDuringInteractiveStartup = Boolean(latestForInteractiveStartup);
2100
2693
  }
2101
- const { startREPL } = await import("./interactive-EAUEVR63.js");
2694
+ const { startREPL } = await import("./interactive-BFAXB5SN.js");
2102
2695
  program.exitOverride();
2103
2696
  program.configureOutput({
2104
2697
  writeErr: () => {
@@ -2112,9 +2705,12 @@ ${styledLine}`;
2112
2705
  registerRPC(program);
2113
2706
  registerBalance(program);
2114
2707
  registerTx(program);
2708
+ registerReceipt(program);
2115
2709
  registerBlock(program);
2116
2710
  registerTrace(program);
2117
2711
  registerDebug(program);
2712
+ registerGas(program);
2713
+ registerLogs(program);
2118
2714
  registerTokens(program);
2119
2715
  registerNFTs(program);
2120
2716
  registerTransfers(program);
@@ -2126,12 +2722,12 @@ registerBundler(program);
2126
2722
  registerGasManager(program);
2127
2723
  registerWebhooks(program);
2128
2724
  registerNetwork(program);
2129
- registerChains(program);
2130
2725
  registerApps(program);
2131
2726
  registerSetup(program);
2132
2727
  registerConfig(program);
2133
2728
  registerSolana(program);
2134
2729
  registerAgentPrompt(program);
2730
+ registerCompletions(program);
2135
2731
  registerUpdateCheck(program);
2136
2732
  registerVersion(program);
2137
2733
  program.command("help [command...]").description("display help for command").action((commandPath) => {