@alchemy/cli 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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-J6RZM4CJ.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-QDDJ3OYO.js";
24
26
  import {
25
27
  EXIT_CODES,
26
28
  ErrorCode,
@@ -57,22 +59,25 @@ import {
57
59
  printSyntaxJSON,
58
60
  printTable,
59
61
  printUpdateNotice,
62
+ promptConfirm,
60
63
  promptSelect,
61
64
  quiet,
65
+ red,
62
66
  setFlags,
67
+ setNoColor,
63
68
  successBadge,
64
69
  timeAgo,
65
70
  verbose,
66
71
  weiToEth,
67
72
  withSpinner
68
- } from "./chunk-2WI4JODY.js";
73
+ } from "./chunk-6XTLILDF.js";
69
74
 
70
75
  // src/index.ts
71
76
  import { Command, Help } from "commander";
72
77
 
73
78
  // src/commands/rpc.ts
74
79
  function registerRPC(program2) {
75
- program2.command("rpc <method> [params...]").description("Make a raw JSON-RPC call").addHelpText(
80
+ 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
81
  "after",
77
82
  `
78
83
  Examples:
@@ -104,36 +109,50 @@ Examples:
104
109
 
105
110
  // src/commands/balance.ts
106
111
  function registerBalance(program2) {
107
- program2.command("balance [address]").alias("bal").description("Get the ETH balance of an address").addHelpText(
112
+ 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
113
  "after",
109
114
  `
110
115
  Examples:
111
116
  alchemy balance 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
112
117
  alchemy balance 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 -n polygon-mainnet
113
- echo 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 | alchemy balance`
114
- ).action(async (addressArg) => {
118
+ echo 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 | alchemy balance
119
+ alchemy balance 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --block 15537393
120
+ alchemy balance vitalik.eth`
121
+ ).option("--block <block>", "Block number, hex, or tag (default: latest)").action(async (addressArg, opts) => {
115
122
  try {
116
- const address = addressArg ?? await readStdinArg("address");
117
- validateAddress(address);
123
+ const addressInput = addressArg ?? await readStdinArg("address");
118
124
  const client = clientFromFlags(program2);
125
+ const address = await resolveAddress(addressInput, client);
126
+ let blockParam = opts?.block ?? "latest";
127
+ if (blockParam !== "latest" && blockParam !== "earliest" && blockParam !== "pending") {
128
+ if (!blockParam.startsWith("0x")) {
129
+ const num = parseInt(blockParam, 10);
130
+ if (isNaN(num) || num < 0) {
131
+ throw errInvalidArgs("Block must be a number, hex, or tag (latest, earliest, pending).");
132
+ }
133
+ blockParam = `0x${num.toString(16)}`;
134
+ }
135
+ }
119
136
  const result = await withSpinner(
120
137
  "Fetching balance\u2026",
121
138
  "Balance fetched",
122
- () => client.call("eth_getBalance", [address, "latest"])
139
+ () => client.call("eth_getBalance", [address, blockParam])
123
140
  );
124
141
  const wei = BigInt(result);
125
142
  const network = resolveNetwork(program2);
143
+ const symbol = nativeTokenSymbol(network);
126
144
  if (isJSONMode()) {
127
145
  printJSON({
128
146
  address,
129
147
  wei: wei.toString(),
130
- eth: weiToEth(wei),
148
+ balance: weiToEth(wei),
149
+ symbol,
131
150
  network
132
151
  });
133
152
  } else {
134
153
  printKeyValueBox([
135
154
  ["Address", address],
136
- ["Balance", green(weiToEth(wei) + " ETH")],
155
+ ["Balance", green(`${weiToEth(wei)} ${symbol}`)],
137
156
  ["Network", network]
138
157
  ]);
139
158
  if (verbose) {
@@ -151,9 +170,76 @@ Examples:
151
170
  });
152
171
  }
153
172
 
173
+ // src/lib/block-format.ts
174
+ function parseHexQuantity(value) {
175
+ if (typeof value !== "string" || !/^0x[0-9a-f]+$/i.test(value)) {
176
+ return void 0;
177
+ }
178
+ try {
179
+ return BigInt(value);
180
+ } catch {
181
+ return void 0;
182
+ }
183
+ }
184
+ function formatWithCommas(value) {
185
+ return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
186
+ }
187
+ function formatHexQuantity(value) {
188
+ const parsed = parseHexQuantity(value);
189
+ if (parsed === void 0) return void 0;
190
+ return formatWithCommas(parsed);
191
+ }
192
+ function formatBlockTimestamp(value) {
193
+ if (typeof value !== "string") return void 0;
194
+ const parsed = parseHexQuantity(value);
195
+ if (parsed === void 0) return void 0;
196
+ const millis = Number(parsed) * 1e3;
197
+ if (!Number.isFinite(millis)) return void 0;
198
+ const d = new Date(millis);
199
+ if (Number.isNaN(d.getTime())) return void 0;
200
+ const iso = d.toISOString().replace(".000Z", "Z");
201
+ return `${iso} ${dim("(" + timeAgo(value) + ")")}`;
202
+ }
203
+ function formatHexWithRaw(value) {
204
+ if (typeof value !== "string") return void 0;
205
+ const parsed = parseHexQuantity(value);
206
+ if (parsed === void 0) return void 0;
207
+ return `${formatWithCommas(parsed)} ${dim(`(${value})`)}`;
208
+ }
209
+ function formatWeiWithRaw(value, symbol = "ETH") {
210
+ if (typeof value !== "string") return void 0;
211
+ const parsed = parseHexQuantity(value);
212
+ if (parsed === void 0) return void 0;
213
+ return `${weiToEth(parsed)} ${symbol} ${dim(`(${value})`)}`;
214
+ }
215
+ function formatGwei(gwei) {
216
+ const fixed = gwei.toFixed(9);
217
+ return fixed.replace(/\.?0+$/, "") || "0";
218
+ }
219
+ function formatGweiWithRaw(value) {
220
+ if (typeof value !== "string") return void 0;
221
+ const parsed = parseHexQuantity(value);
222
+ if (parsed === void 0) return void 0;
223
+ const gwei = Number(parsed) / 1e9;
224
+ return `${formatGwei(gwei)} gwei ${dim(`(${value})`)}`;
225
+ }
226
+ function formatGasSummary(gasUsed, gasLimit, options) {
227
+ const used = parseHexQuantity(gasUsed);
228
+ const limit = parseHexQuantity(gasLimit);
229
+ if (used === void 0 || limit === void 0) return void 0;
230
+ const usedFormatted = formatWithCommas(used);
231
+ const limitFormatted = formatWithCommas(limit);
232
+ if (limit === 0n) return `${usedFormatted} / ${limitFormatted}`;
233
+ const bps = used * 10000n / limit;
234
+ const percent = Number(bps) / 100;
235
+ const percentText = `${percent.toFixed(2)}%`;
236
+ const percentDisplay = options?.colored ? dim(percentText) : percentText;
237
+ return `${usedFormatted} / ${limitFormatted} (${percentDisplay})`;
238
+ }
239
+
154
240
  // src/commands/tx.ts
155
241
  function registerTx(program2) {
156
- program2.command("tx [hash]").description("Get transaction details by hash").addHelpText(
242
+ program2.command("tx").argument("[hash]", "Transaction hash (0x...) or pipe via stdin").description("Get transaction details by hash").addHelpText(
157
243
  "after",
158
244
  `
159
245
  Examples:
@@ -178,23 +264,34 @@ Examples:
178
264
  printJSON({ transaction: tx, receipt });
179
265
  return;
180
266
  }
267
+ const network = resolveNetwork(program2);
268
+ const symbol = nativeTokenSymbol(network);
181
269
  const pairs = [["Hash", hash]];
182
270
  if (tx.from) pairs.push(["From", String(tx.from)]);
183
271
  if (tx.to) pairs.push(["To", String(tx.to)]);
184
272
  if (tx.value) {
185
- const wei = BigInt(tx.value);
186
- pairs.push(["Value", green(weiToEth(wei) + " ETH")]);
273
+ const formatted = formatWeiWithRaw(tx.value, symbol);
274
+ pairs.push(["Value", formatted ? green(formatted) : String(tx.value)]);
275
+ }
276
+ if (tx.blockNumber) {
277
+ const formatted = formatHexWithRaw(tx.blockNumber);
278
+ pairs.push(["Block", formatted ?? String(tx.blockNumber)]);
187
279
  }
188
- if (tx.blockNumber) pairs.push(["Block", String(tx.blockNumber)]);
189
280
  if (receipt) {
190
281
  if (receipt.status === "0x1") {
191
282
  pairs.push(["Status", `${successBadge()} Success`]);
192
283
  } else if (receipt.status) {
193
284
  pairs.push(["Status", `${failBadge()} Failed`]);
194
285
  }
195
- if (receipt.gasUsed) pairs.push(["Gas Used", String(receipt.gasUsed)]);
286
+ if (receipt.gasUsed) {
287
+ const formatted = formatHexWithRaw(receipt.gasUsed);
288
+ pairs.push(["Gas Used", formatted ?? String(receipt.gasUsed)]);
289
+ }
290
+ if (receipt.effectiveGasPrice) {
291
+ const formatted = formatGweiWithRaw(receipt.effectiveGasPrice);
292
+ pairs.push(["Gas Price", formatted ?? String(receipt.effectiveGasPrice)]);
293
+ }
196
294
  }
197
- const network = resolveNetwork(program2);
198
295
  const explorerURL = etherscanTxURL(hash, network);
199
296
  if (explorerURL) {
200
297
  pairs.push(["Explorer", explorerURL]);
@@ -210,53 +307,74 @@ Examples:
210
307
  });
211
308
  }
212
309
 
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})`;
310
+ // src/commands/receipt.ts
311
+ function registerReceipt(program2) {
312
+ program2.command("receipt").argument("[hash]", "Transaction hash (0x...) or pipe via stdin").description("Get transaction receipt (status, gas used, logs)").addHelpText(
313
+ "after",
314
+ `
315
+ Examples:
316
+ alchemy receipt 0xabc123...
317
+ echo 0xabc123... | alchemy receipt`
318
+ ).action(async (hashArg) => {
319
+ try {
320
+ const hash = hashArg ?? await readStdinArg("hash");
321
+ validateTxHash(hash);
322
+ const client = clientFromFlags(program2);
323
+ const receipt = await withSpinner(
324
+ "Fetching receipt\u2026",
325
+ "Receipt fetched",
326
+ () => client.call("eth_getTransactionReceipt", [hash])
327
+ );
328
+ if (!receipt) throw errNotFound(`receipt for ${hash}`);
329
+ if (isJSONMode()) {
330
+ printJSON(receipt);
331
+ return;
332
+ }
333
+ const pairs = [["Hash", hash]];
334
+ if (receipt.status === "0x1") {
335
+ pairs.push(["Status", `${successBadge()} ${green("Success")}`]);
336
+ } else if (receipt.status) {
337
+ pairs.push(["Status", `${failBadge()} ${red("Failed")}`]);
338
+ }
339
+ if (receipt.from) pairs.push(["From", String(receipt.from)]);
340
+ if (receipt.to) pairs.push(["To", String(receipt.to)]);
341
+ if (receipt.contractAddress) {
342
+ pairs.push(["Contract Created", String(receipt.contractAddress)]);
343
+ }
344
+ if (receipt.blockNumber) {
345
+ const hex = String(receipt.blockNumber);
346
+ const decoded = formatHexQuantity(hex);
347
+ pairs.push(["Block", decoded ? `${decoded} ${dim(`(${hex})`)}` : hex]);
348
+ }
349
+ if (receipt.gasUsed) {
350
+ const hex = String(receipt.gasUsed);
351
+ const decoded = formatHexQuantity(hex);
352
+ pairs.push(["Gas Used", decoded ? `${decoded} ${dim(`(${hex})`)}` : hex]);
353
+ }
354
+ if (receipt.effectiveGasPrice) {
355
+ const hex = String(receipt.effectiveGasPrice);
356
+ const wei = BigInt(hex);
357
+ const gwei = Number(wei) / 1e9;
358
+ pairs.push(["Gas Price", `${formatGwei(gwei)} gwei ${dim(`(${hex})`)}`]);
359
+ }
360
+ if (Array.isArray(receipt.logs)) {
361
+ pairs.push(["Logs", `${receipt.logs.length} event${receipt.logs.length === 1 ? "" : "s"}`]);
362
+ }
363
+ const network = resolveNetwork(program2);
364
+ const explorerURL = etherscanTxURL(hash, network);
365
+ if (explorerURL) {
366
+ pairs.push(["Explorer", explorerURL]);
367
+ }
368
+ printKeyValueBox(pairs);
369
+ } catch (err) {
370
+ exitWithError(err);
371
+ }
372
+ });
255
373
  }
256
374
 
257
375
  // src/commands/block.ts
258
376
  function registerBlock(program2) {
259
- program2.command("block <number>").description("Get block details by number").addHelpText(
377
+ program2.command("block").argument("<number>", "Block number, hex (0x...), or tag (latest, earliest, pending)").description("Get block details by number").addHelpText(
260
378
  "after",
261
379
  `
262
380
  Examples:
@@ -295,7 +413,7 @@ Examples:
295
413
  }
296
414
  const pairs = [];
297
415
  if (block.number) {
298
- const formatted = formatHexQuantity(block.number);
416
+ const formatted = formatHexWithRaw(block.number);
299
417
  pairs.push(["Block", bold(formatted ?? String(block.number))]);
300
418
  }
301
419
  if (block.hash) pairs.push(["Hash", String(block.hash)]);
@@ -313,11 +431,11 @@ Examples:
313
431
  pairs.push(["Gas", gasSummary]);
314
432
  } else {
315
433
  if (block.gasUsed) {
316
- const formatted = formatHexQuantity(block.gasUsed);
434
+ const formatted = formatHexWithRaw(block.gasUsed);
317
435
  pairs.push(["Gas Used", formatted ?? String(block.gasUsed)]);
318
436
  }
319
437
  if (block.gasLimit) {
320
- const formatted = formatHexQuantity(block.gasLimit);
438
+ const formatted = formatHexWithRaw(block.gasLimit);
321
439
  pairs.push(["Gas Limit", formatted ?? String(block.gasLimit)]);
322
440
  }
323
441
  }
@@ -336,8 +454,28 @@ Examples:
336
454
  }
337
455
 
338
456
  // src/commands/nfts.ts
457
+ async function promptNFTPagination(shown, total) {
458
+ const action = await promptSelect({
459
+ message: `Showing ${shown} of ${total} NFTs`,
460
+ options: [
461
+ { label: "Load next page", value: "next" },
462
+ { label: "Stop here", value: "stop" }
463
+ ],
464
+ initialValue: "next",
465
+ cancelMessage: "Stopped pagination."
466
+ });
467
+ if (action === null) return "stop";
468
+ return action;
469
+ }
470
+ function formatNFTRows(nfts) {
471
+ return nfts.map((nft) => [
472
+ nft.contract.name || dim("unnamed"),
473
+ nft.name || `#${nft.tokenId}`,
474
+ nft.contract.address
475
+ ]);
476
+ }
339
477
  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(
478
+ 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
479
  "after",
342
480
  `
343
481
  Examples:
@@ -347,15 +485,15 @@ Examples:
347
485
  echo 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 | alchemy nfts`
348
486
  ).action(async (addressArg, opts) => {
349
487
  try {
350
- const address = addressArg ?? await readStdinArg("address");
351
- validateAddress(address);
488
+ const addressInput = addressArg ?? await readStdinArg("address");
489
+ const client = clientFromFlags(program2);
490
+ const address = await resolveAddress(addressInput, client);
352
491
  const params = {
353
492
  owner: address,
354
493
  withMetadata: "true"
355
494
  };
356
495
  if (opts.limit) params.pageSize = String(opts.limit);
357
496
  if (opts.pageKey) params.pageKey = opts.pageKey;
358
- const client = clientFromFlags(program2);
359
497
  const result = await withSpinner(
360
498
  "Fetching NFTs\u2026",
361
499
  "NFTs fetched",
@@ -369,19 +507,41 @@ Examples:
369
507
  emptyState("No NFTs found.");
370
508
  return;
371
509
  }
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);
510
+ let shown = result.ownedNfts.length;
511
+ printTable(["Collection", "Name", "Contract"], formatNFTRows(result.ownedNfts));
378
512
  if (verbose) {
379
513
  console.log("");
380
514
  printJSON(result);
381
515
  }
382
- if (result.pageKey) {
516
+ const interactive = isInteractiveAllowed(program2);
517
+ let pageKey = result.pageKey;
518
+ while (pageKey && interactive) {
519
+ const action = await promptNFTPagination(shown, result.totalCount);
520
+ if (action === "stop") {
521
+ console.log(`
522
+ ${dim(`Next page key: ${pageKey}`)}`);
523
+ break;
524
+ }
525
+ const nextParams = {
526
+ owner: address,
527
+ withMetadata: "true",
528
+ pageKey
529
+ };
530
+ if (opts.limit) nextParams.pageSize = String(opts.limit);
531
+ const nextResult = await withSpinner(
532
+ "Fetching next page\u2026",
533
+ "Page fetched",
534
+ () => client.callEnhanced("getNFTsForOwner", nextParams)
535
+ );
536
+ if (nextResult.ownedNfts.length > 0) {
537
+ printTable(["Collection", "Name", "Contract"], formatNFTRows(nextResult.ownedNfts));
538
+ shown += nextResult.ownedNfts.length;
539
+ }
540
+ pageKey = nextResult.pageKey;
541
+ }
542
+ if (pageKey && !interactive) {
383
543
  console.log(`
384
- ${dim(`More results available. Use --page-key ${result.pageKey} to see the next page.`)}`);
544
+ ${dim(`More results available. Use --page-key ${pageKey} to see the next page.`)}`);
385
545
  }
386
546
  } catch (err) {
387
547
  exitWithError(err);
@@ -425,8 +585,34 @@ Examples:
425
585
  }
426
586
 
427
587
  // src/commands/tokens.ts
588
+ async function promptTokensPagination() {
589
+ const action = await promptSelect({
590
+ message: "More token balances available",
591
+ options: [
592
+ { label: "Load next page", value: "next" },
593
+ { label: "Stop here", value: "stop" }
594
+ ],
595
+ initialValue: "next",
596
+ cancelMessage: "Stopped pagination."
597
+ });
598
+ if (action === null) return "stop";
599
+ return action;
600
+ }
601
+ function formatTokenRows(balances) {
602
+ const nonZero = balances.filter(
603
+ (tb) => tb.tokenBalance !== "0x0" && tb.tokenBalance !== "0x0000000000000000000000000000000000000000000000000000000000000000"
604
+ );
605
+ return nonZero.map((tb) => {
606
+ let decimalBalance = dim("unparseable");
607
+ try {
608
+ decimalBalance = BigInt(tb.tokenBalance).toString();
609
+ } catch {
610
+ }
611
+ return [tb.contractAddress, decimalBalance, tb.tokenBalance];
612
+ });
613
+ }
428
614
  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(
615
+ const cmd = program2.command("tokens").description("Token API wrappers").argument("[address]", "Wallet address or ENS name (default action: list balances)").option("--page-key <key>", "Pagination key from a previous response").addHelpText(
430
616
  "after",
431
617
  `
432
618
  Examples:
@@ -436,13 +622,13 @@ Examples:
436
622
  echo 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 | alchemy tokens`
437
623
  ).action(async (addressArg, opts) => {
438
624
  try {
439
- const address = addressArg ?? await readStdinArg("address");
440
- validateAddress(address);
625
+ const addressInput = addressArg ?? await readStdinArg("address");
626
+ const client = clientFromFlags(program2);
627
+ const address = await resolveAddress(addressInput, client);
441
628
  const params = [address];
442
629
  if (opts.pageKey) {
443
630
  params.push("erc20", { pageKey: opts.pageKey });
444
631
  }
445
- const client = clientFromFlags(program2);
446
632
  const result = await withSpinner(
447
633
  "Fetching token balances\u2026",
448
634
  "Token balances fetched",
@@ -452,36 +638,54 @@ Examples:
452
638
  printJSON(result);
453
639
  return;
454
640
  }
455
- const nonZero = result.tokenBalances.filter(
456
- (tb) => tb.tokenBalance !== "0x0" && tb.tokenBalance !== "0x0000000000000000000000000000000000000000000000000000000000000000"
457
- );
458
- if (nonZero.length === 0) {
641
+ const rows = formatTokenRows(result.tokenBalances);
642
+ if (rows.length === 0) {
459
643
  emptyState("No token balances found.");
460
644
  return;
461
645
  }
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
- });
646
+ let totalShown = rows.length;
470
647
  printKeyValueBox([
471
648
  ["Address", address],
472
649
  ["Network", client.network],
473
- ["Non-zero tokens", String(nonZero.length)]
650
+ ["Tokens", String(totalShown)]
474
651
  ]);
475
652
  printTable(["Contract", "Balance (base units)", "Raw (hex)"], rows);
476
653
  console.log(`
477
- ${dim(`Showing ${nonZero.length} of ${result.tokenBalances.length} contracts (non-zero only).`)}`);
654
+ ${dim(`${totalShown} tokens (zero balances hidden).`)}`);
478
655
  if (verbose) {
479
656
  console.log("");
480
657
  printJSON(result);
481
658
  }
482
- if (result.pageKey) {
659
+ const interactive = isInteractiveAllowed(program2);
660
+ let pageKey = result.pageKey;
661
+ while (pageKey && interactive) {
662
+ const action = await promptTokensPagination();
663
+ if (action === "stop") {
664
+ console.log(`
665
+ ${dim(`Next page key: ${pageKey}`)}`);
666
+ break;
667
+ }
668
+ const nextResult = await withSpinner(
669
+ "Fetching next page\u2026",
670
+ "Page fetched",
671
+ () => client.call("alchemy_getTokenBalances", [address, "erc20", { pageKey }])
672
+ );
673
+ if (isJSONMode()) {
674
+ printJSON(nextResult);
675
+ return;
676
+ }
677
+ const nextRows = formatTokenRows(nextResult.tokenBalances);
678
+ totalShown += nextRows.length;
679
+ if (nextRows.length > 0) {
680
+ printTable(["Contract", "Balance (base units)", "Raw (hex)"], nextRows);
681
+ }
682
+ console.log(`
683
+ ${dim(`${totalShown} tokens total (zero balances hidden).`)}`);
684
+ pageKey = nextResult.pageKey;
685
+ }
686
+ if (pageKey && !interactive) {
483
687
  console.log(`
484
- ${dim(`More results available. Use --page-key ${result.pageKey} to see the next page.`)}`);
688
+ ${dim(`More results available. Use --page-key ${pageKey} to see the next page.`)}`);
485
689
  }
486
690
  } catch (err) {
487
691
  exitWithError(err);
@@ -528,7 +732,7 @@ Examples:
528
732
  // src/commands/network.ts
529
733
  function registerNetwork(program2) {
530
734
  const cmd = program2.command("network").description("Manage networks");
531
- cmd.command("list").description("List supported RPC network slugs").option(
735
+ cmd.command("list").description("List RPC network IDs for use with --network (e.g. eth-mainnet)").option(
532
736
  "--configured",
533
737
  "List only configured app RPC networks (requires access key and app context)"
534
738
  ).option(
@@ -576,7 +780,7 @@ function registerNetwork(program2) {
576
780
  console.log(`
577
781
  Current: ${green(current)}`);
578
782
  console.log(
579
- ` ${dim("Need Admin API chain enums instead? Run: alchemy chains list")}`
783
+ ` ${dim("Need Admin API chain identifiers (e.g. ETH_MAINNET)? See: apps chains")}`
580
784
  );
581
785
  if (verbose) {
582
786
  console.log("");
@@ -603,43 +807,6 @@ function registerVersion(program2) {
603
807
  });
604
808
  }
605
809
 
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
810
  // src/commands/apps.ts
644
811
  function maskAppSecrets(app) {
645
812
  return {
@@ -658,19 +825,14 @@ async function promptPaginationAction() {
658
825
  message: "More apps available",
659
826
  options: [
660
827
  { label: "Load next page", value: "next" },
661
- { label: "Load all remaining pages", value: "all" },
828
+ { label: "Load next 5 pages", value: "next5" },
662
829
  { label: "Stop here", value: "stop" }
663
830
  ],
664
831
  initialValue: "next",
665
832
  cancelMessage: "Stopped pagination."
666
833
  });
667
- if (action === null) {
668
- return "stop";
669
- }
670
- if (action === "next" || action === "all" || action === "stop") {
671
- return action;
672
- }
673
- return "stop";
834
+ if (action === null) return "stop";
835
+ return action;
674
836
  }
675
837
  function matchesSearch(app, query) {
676
838
  const q = query.toLowerCase();
@@ -763,7 +925,7 @@ function registerApps(program2) {
763
925
  const interactivePagination = isInteractiveAllowed(program2) && !opts.all;
764
926
  if (interactivePagination) {
765
927
  let page = result;
766
- let autoFetchRemaining = false;
928
+ let batchRemaining = 0;
767
929
  let pagesFetched = 0;
768
930
  let appsFetched = 0;
769
931
  while (true) {
@@ -780,7 +942,7 @@ function registerApps(program2) {
780
942
  printFetchSummary(appsFetched, pagesFetched);
781
943
  return;
782
944
  }
783
- if (!autoFetchRemaining) {
945
+ if (batchRemaining <= 0) {
784
946
  printFetchSummary(appsFetched, pagesFetched, { suffix: "so far" });
785
947
  const action = await promptPaginationAction();
786
948
  if (action === "stop") {
@@ -789,9 +951,11 @@ function registerApps(program2) {
789
951
  printFetchSummary(appsFetched, pagesFetched);
790
952
  return;
791
953
  }
792
- if (action === "all") {
793
- autoFetchRemaining = true;
954
+ if (action === "next5") {
955
+ batchRemaining = 4;
794
956
  }
957
+ } else {
958
+ batchRemaining -= 1;
795
959
  }
796
960
  page = await withSpinner(
797
961
  "Fetching next page\u2026",
@@ -875,9 +1039,21 @@ function registerApps(program2) {
875
1039
  exitWithError(err);
876
1040
  }
877
1041
  });
878
- cmd.command("delete <id>").description("Delete an app").option("--dry-run", "Preview without executing").action(async (id, opts) => {
1042
+ cmd.command("delete <id>").description("Delete an app").option("--dry-run", "Preview without executing").option("-y, --yes", "Skip confirmation prompt").action(async (id, opts) => {
879
1043
  try {
880
1044
  if (handleDryRun(opts, "delete", { id }, `Would delete app ${id}`)) return;
1045
+ if (!opts.yes && isInteractiveAllowed(program2)) {
1046
+ const proceed = await promptConfirm({
1047
+ message: `Delete app ${id}?`,
1048
+ initialValue: false,
1049
+ cancelMessage: "Cancelled app deletion."
1050
+ });
1051
+ if (proceed === null) return;
1052
+ if (!proceed) {
1053
+ console.log(` ${dim("Skipped app deletion.")}`);
1054
+ return;
1055
+ }
1056
+ }
881
1057
  const admin = adminClientFromFlags(program2);
882
1058
  await withSpinner(
883
1059
  "Deleting app\u2026",
@@ -1006,6 +1182,45 @@ function registerApps(program2) {
1006
1182
  exitWithError(err);
1007
1183
  }
1008
1184
  });
1185
+ cmd.command("chains").description("List Admin API chain identifiers for app configuration (e.g. ETH_MAINNET)").action(async () => {
1186
+ try {
1187
+ const admin = adminClientFromFlags(program2);
1188
+ const chains = await withSpinner(
1189
+ "Fetching chains\u2026",
1190
+ "Chains fetched",
1191
+ () => admin.listChains()
1192
+ );
1193
+ if (isJSONMode()) {
1194
+ printJSON(chains);
1195
+ return;
1196
+ }
1197
+ if (chains.length === 0) {
1198
+ emptyState("No chain networks were returned.");
1199
+ return;
1200
+ }
1201
+ const formatChainId = (value) => {
1202
+ if (!value) return dim("\u2014");
1203
+ const num = parseInt(value, 10);
1204
+ if (isNaN(num)) return value;
1205
+ return `${num} (0x${num.toString(16)})`;
1206
+ };
1207
+ const rows = chains.map((c) => [
1208
+ c.id,
1209
+ c.name,
1210
+ formatChainId(c.networkChainId),
1211
+ c.isTestnet ? dim("yes") : "no",
1212
+ c.availability === "public" ? green(c.availability) : dim(c.availability),
1213
+ c.currency
1214
+ ]);
1215
+ printTable(["ID", "Name", "Chain ID", "Testnet", "Availability", "Currency"], rows);
1216
+ if (verbose) {
1217
+ console.log("");
1218
+ printJSON(chains);
1219
+ }
1220
+ } catch (err) {
1221
+ exitWithError(err);
1222
+ }
1223
+ });
1009
1224
  }
1010
1225
 
1011
1226
  // src/commands/setup.ts
@@ -1066,7 +1281,7 @@ function normalizeTraceMethod(method) {
1066
1281
  return `trace_${normalized}`;
1067
1282
  }
1068
1283
  function registerTrace(program2) {
1069
- program2.command("trace <method> [params...]").description("Call a trace_* method").action(async (method, params) => {
1284
+ 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
1285
  try {
1071
1286
  const client = clientFromFlags(program2);
1072
1287
  const rpcMethod = normalizeTraceMethod(method);
@@ -1088,7 +1303,7 @@ function normalizeDebugMethod(method) {
1088
1303
  return `debug_${method.replace(/-/g, "_")}`;
1089
1304
  }
1090
1305
  function registerDebug(program2) {
1091
- program2.command("debug <method> [params...]").description("Call a debug_* method").action(async (method, params) => {
1306
+ 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
1307
  try {
1093
1308
  const client = clientFromFlags(program2);
1094
1309
  const rpcMethod = normalizeDebugMethod(method);
@@ -1105,29 +1320,72 @@ function registerDebug(program2) {
1105
1320
  }
1106
1321
 
1107
1322
  // src/commands/transfers.ts
1323
+ async function promptTransfersPagination(shown) {
1324
+ const action = await promptSelect({
1325
+ message: `${shown} transfers loaded \u2014 more available`,
1326
+ options: [
1327
+ { label: "Load next page", value: "next" },
1328
+ { label: "Stop here", value: "stop" }
1329
+ ],
1330
+ initialValue: "next",
1331
+ cancelMessage: "Stopped pagination."
1332
+ });
1333
+ if (action === null) return "stop";
1334
+ return action;
1335
+ }
1336
+ var TABLE_HEADERS = ["Block", "From", "To", "Value", "Asset", "Category"];
1337
+ function formatTransferRows(transfers) {
1338
+ return transfers.map((t) => {
1339
+ const block = t.blockNum ? String(parseInt(t.blockNum, 16)) : dim("\u2014");
1340
+ const from = t.from ? `${t.from.slice(0, 8)}\u2026${t.from.slice(-4)}` : dim("\u2014");
1341
+ const to = t.to ? `${t.to.slice(0, 8)}\u2026${t.to.slice(-4)}` : dim("contract creation");
1342
+ const value = t.value !== null && t.value !== void 0 ? String(t.value) : dim("\u2014");
1343
+ const asset = t.asset ?? dim("\u2014");
1344
+ const category = t.category;
1345
+ return [block, from, to, value, asset, category];
1346
+ });
1347
+ }
1108
1348
  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) => {
1349
+ 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(
1350
+ "after",
1351
+ `
1352
+ Examples:
1353
+ # Outgoing transfers from an address
1354
+ alchemy transfers 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
1355
+
1356
+ # Incoming transfers to an address
1357
+ alchemy transfers --to-address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
1358
+
1359
+ # Outgoing ERC-20 transfers only
1360
+ alchemy transfers --from-address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --category erc20
1361
+
1362
+ # Transfers within a block range
1363
+ alchemy transfers 0xd8dA... --from-block 0x100000 --to-block latest`
1364
+ ).action(async (addressArg, opts) => {
1110
1365
  try {
1111
1366
  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 = {
1367
+ const address = addressArg ? await resolveAddress(addressArg, client) : void 0;
1368
+ if (opts.fromAddress) opts.fromAddress = await resolveAddress(opts.fromAddress, client);
1369
+ if (opts.toAddress) opts.toAddress = await resolveAddress(opts.toAddress, client);
1370
+ const baseFilter = {
1117
1371
  fromBlock: opts.fromBlock ?? "0x0",
1118
1372
  toBlock: opts.toBlock ?? "latest",
1119
- withMetadata: true
1373
+ withMetadata: true,
1374
+ category: opts.category ? splitCommaList(opts.category) : ["external", "internal", "erc20", "erc721", "erc1155", "specialnft"]
1120
1375
  };
1376
+ if (opts.maxCount) {
1377
+ baseFilter.maxCount = opts.maxCount.startsWith("0x") ? opts.maxCount : `0x${Number.parseInt(opts.maxCount, 10).toString(16)}`;
1378
+ } else if (!isJSONMode()) {
1379
+ baseFilter.maxCount = "0x19";
1380
+ }
1381
+ if (opts.pageKey) baseFilter.pageKey = opts.pageKey;
1382
+ const filter = { ...baseFilter };
1121
1383
  if (address && !opts.fromAddress && !opts.toAddress) {
1122
1384
  filter.fromAddress = address;
1123
- filter.toAddress = address;
1124
1385
  } else {
1125
1386
  if (opts.fromAddress) filter.fromAddress = opts.fromAddress;
1126
1387
  if (opts.toAddress) filter.toAddress = opts.toAddress;
1127
1388
  }
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
1389
  const result = await withSpinner(
1132
1390
  "Fetching transfers\u2026",
1133
1391
  "Transfers fetched",
@@ -1135,8 +1393,43 @@ function registerTransfers(program2) {
1135
1393
  );
1136
1394
  if (isJSONMode()) {
1137
1395
  printJSON(result);
1138
- } else {
1139
- printSyntaxJSON(result);
1396
+ return;
1397
+ }
1398
+ let totalShown = result.transfers.length;
1399
+ if (totalShown === 0) {
1400
+ console.log(dim("No transfers found."));
1401
+ return;
1402
+ }
1403
+ console.log(`${totalShown} transfer${totalShown === 1 ? "" : "s"}
1404
+ `);
1405
+ printTable(TABLE_HEADERS, formatTransferRows(result.transfers));
1406
+ const interactive = isInteractiveAllowed(program2);
1407
+ let pageKey = result.pageKey;
1408
+ while (pageKey && interactive) {
1409
+ const action = await promptTransfersPagination(totalShown);
1410
+ if (action === "stop") {
1411
+ console.log(`
1412
+ ${dim(`Next page key: ${pageKey}`)}`);
1413
+ break;
1414
+ }
1415
+ filter.pageKey = pageKey;
1416
+ const nextResult = await withSpinner(
1417
+ "Fetching next page\u2026",
1418
+ "Page fetched",
1419
+ () => client.call("alchemy_getAssetTransfers", [filter])
1420
+ );
1421
+ if (nextResult.transfers.length > 0) {
1422
+ totalShown += nextResult.transfers.length;
1423
+ console.log(`
1424
+ ${totalShown} transfers total
1425
+ `);
1426
+ printTable(TABLE_HEADERS, formatTransferRows(nextResult.transfers));
1427
+ }
1428
+ pageKey = nextResult.pageKey;
1429
+ }
1430
+ if (pageKey && !interactive) {
1431
+ console.log(`
1432
+ ${dim(`More results available. Use --page-key ${pageKey} to see the next page.`)}`);
1140
1433
  }
1141
1434
  } catch (err) {
1142
1435
  exitWithError(err);
@@ -1455,8 +1748,20 @@ function registerWebhooks(program2) {
1455
1748
  exitWithError(err);
1456
1749
  }
1457
1750
  });
1458
- cmd.command("delete <webhookId>").description("Delete webhook").action(async (webhookId) => {
1751
+ cmd.command("delete <webhookId>").description("Delete webhook").option("-y, --yes", "Skip confirmation prompt").action(async (webhookId, opts) => {
1459
1752
  try {
1753
+ if (!opts.yes && isInteractiveAllowed(program2)) {
1754
+ const proceed = await promptConfirm({
1755
+ message: `Delete webhook ${webhookId}?`,
1756
+ initialValue: false,
1757
+ cancelMessage: "Cancelled webhook deletion."
1758
+ });
1759
+ if (proceed === null) return;
1760
+ if (!proceed) {
1761
+ console.log(` ${dim("Skipped webhook deletion.")}`);
1762
+ return;
1763
+ }
1764
+ }
1460
1765
  const token = resolveWebhookApiKey(cmd.opts());
1461
1766
  const result = await withSpinner(
1462
1767
  "Deleting webhook\u2026",
@@ -1590,7 +1895,7 @@ function registerSolana(program2) {
1590
1895
  const cmd = program2.command("solana").description("Solana RPC and DAS wrappers");
1591
1896
  cmd.command("rpc <method> [params...]").description("Call a Solana JSON-RPC method").action(async (method, params) => {
1592
1897
  try {
1593
- const client = clientFromFlags(program2);
1898
+ const client = clientFromFlags(program2, { defaultNetwork: "solana-mainnet" });
1594
1899
  const result = await withSpinner(
1595
1900
  `Calling ${method}\u2026`,
1596
1901
  `Called ${method}`,
@@ -1601,14 +1906,15 @@ function registerSolana(program2) {
1601
1906
  exitWithError(err);
1602
1907
  }
1603
1908
  });
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) => {
1909
+ cmd.command("das <method> [params...]").description("Call a Solana DAS method (e.g. getAssetsByOwner)").action(async (method, params) => {
1606
1910
  try {
1607
- const client = clientFromFlags(program2);
1911
+ const client = clientFromFlags(program2, { defaultNetwork: "solana-mainnet" });
1912
+ const parsed = parseCLIParams(params);
1913
+ const rpcParams = parsed.length === 1 && typeof parsed[0] === "object" && parsed[0] !== null && !Array.isArray(parsed[0]) ? parsed[0] : parsed;
1608
1914
  const result = await withSpinner(
1609
1915
  `Calling ${method}\u2026`,
1610
1916
  `Called ${method}`,
1611
- () => client.call(method, parseCLIParams(params))
1917
+ () => client.call(method, rpcParams)
1612
1918
  );
1613
1919
  printSyntaxJSON(result);
1614
1920
  } catch (err) {
@@ -1617,6 +1923,311 @@ function registerSolana(program2) {
1617
1923
  });
1618
1924
  }
1619
1925
 
1926
+ // src/commands/gas.ts
1927
+ function registerGas(program2) {
1928
+ program2.command("gas").description("Get current gas prices (base fee + priority fee)").addHelpText(
1929
+ "after",
1930
+ `
1931
+ Examples:
1932
+ alchemy gas
1933
+ alchemy gas -n polygon-mainnet
1934
+ alchemy gas --json`
1935
+ ).action(async () => {
1936
+ try {
1937
+ const client = clientFromFlags(program2);
1938
+ const [gasPrice, maxPriorityFee] = await withSpinner(
1939
+ "Fetching gas prices\u2026",
1940
+ "Gas prices fetched",
1941
+ async () => {
1942
+ const [gp, mpf] = await Promise.all([
1943
+ client.call("eth_gasPrice", []),
1944
+ client.call("eth_maxPriorityFeePerGas", []).catch(() => null)
1945
+ ]);
1946
+ return [gp, mpf];
1947
+ }
1948
+ );
1949
+ const network = resolveNetwork(program2);
1950
+ const gasPriceWei = BigInt(gasPrice);
1951
+ const gasPriceGwei = Number(gasPriceWei) / 1e9;
1952
+ let priorityFeeGwei = null;
1953
+ let priorityFeeWei = null;
1954
+ if (maxPriorityFee) {
1955
+ priorityFeeWei = BigInt(maxPriorityFee);
1956
+ priorityFeeGwei = Number(priorityFeeWei) / 1e9;
1957
+ }
1958
+ if (isJSONMode()) {
1959
+ const json = {
1960
+ gasPrice,
1961
+ gasPriceGwei: formatGwei(gasPriceGwei),
1962
+ network
1963
+ };
1964
+ if (maxPriorityFee) {
1965
+ json.maxPriorityFeePerGas = maxPriorityFee;
1966
+ json.maxPriorityFeePerGasGwei = formatGwei(priorityFeeGwei);
1967
+ }
1968
+ printJSON(json);
1969
+ } else {
1970
+ const pairs = [];
1971
+ const gasPriceFormatted = formatGweiWithRaw(gasPrice);
1972
+ pairs.push(["Gas Price", gasPriceFormatted ?? `${formatGwei(gasPriceGwei)} gwei`]);
1973
+ if (maxPriorityFee && priorityFeeGwei !== null) {
1974
+ const priorityFormatted = formatGweiWithRaw(maxPriorityFee);
1975
+ pairs.push(["Priority Fee", priorityFormatted ?? `${formatGwei(priorityFeeGwei)} gwei`]);
1976
+ }
1977
+ pairs.push(["Network", network]);
1978
+ printKeyValueBox(pairs);
1979
+ if (verbose) {
1980
+ console.log("");
1981
+ const verboseData = {
1982
+ rpcMethod: "eth_gasPrice",
1983
+ rpcResult: gasPrice
1984
+ };
1985
+ if (maxPriorityFee) {
1986
+ verboseData.rpcMethod2 = "eth_maxPriorityFeePerGas";
1987
+ verboseData.rpcResult2 = maxPriorityFee;
1988
+ }
1989
+ printJSON(verboseData);
1990
+ }
1991
+ }
1992
+ } catch (err) {
1993
+ exitWithError(err);
1994
+ }
1995
+ });
1996
+ }
1997
+
1998
+ // src/commands/logs.ts
1999
+ var PAGE_SIZE = 25;
2000
+ var LARGE_PAGE_SIZE = 100;
2001
+ function normalizeBlockParam(value) {
2002
+ if (value === "latest" || value === "earliest" || value === "pending") {
2003
+ return value;
2004
+ }
2005
+ if (value.startsWith("0x")) return value;
2006
+ const num = parseInt(value, 10);
2007
+ if (isNaN(num) || num < 0) {
2008
+ throw errInvalidArgs("Block must be a number, hex, or tag (latest, earliest, pending).");
2009
+ }
2010
+ return `0x${num.toString(16)}`;
2011
+ }
2012
+ function formatLogRows(logs) {
2013
+ return logs.map((log) => {
2014
+ const block = formatHexQuantity(log.blockNumber) ?? log.blockNumber;
2015
+ const idx = formatHexQuantity(log.logIndex) ?? log.logIndex;
2016
+ const topic0 = log.topics[0] ? `${log.topics[0].slice(0, 10)}\u2026` : dim("none");
2017
+ const addr = log.address;
2018
+ const txHash = `${log.transactionHash.slice(0, 10)}\u2026`;
2019
+ return [block, idx, addr, topic0, txHash];
2020
+ });
2021
+ }
2022
+ var TABLE_HEADERS2 = ["Block", "Index", "Address", "Topic0", "Tx Hash"];
2023
+ async function promptPaginationAction2(shown, total) {
2024
+ const remaining = total - shown;
2025
+ const options = [
2026
+ { label: `Show next ${Math.min(PAGE_SIZE, remaining)}`, value: "next" }
2027
+ ];
2028
+ if (remaining > PAGE_SIZE) {
2029
+ options.push({ label: `Show next ${Math.min(LARGE_PAGE_SIZE, remaining)}`, value: "next-large" });
2030
+ }
2031
+ options.push({ label: "Stop here", value: "stop" });
2032
+ const action = await promptSelect({
2033
+ message: `Showing ${shown} of ${total} logs (${remaining} remaining)`,
2034
+ options,
2035
+ initialValue: "next",
2036
+ cancelMessage: "Stopped pagination."
2037
+ });
2038
+ if (action === null) return "stop";
2039
+ return action;
2040
+ }
2041
+ function registerLogs(program2) {
2042
+ program2.command("logs").description("Query event logs (eth_getLogs)").addHelpText(
2043
+ "after",
2044
+ `
2045
+ Examples:
2046
+ alchemy logs --from-block 18000000 --to-block 18000010
2047
+ alchemy logs --address 0xdAC17F958D2ee523a2206206994597C13D831ec7 --from-block 18000000 --to-block 18000010
2048
+ alchemy logs --address 0xdAC17F958D2ee523a2206206994597C13D831ec7 --topic 0xddf252ad...
2049
+ alchemy logs --from-block latest --json`
2050
+ ).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) => {
2051
+ try {
2052
+ const filter = {
2053
+ fromBlock: normalizeBlockParam(opts.fromBlock),
2054
+ toBlock: normalizeBlockParam(opts.toBlock)
2055
+ };
2056
+ if (opts.address) {
2057
+ filter.address = opts.address;
2058
+ }
2059
+ if (opts.topic && opts.topic.length > 0) {
2060
+ filter.topics = opts.topic;
2061
+ }
2062
+ const client = clientFromFlags(program2);
2063
+ const logs = await withSpinner(
2064
+ "Fetching logs\u2026",
2065
+ "Logs fetched",
2066
+ () => client.call("eth_getLogs", [filter])
2067
+ );
2068
+ const network = resolveNetwork(program2);
2069
+ if (isJSONMode()) {
2070
+ printJSON({ logs, count: logs.length, network });
2071
+ return;
2072
+ }
2073
+ if (logs.length === 0) {
2074
+ console.log(dim("No logs found for the given filter."));
2075
+ return;
2076
+ }
2077
+ const total = logs.length;
2078
+ const interactive = isInteractiveAllowed(program2);
2079
+ if (!interactive) {
2080
+ console.log(`Found ${total} log${total === 1 ? "" : "s"} on ${network}
2081
+ `);
2082
+ printTable(TABLE_HEADERS2, formatLogRows(logs));
2083
+ } else {
2084
+ let offset = 0;
2085
+ const firstPage = logs.slice(0, PAGE_SIZE);
2086
+ console.log(`Found ${total} log${total === 1 ? "" : "s"} on ${network}
2087
+ `);
2088
+ printTable(TABLE_HEADERS2, formatLogRows(firstPage));
2089
+ offset = firstPage.length;
2090
+ while (offset < total) {
2091
+ const action = await promptPaginationAction2(offset, total);
2092
+ if (action === "stop") break;
2093
+ const size = action === "next-large" ? LARGE_PAGE_SIZE : PAGE_SIZE;
2094
+ const page = logs.slice(offset, offset + size);
2095
+ console.log("");
2096
+ printTable(TABLE_HEADERS2, formatLogRows(page));
2097
+ offset += page.length;
2098
+ }
2099
+ }
2100
+ if (total >= 1e4) {
2101
+ console.log("");
2102
+ console.log(dim("\u26A0 RPC result limit reached (10,000). Narrow your block range for complete results."));
2103
+ }
2104
+ } catch (err) {
2105
+ exitWithError(err);
2106
+ }
2107
+ });
2108
+ }
2109
+
2110
+ // src/commands/completions.ts
2111
+ function getDescription(cmd) {
2112
+ try {
2113
+ return typeof cmd.description === "function" ? cmd.description() : String(cmd.description || "");
2114
+ } catch {
2115
+ return "";
2116
+ }
2117
+ }
2118
+ function collectCommands(cmd, prefix = []) {
2119
+ const results = [];
2120
+ for (const sub of cmd.commands) {
2121
+ const path = [...prefix, sub.name()];
2122
+ results.push({ path, description: getDescription(sub) });
2123
+ results.push(...collectCommands(sub, path));
2124
+ }
2125
+ return results;
2126
+ }
2127
+ function generateBash(program2) {
2128
+ const commands = collectCommands(program2);
2129
+ const topLevel = program2.commands.map((c) => c.name()).join(" ");
2130
+ const cases = commands.filter((c) => c.path.length === 1).map((c) => {
2131
+ const subs = commands.filter((s) => s.path.length === 2 && s.path[0] === c.path[0]).map((s) => s.path[1]);
2132
+ if (subs.length === 0) return "";
2133
+ return ` ${c.path[0]}) COMPREPLY=($(compgen -W "${subs.join(" ")}" -- "$cur")) ;;`;
2134
+ }).filter(Boolean).join("\n");
2135
+ return `# bash completion for alchemy CLI
2136
+ # Add to ~/.bashrc: eval "$(alchemy completions bash)"
2137
+ _alchemy_completions() {
2138
+ local cur prev
2139
+ cur="\${COMP_WORDS[COMP_CWORD]}"
2140
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
2141
+
2142
+ if [[ \${COMP_CWORD} -eq 1 ]]; then
2143
+ COMPREPLY=($(compgen -W "${topLevel}" -- "$cur"))
2144
+ return
2145
+ fi
2146
+
2147
+ case "$prev" in
2148
+ ${cases}
2149
+ -n|--network) COMPREPLY=() ;;
2150
+ *) COMPREPLY=() ;;
2151
+ esac
2152
+ }
2153
+ complete -F _alchemy_completions alchemy
2154
+ `;
2155
+ }
2156
+ function generateZsh(program2) {
2157
+ const commands = collectCommands(program2);
2158
+ const topLevel = program2.commands.map((c) => `'${c.name()}:${getDescription(c).replace(/'/g, "'\\''")}'`).join("\n ");
2159
+ const subcommandCases = commands.filter((c) => c.path.length === 1).map((c) => {
2160
+ const subs = commands.filter((s) => s.path.length === 2 && s.path[0] === c.path[0]).map((s) => `'${s.path[1]}:${s.description.replace(/'/g, "'\\''")}'`);
2161
+ if (subs.length === 0) return "";
2162
+ return ` ${c.path[0]})
2163
+ _values 'subcommand' \\
2164
+ ${subs.join(" \\\n ")}
2165
+ ;;`;
2166
+ }).filter(Boolean).join("\n");
2167
+ return `#compdef alchemy
2168
+ # zsh completion for alchemy CLI
2169
+ # Add to ~/.zshrc: eval "$(alchemy completions zsh)"
2170
+ _alchemy() {
2171
+ local -a commands
2172
+ if (( CURRENT == 2 )); then
2173
+ commands=(
2174
+ ${topLevel}
2175
+ )
2176
+ _describe 'command' commands
2177
+ return
2178
+ fi
2179
+
2180
+ case "$words[2]" in
2181
+ ${subcommandCases}
2182
+ esac
2183
+ }
2184
+ _alchemy "$@"
2185
+ compdef _alchemy alchemy
2186
+ `;
2187
+ }
2188
+ function generateFish(program2) {
2189
+ const commands = collectCommands(program2);
2190
+ const lines = commands.map((c) => {
2191
+ const desc = c.description.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
2192
+ if (c.path.length === 1) {
2193
+ return `complete -c alchemy -n '__fish_use_subcommand' -a '${c.path[0]}' -d '${desc}'`;
2194
+ }
2195
+ if (c.path.length === 2) {
2196
+ return `complete -c alchemy -n '__fish_seen_subcommand_from ${c.path[0]}' -a '${c.path[1]}' -d '${desc}'`;
2197
+ }
2198
+ return "";
2199
+ }).filter(Boolean);
2200
+ return `# fish completion for alchemy CLI
2201
+ # Add to ~/.config/fish/completions/alchemy.fish
2202
+ ${lines.join("\n")}
2203
+ `;
2204
+ }
2205
+ function registerCompletions(program2) {
2206
+ program2.command("completions").argument("<shell>", "Shell type: bash, zsh, or fish").description("Generate shell completion scripts").addHelpText(
2207
+ "after",
2208
+ `
2209
+ Examples:
2210
+ alchemy completions bash >> ~/.bashrc
2211
+ eval "$(alchemy completions zsh)"
2212
+ alchemy completions fish > ~/.config/fish/completions/alchemy.fish`
2213
+ ).action((shell) => {
2214
+ switch (shell.toLowerCase()) {
2215
+ case "bash":
2216
+ process.stdout.write(generateBash(program2));
2217
+ break;
2218
+ case "zsh":
2219
+ process.stdout.write(generateZsh(program2));
2220
+ break;
2221
+ case "fish":
2222
+ process.stdout.write(generateFish(program2));
2223
+ break;
2224
+ default:
2225
+ console.error(`Unknown shell: ${shell}. Supported: bash, zsh, fish`);
2226
+ process.exit(2);
2227
+ }
2228
+ });
2229
+ }
2230
+
1620
2231
  // src/commands/agent-prompt.ts
1621
2232
  var RETRYABLE_CODES = /* @__PURE__ */ new Set([
1622
2233
  ErrorCode.RATE_LIMITED,
@@ -1718,7 +2329,7 @@ function buildAgentPrompt(program2) {
1718
2329
  envVar: "ALCHEMY_ACCESS_KEY",
1719
2330
  flag: "--access-key <key>",
1720
2331
  configKey: "access-key",
1721
- commandFamilies: ["apps", "chains", "network list --configured"]
2332
+ commandFamilies: ["apps", "network list --configured"]
1722
2333
  },
1723
2334
  {
1724
2335
  method: "Webhook API key",
@@ -1850,7 +2461,7 @@ var ROOT_OPTION_GROUPS = [
1850
2461
  var ROOT_COMMAND_PILLARS = [
1851
2462
  {
1852
2463
  label: "Node",
1853
- commands: ["balance", "tx", "block", "rpc", "trace", "debug"]
2464
+ commands: ["balance", "tx", "block", "rpc", "trace", "debug", "gas", "logs"]
1854
2465
  },
1855
2466
  {
1856
2467
  label: "Data",
@@ -1862,11 +2473,11 @@ var ROOT_COMMAND_PILLARS = [
1862
2473
  },
1863
2474
  {
1864
2475
  label: "Chains",
1865
- commands: ["network", "chains", "solana"]
2476
+ commands: ["network", "solana"]
1866
2477
  },
1867
2478
  {
1868
2479
  label: "Admin",
1869
- commands: ["apps", "config", "setup", "agent-prompt", "update-check", "version", "help"]
2480
+ commands: ["apps", "config", "setup", "completions", "agent-prompt", "update-check", "version", "help"]
1870
2481
  }
1871
2482
  ];
1872
2483
  function formatCommandSignature(sub) {
@@ -1915,10 +2526,10 @@ function resetUpdateNoticeState() {
1915
2526
  }
1916
2527
  program.name("alchemy").description(
1917
2528
  "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(
2529
+ ).version("0.3.1", "-v, --version", "display CLI version").option("--api-key <key>", "Alchemy API key (env: ALCHEMY_API_KEY)").option("--access-key <key>", "Alchemy access key (env: ALCHEMY_ACCESS_KEY)").option(
1919
2530
  "-n, --network <network>",
1920
2531
  "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) => {
2532
+ ).option("--x402", "Use x402 wallet-based gateway auth").option("--wallet-key-file <path>", "Path to wallet private key file for x402").option("--json", "Force JSON output (auto-enabled when piped)").option("-q, --quiet", "Suppress non-essential output").option("--verbose", "Enable verbose output").option("--no-color", "Disable color output").option("--reveal", "Show secrets in plain text (TTY only)").option("--timeout <ms>", "Request timeout in milliseconds", parseInt).option("--debug", "Enable debug diagnostics").option("--no-interactive", "Disable REPL and prompt-driven interactions").addHelpCommand(false).allowExcessArguments(true).exitOverride((err) => {
1922
2533
  if (err.code === "commander.help" || err.code === "commander.helpDisplayed" || err.code === "commander.version") {
1923
2534
  process.exit(0);
1924
2535
  }
@@ -2031,6 +2642,14 @@ ${styledLine}`;
2031
2642
  }).addHelpText("beforeAll", () => isHelpInvocation ? brandedHelp() : "").addHelpText("after", () => {
2032
2643
  if (isJSONMode()) return "";
2033
2644
  return [
2645
+ "",
2646
+ `${hBrand("\u25C6")} ${hBold("Quick Start")}`,
2647
+ ` ${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")}`,
2648
+ ` ${hBrand("alchemy")} ${hDim("Interactive mode with guided setup")}`,
2649
+ ` ${hBrand("alchemy balance")} ${hDim("<address>")} ${hDim("Get native token balance")}`,
2650
+ ` ${hBrand("alchemy block latest")} ${hDim("Latest block summary")}`,
2651
+ ` ${hBrand("alchemy rpc eth_chainId")} ${hDim("Raw JSON-RPC call")}`,
2652
+ ` ${hBrand("alchemy config list")} ${hDim("View current configuration")}`,
2034
2653
  "",
2035
2654
  `${hBrand("\u25C6")} ${hBold("Exit Codes")}`,
2036
2655
  ` ${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")}`,
@@ -2052,6 +2671,7 @@ ${styledLine}`;
2052
2671
  ].join("\n");
2053
2672
  }).hook("preAction", () => {
2054
2673
  const opts = program.opts();
2674
+ if (opts.color === false) setNoColor(true);
2055
2675
  const cfg = load();
2056
2676
  setFlags({
2057
2677
  json: opts.json,
@@ -2086,7 +2706,7 @@ ${styledLine}`;
2086
2706
  if (isInteractiveAllowed(program)) {
2087
2707
  let latestForInteractiveStartup = null;
2088
2708
  if (shouldRunOnboarding(program, cfg)) {
2089
- const { runOnboarding } = await import("./onboarding-SJ2BRK5I.js");
2709
+ const { runOnboarding } = await import("./onboarding-XNAWN5BR.js");
2090
2710
  const latest = getAvailableUpdateOnce();
2091
2711
  const completed = await runOnboarding(program, latest);
2092
2712
  updateShownDuringInteractiveStartup = Boolean(latest);
@@ -2098,7 +2718,7 @@ ${styledLine}`;
2098
2718
  latestForInteractiveStartup = getAvailableUpdateOnce();
2099
2719
  updateShownDuringInteractiveStartup = Boolean(latestForInteractiveStartup);
2100
2720
  }
2101
- const { startREPL } = await import("./interactive-EAUEVR63.js");
2721
+ const { startREPL } = await import("./interactive-CLPT5QDZ.js");
2102
2722
  program.exitOverride();
2103
2723
  program.configureOutput({
2104
2724
  writeErr: () => {
@@ -2112,9 +2732,12 @@ ${styledLine}`;
2112
2732
  registerRPC(program);
2113
2733
  registerBalance(program);
2114
2734
  registerTx(program);
2735
+ registerReceipt(program);
2115
2736
  registerBlock(program);
2116
2737
  registerTrace(program);
2117
2738
  registerDebug(program);
2739
+ registerGas(program);
2740
+ registerLogs(program);
2118
2741
  registerTokens(program);
2119
2742
  registerNFTs(program);
2120
2743
  registerTransfers(program);
@@ -2126,12 +2749,12 @@ registerBundler(program);
2126
2749
  registerGasManager(program);
2127
2750
  registerWebhooks(program);
2128
2751
  registerNetwork(program);
2129
- registerChains(program);
2130
2752
  registerApps(program);
2131
2753
  registerSetup(program);
2132
2754
  registerConfig(program);
2133
2755
  registerSolana(program);
2134
2756
  registerAgentPrompt(program);
2757
+ registerCompletions(program);
2135
2758
  registerUpdateCheck(program);
2136
2759
  registerVersion(program);
2137
2760
  program.command("help [command...]").description("display help for command").action((commandPath) => {