@alchemy/cli 0.2.1 → 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-XP5KF4W2.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-HBRTTBCY.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-SDUCDSCZ.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:
@@ -277,6 +393,9 @@ Examples:
277
393
  "block must be a number, hex, or 'latest'"
278
394
  );
279
395
  }
396
+ if (num < 0) {
397
+ throw errInvalidArgs("Block number must be non-negative.");
398
+ }
280
399
  blockParam = `0x${num.toString(16)}`;
281
400
  }
282
401
  const client = clientFromFlags(program2);
@@ -292,7 +411,7 @@ Examples:
292
411
  }
293
412
  const pairs = [];
294
413
  if (block.number) {
295
- const formatted = formatHexQuantity(block.number);
414
+ const formatted = formatHexWithRaw(block.number);
296
415
  pairs.push(["Block", bold(formatted ?? String(block.number))]);
297
416
  }
298
417
  if (block.hash) pairs.push(["Hash", String(block.hash)]);
@@ -310,11 +429,11 @@ Examples:
310
429
  pairs.push(["Gas", gasSummary]);
311
430
  } else {
312
431
  if (block.gasUsed) {
313
- const formatted = formatHexQuantity(block.gasUsed);
432
+ const formatted = formatHexWithRaw(block.gasUsed);
314
433
  pairs.push(["Gas Used", formatted ?? String(block.gasUsed)]);
315
434
  }
316
435
  if (block.gasLimit) {
317
- const formatted = formatHexQuantity(block.gasLimit);
436
+ const formatted = formatHexWithRaw(block.gasLimit);
318
437
  pairs.push(["Gas Limit", formatted ?? String(block.gasLimit)]);
319
438
  }
320
439
  }
@@ -333,8 +452,28 @@ Examples:
333
452
  }
334
453
 
335
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
+ }
336
475
  function registerNFTs(program2) {
337
- 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(
338
477
  "after",
339
478
  `
340
479
  Examples:
@@ -344,15 +483,15 @@ Examples:
344
483
  echo 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 | alchemy nfts`
345
484
  ).action(async (addressArg, opts) => {
346
485
  try {
347
- const address = addressArg ?? await readStdinArg("address");
348
- validateAddress(address);
486
+ const addressInput = addressArg ?? await readStdinArg("address");
487
+ const client = clientFromFlags(program2);
488
+ const address = await resolveAddress(addressInput, client);
349
489
  const params = {
350
490
  owner: address,
351
491
  withMetadata: "true"
352
492
  };
353
493
  if (opts.limit) params.pageSize = String(opts.limit);
354
494
  if (opts.pageKey) params.pageKey = opts.pageKey;
355
- const client = clientFromFlags(program2);
356
495
  const result = await withSpinner(
357
496
  "Fetching NFTs\u2026",
358
497
  "NFTs fetched",
@@ -366,19 +505,41 @@ Examples:
366
505
  emptyState("No NFTs found.");
367
506
  return;
368
507
  }
369
- const rows = result.ownedNfts.map((nft) => [
370
- nft.contract.name || dim("unnamed"),
371
- nft.name || `#${nft.tokenId}`,
372
- nft.contract.address
373
- ]);
374
- printTable(["Collection", "Name", "Contract"], rows);
508
+ let shown = result.ownedNfts.length;
509
+ printTable(["Collection", "Name", "Contract"], formatNFTRows(result.ownedNfts));
375
510
  if (verbose) {
376
511
  console.log("");
377
512
  printJSON(result);
378
513
  }
379
- 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) {
380
541
  console.log(`
381
- ${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.`)}`);
382
543
  }
383
544
  } catch (err) {
384
545
  exitWithError(err);
@@ -422,8 +583,34 @@ Examples:
422
583
  }
423
584
 
424
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
+ }
425
612
  function registerTokens(program2) {
426
- 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(
427
614
  "after",
428
615
  `
429
616
  Examples:
@@ -433,13 +620,13 @@ Examples:
433
620
  echo 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 | alchemy tokens`
434
621
  ).action(async (addressArg, opts) => {
435
622
  try {
436
- const address = addressArg ?? await readStdinArg("address");
437
- validateAddress(address);
623
+ const addressInput = addressArg ?? await readStdinArg("address");
624
+ const client = clientFromFlags(program2);
625
+ const address = await resolveAddress(addressInput, client);
438
626
  const params = [address];
439
627
  if (opts.pageKey) {
440
628
  params.push("erc20", { pageKey: opts.pageKey });
441
629
  }
442
- const client = clientFromFlags(program2);
443
630
  const result = await withSpinner(
444
631
  "Fetching token balances\u2026",
445
632
  "Token balances fetched",
@@ -449,36 +636,54 @@ Examples:
449
636
  printJSON(result);
450
637
  return;
451
638
  }
452
- const nonZero = result.tokenBalances.filter(
453
- (tb) => tb.tokenBalance !== "0x0" && tb.tokenBalance !== "0x0000000000000000000000000000000000000000000000000000000000000000"
454
- );
455
- if (nonZero.length === 0) {
639
+ const rows = formatTokenRows(result.tokenBalances);
640
+ if (rows.length === 0) {
456
641
  emptyState("No token balances found.");
457
642
  return;
458
643
  }
459
- const rows = nonZero.map((tb) => {
460
- let decimalBalance = dim("unparseable");
461
- try {
462
- decimalBalance = BigInt(tb.tokenBalance).toString();
463
- } catch {
464
- }
465
- return [tb.contractAddress, decimalBalance, tb.tokenBalance];
466
- });
644
+ let totalShown = rows.length;
467
645
  printKeyValueBox([
468
646
  ["Address", address],
469
647
  ["Network", client.network],
470
- ["Non-zero tokens", String(nonZero.length)]
648
+ ["Tokens", String(totalShown)]
471
649
  ]);
472
650
  printTable(["Contract", "Balance (base units)", "Raw (hex)"], rows);
473
651
  console.log(`
474
- ${dim(`Showing ${nonZero.length} of ${result.tokenBalances.length} contracts (non-zero only).`)}`);
652
+ ${dim(`${totalShown} tokens (zero balances hidden).`)}`);
475
653
  if (verbose) {
476
654
  console.log("");
477
655
  printJSON(result);
478
656
  }
479
- 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
+ }
680
+ console.log(`
681
+ ${dim(`${totalShown} tokens total (zero balances hidden).`)}`);
682
+ pageKey = nextResult.pageKey;
683
+ }
684
+ if (pageKey && !interactive) {
480
685
  console.log(`
481
- ${dim(`More results available. Use --page-key ${result.pageKey} to see the next page.`)}`);
686
+ ${dim(`More results available. Use --page-key ${pageKey} to see the next page.`)}`);
482
687
  }
483
688
  } catch (err) {
484
689
  exitWithError(err);
@@ -525,7 +730,7 @@ Examples:
525
730
  // src/commands/network.ts
526
731
  function registerNetwork(program2) {
527
732
  const cmd = program2.command("network").description("Manage networks");
528
- 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(
529
734
  "--configured",
530
735
  "List only configured app RPC networks (requires access key and app context)"
531
736
  ).option(
@@ -573,7 +778,7 @@ function registerNetwork(program2) {
573
778
  console.log(`
574
779
  Current: ${green(current)}`);
575
780
  console.log(
576
- ` ${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")}`
577
782
  );
578
783
  if (verbose) {
579
784
  console.log("");
@@ -600,43 +805,6 @@ function registerVersion(program2) {
600
805
  });
601
806
  }
602
807
 
603
- // src/commands/chains.ts
604
- function registerChains(program2) {
605
- const cmd = program2.command("chains").description("Manage Admin API chain enums");
606
- cmd.command("list").description("List available Admin API chain enums").action(async () => {
607
- try {
608
- const admin = adminClientFromFlags(program2);
609
- const chains = await withSpinner(
610
- "Fetching chains\u2026",
611
- "Chains fetched",
612
- () => admin.listChains()
613
- );
614
- if (isJSONMode()) {
615
- printJSON(chains);
616
- return;
617
- }
618
- if (chains.length === 0) {
619
- emptyState("No chain networks were returned.");
620
- return;
621
- }
622
- const rows = chains.map((c) => [
623
- c.id,
624
- c.name,
625
- c.isTestnet ? dim("yes") : "no",
626
- c.availability === "public" ? green(c.availability) : dim(c.availability),
627
- c.currency
628
- ]);
629
- printTable(["ID", "Name", "Testnet", "Availability", "Currency"], rows);
630
- if (verbose) {
631
- console.log("");
632
- printJSON(chains);
633
- }
634
- } catch (err) {
635
- exitWithError(err);
636
- }
637
- });
638
- }
639
-
640
808
  // src/commands/apps.ts
641
809
  function maskAppSecrets(app) {
642
810
  return {
@@ -655,19 +823,14 @@ async function promptPaginationAction() {
655
823
  message: "More apps available",
656
824
  options: [
657
825
  { label: "Load next page", value: "next" },
658
- { label: "Load all remaining pages", value: "all" },
826
+ { label: "Load next 5 pages", value: "next5" },
659
827
  { label: "Stop here", value: "stop" }
660
828
  ],
661
829
  initialValue: "next",
662
830
  cancelMessage: "Stopped pagination."
663
831
  });
664
- if (action === null) {
665
- return "stop";
666
- }
667
- if (action === "next" || action === "all" || action === "stop") {
668
- return action;
669
- }
670
- return "stop";
832
+ if (action === null) return "stop";
833
+ return action;
671
834
  }
672
835
  function matchesSearch(app, query) {
673
836
  const q = query.toLowerCase();
@@ -760,7 +923,7 @@ function registerApps(program2) {
760
923
  const interactivePagination = isInteractiveAllowed(program2) && !opts.all;
761
924
  if (interactivePagination) {
762
925
  let page = result;
763
- let autoFetchRemaining = false;
926
+ let batchRemaining = 0;
764
927
  let pagesFetched = 0;
765
928
  let appsFetched = 0;
766
929
  while (true) {
@@ -777,7 +940,7 @@ function registerApps(program2) {
777
940
  printFetchSummary(appsFetched, pagesFetched);
778
941
  return;
779
942
  }
780
- if (!autoFetchRemaining) {
943
+ if (batchRemaining <= 0) {
781
944
  printFetchSummary(appsFetched, pagesFetched, { suffix: "so far" });
782
945
  const action = await promptPaginationAction();
783
946
  if (action === "stop") {
@@ -786,9 +949,11 @@ function registerApps(program2) {
786
949
  printFetchSummary(appsFetched, pagesFetched);
787
950
  return;
788
951
  }
789
- if (action === "all") {
790
- autoFetchRemaining = true;
952
+ if (action === "next5") {
953
+ batchRemaining = 4;
791
954
  }
955
+ } else {
956
+ batchRemaining -= 1;
792
957
  }
793
958
  page = await withSpinner(
794
959
  "Fetching next page\u2026",
@@ -1003,6 +1168,45 @@ function registerApps(program2) {
1003
1168
  exitWithError(err);
1004
1169
  }
1005
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
+ });
1006
1210
  }
1007
1211
 
1008
1212
  // src/commands/setup.ts
@@ -1063,7 +1267,7 @@ function normalizeTraceMethod(method) {
1063
1267
  return `trace_${normalized}`;
1064
1268
  }
1065
1269
  function registerTrace(program2) {
1066
- 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) => {
1067
1271
  try {
1068
1272
  const client = clientFromFlags(program2);
1069
1273
  const rpcMethod = normalizeTraceMethod(method);
@@ -1085,7 +1289,7 @@ function normalizeDebugMethod(method) {
1085
1289
  return `debug_${method.replace(/-/g, "_")}`;
1086
1290
  }
1087
1291
  function registerDebug(program2) {
1088
- 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) => {
1089
1293
  try {
1090
1294
  const client = clientFromFlags(program2);
1091
1295
  const rpcMethod = normalizeDebugMethod(method);
@@ -1102,29 +1306,72 @@ function registerDebug(program2) {
1102
1306
  }
1103
1307
 
1104
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
+ }
1105
1334
  function registerTransfers(program2) {
1106
- 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) => {
1107
1351
  try {
1108
1352
  const client = clientFromFlags(program2);
1109
- const address = addressArg ?? void 0;
1110
- if (address) validateAddress(address);
1111
- if (opts.fromAddress) validateAddress(opts.fromAddress);
1112
- if (opts.toAddress) validateAddress(opts.toAddress);
1113
- 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 = {
1114
1357
  fromBlock: opts.fromBlock ?? "0x0",
1115
1358
  toBlock: opts.toBlock ?? "latest",
1116
- withMetadata: true
1359
+ withMetadata: true,
1360
+ category: opts.category ? splitCommaList(opts.category) : ["external", "internal", "erc20", "erc721", "erc1155", "specialnft"]
1117
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 };
1118
1369
  if (address && !opts.fromAddress && !opts.toAddress) {
1119
1370
  filter.fromAddress = address;
1120
- filter.toAddress = address;
1121
1371
  } else {
1122
1372
  if (opts.fromAddress) filter.fromAddress = opts.fromAddress;
1123
1373
  if (opts.toAddress) filter.toAddress = opts.toAddress;
1124
1374
  }
1125
- if (opts.category) filter.category = splitCommaList(opts.category);
1126
- if (opts.maxCount) filter.maxCount = opts.maxCount.startsWith("0x") ? opts.maxCount : `0x${Number.parseInt(opts.maxCount, 10).toString(16)}`;
1127
- if (opts.pageKey) filter.pageKey = opts.pageKey;
1128
1375
  const result = await withSpinner(
1129
1376
  "Fetching transfers\u2026",
1130
1377
  "Transfers fetched",
@@ -1132,8 +1379,43 @@ function registerTransfers(program2) {
1132
1379
  );
1133
1380
  if (isJSONMode()) {
1134
1381
  printJSON(result);
1135
- } else {
1136
- 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.`)}`);
1137
1419
  }
1138
1420
  } catch (err) {
1139
1421
  exitWithError(err);
@@ -1587,7 +1869,7 @@ function registerSolana(program2) {
1587
1869
  const cmd = program2.command("solana").description("Solana RPC and DAS wrappers");
1588
1870
  cmd.command("rpc <method> [params...]").description("Call a Solana JSON-RPC method").action(async (method, params) => {
1589
1871
  try {
1590
- const client = clientFromFlags(program2);
1872
+ const client = clientFromFlags(program2, { defaultNetwork: "solana-mainnet" });
1591
1873
  const result = await withSpinner(
1592
1874
  `Calling ${method}\u2026`,
1593
1875
  `Called ${method}`,
@@ -1598,14 +1880,15 @@ function registerSolana(program2) {
1598
1880
  exitWithError(err);
1599
1881
  }
1600
1882
  });
1601
- const das = cmd.command("das").description("Solana DAS (Digital Asset Standard) wrappers");
1602
- 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) => {
1603
1884
  try {
1604
- 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;
1605
1888
  const result = await withSpinner(
1606
1889
  `Calling ${method}\u2026`,
1607
1890
  `Called ${method}`,
1608
- () => client.call(method, parseCLIParams(params))
1891
+ () => client.call(method, rpcParams)
1609
1892
  );
1610
1893
  printSyntaxJSON(result);
1611
1894
  } catch (err) {
@@ -1614,6 +1897,311 @@ function registerSolana(program2) {
1614
1897
  });
1615
1898
  }
1616
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
+
1617
2205
  // src/commands/agent-prompt.ts
1618
2206
  var RETRYABLE_CODES = /* @__PURE__ */ new Set([
1619
2207
  ErrorCode.RATE_LIMITED,
@@ -1715,7 +2303,7 @@ function buildAgentPrompt(program2) {
1715
2303
  envVar: "ALCHEMY_ACCESS_KEY",
1716
2304
  flag: "--access-key <key>",
1717
2305
  configKey: "access-key",
1718
- commandFamilies: ["apps", "chains", "network list --configured"]
2306
+ commandFamilies: ["apps", "network list --configured"]
1719
2307
  },
1720
2308
  {
1721
2309
  method: "Webhook API key",
@@ -1847,7 +2435,7 @@ var ROOT_OPTION_GROUPS = [
1847
2435
  var ROOT_COMMAND_PILLARS = [
1848
2436
  {
1849
2437
  label: "Node",
1850
- commands: ["balance", "tx", "block", "rpc", "trace", "debug"]
2438
+ commands: ["balance", "tx", "block", "rpc", "trace", "debug", "gas", "logs"]
1851
2439
  },
1852
2440
  {
1853
2441
  label: "Data",
@@ -1859,11 +2447,11 @@ var ROOT_COMMAND_PILLARS = [
1859
2447
  },
1860
2448
  {
1861
2449
  label: "Chains",
1862
- commands: ["network", "chains", "solana"]
2450
+ commands: ["network", "solana"]
1863
2451
  },
1864
2452
  {
1865
2453
  label: "Admin",
1866
- commands: ["apps", "config", "setup", "agent-prompt", "update-check", "version", "help"]
2454
+ commands: ["apps", "config", "setup", "completions", "agent-prompt", "update-check", "version", "help"]
1867
2455
  }
1868
2456
  ];
1869
2457
  function formatCommandSignature(sub) {
@@ -1912,10 +2500,10 @@ function resetUpdateNoticeState() {
1912
2500
  }
1913
2501
  program.name("alchemy").description(
1914
2502
  "The Alchemy CLI lets you query blockchain data, call JSON-RPC methods, and manage your Alchemy configuration."
1915
- ).version("0.2.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(
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(
1916
2504
  "-n, --network <network>",
1917
2505
  "Target network (default: eth-mainnet) (env: ALCHEMY_NETWORK)"
1918
- ).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).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) => {
1919
2507
  if (err.code === "commander.help" || err.code === "commander.helpDisplayed" || err.code === "commander.version") {
1920
2508
  process.exit(0);
1921
2509
  }
@@ -2028,6 +2616,14 @@ ${styledLine}`;
2028
2616
  }).addHelpText("beforeAll", () => isHelpInvocation ? brandedHelp() : "").addHelpText("after", () => {
2029
2617
  if (isJSONMode()) return "";
2030
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")}`,
2031
2627
  "",
2032
2628
  `${hBrand("\u25C6")} ${hBold("Exit Codes")}`,
2033
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")}`,
@@ -2067,7 +2663,15 @@ ${styledLine}`;
2067
2663
  }
2068
2664
  }
2069
2665
  resetUpdateNoticeState();
2070
- }).action(async () => {
2666
+ }).action(async (_opts, cmd) => {
2667
+ const excessArgs = cmd.args;
2668
+ if (excessArgs.length > 0) {
2669
+ exitWithError(
2670
+ errInvalidArgs(
2671
+ `Unknown command '${excessArgs[0]}'. Run 'alchemy help' for available commands.`
2672
+ )
2673
+ );
2674
+ }
2071
2675
  const cfg = load();
2072
2676
  if (!isSetupComplete(cfg) && !isInteractiveAllowed(program)) {
2073
2677
  throw errSetupRequired(getSetupStatus(cfg));
@@ -2075,7 +2679,7 @@ ${styledLine}`;
2075
2679
  if (isInteractiveAllowed(program)) {
2076
2680
  let latestForInteractiveStartup = null;
2077
2681
  if (shouldRunOnboarding(program, cfg)) {
2078
- const { runOnboarding } = await import("./onboarding-YA32OJOT.js");
2682
+ const { runOnboarding } = await import("./onboarding-MUJF5QIE.js");
2079
2683
  const latest = getAvailableUpdateOnce();
2080
2684
  const completed = await runOnboarding(program, latest);
2081
2685
  updateShownDuringInteractiveStartup = Boolean(latest);
@@ -2087,7 +2691,7 @@ ${styledLine}`;
2087
2691
  latestForInteractiveStartup = getAvailableUpdateOnce();
2088
2692
  updateShownDuringInteractiveStartup = Boolean(latestForInteractiveStartup);
2089
2693
  }
2090
- const { startREPL } = await import("./interactive-L7N4HI7K.js");
2694
+ const { startREPL } = await import("./interactive-BFAXB5SN.js");
2091
2695
  program.exitOverride();
2092
2696
  program.configureOutput({
2093
2697
  writeErr: () => {
@@ -2101,9 +2705,12 @@ ${styledLine}`;
2101
2705
  registerRPC(program);
2102
2706
  registerBalance(program);
2103
2707
  registerTx(program);
2708
+ registerReceipt(program);
2104
2709
  registerBlock(program);
2105
2710
  registerTrace(program);
2106
2711
  registerDebug(program);
2712
+ registerGas(program);
2713
+ registerLogs(program);
2107
2714
  registerTokens(program);
2108
2715
  registerNFTs(program);
2109
2716
  registerTransfers(program);
@@ -2115,12 +2722,12 @@ registerBundler(program);
2115
2722
  registerGasManager(program);
2116
2723
  registerWebhooks(program);
2117
2724
  registerNetwork(program);
2118
- registerChains(program);
2119
2725
  registerApps(program);
2120
2726
  registerSetup(program);
2121
2727
  registerConfig(program);
2122
2728
  registerSolana(program);
2123
2729
  registerAgentPrompt(program);
2730
+ registerCompletions(program);
2124
2731
  registerUpdateCheck(program);
2125
2732
  registerVersion(program);
2126
2733
  program.command("help [command...]").description("display help for command").action((commandPath) => {