@spectratools/etherscan-cli 0.2.4 → 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.
Files changed (4) hide show
  1. package/README.md +14 -5
  2. package/dist/cli.js +645 -190
  3. package/dist/index.js +1987 -0
  4. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { readFileSync, realpathSync } from "fs";
5
5
  import { dirname, resolve } from "path";
6
6
  import { fileURLToPath } from "url";
7
- import { Cli as Cli7 } from "incur";
7
+ import { Cli as Cli8 } from "incur";
8
8
 
9
9
  // src/commands/account.ts
10
10
  import {
@@ -153,6 +153,18 @@ var txListItemSchema = z3.object({
153
153
  isError: z3.string(),
154
154
  gasUsed: z3.string()
155
155
  });
156
+ var internalTxItemSchema = z3.object({
157
+ hash: z3.string(),
158
+ from: z3.string(),
159
+ to: z3.string().nullable().optional(),
160
+ value: z3.string(),
161
+ timeStamp: z3.string(),
162
+ blockNumber: z3.string(),
163
+ type: z3.string().optional(),
164
+ traceId: z3.string().optional(),
165
+ isError: z3.string().optional(),
166
+ gasUsed: z3.string().optional()
167
+ });
156
168
  var tokenTxItemSchema = z3.object({
157
169
  hash: z3.string(),
158
170
  from: z3.string(),
@@ -164,6 +176,17 @@ var tokenTxItemSchema = z3.object({
164
176
  timeStamp: z3.string(),
165
177
  contractAddress: z3.string()
166
178
  });
179
+ var nftTxItemSchema = z3.object({
180
+ hash: z3.string(),
181
+ from: z3.string(),
182
+ to: z3.string(),
183
+ tokenID: z3.string(),
184
+ tokenValue: z3.string().optional(),
185
+ tokenName: z3.string(),
186
+ tokenSymbol: z3.string(),
187
+ timeStamp: z3.string(),
188
+ contractAddress: z3.string()
189
+ });
167
190
  function normalizeAddress(address) {
168
191
  try {
169
192
  return checksumAddress(address);
@@ -336,6 +359,96 @@ accountCli.command("txlist", {
336
359
  );
337
360
  }
338
361
  });
362
+ accountCli.command("internaltx", {
363
+ description: "List internal transactions for an address.",
364
+ args: z3.object({
365
+ address: z3.string().describe("Wallet address")
366
+ }),
367
+ options: z3.object({
368
+ startblock: z3.number().optional().default(0).describe("Start block number"),
369
+ endblock: z3.string().optional().default("latest").describe("End block number"),
370
+ page: z3.number().optional().default(1).describe("Page number"),
371
+ offset: z3.number().optional().default(10).describe("Number of results per page"),
372
+ sort: z3.string().optional().default("asc").describe("Sort order (asc or desc)"),
373
+ chain: chainOption
374
+ }),
375
+ env: etherscanEnv,
376
+ output: z3.object({
377
+ address: z3.string(),
378
+ chain: z3.string(),
379
+ count: z3.number(),
380
+ transactions: z3.array(
381
+ z3.object({
382
+ hash: z3.string(),
383
+ from: z3.string(),
384
+ to: z3.string(),
385
+ value: z3.string(),
386
+ eth: z3.string(),
387
+ timestamp: z3.string(),
388
+ block: z3.string(),
389
+ type: z3.string().optional(),
390
+ traceId: z3.string().optional(),
391
+ status: z3.string(),
392
+ gasUsed: z3.string()
393
+ })
394
+ )
395
+ }),
396
+ examples: [
397
+ {
398
+ args: { address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" },
399
+ options: { chain: "ethereum", sort: "desc", offset: 5 },
400
+ description: "List recent internal transactions for an address"
401
+ }
402
+ ],
403
+ async run(c) {
404
+ if (!isAddress(c.args.address)) {
405
+ return c.error({
406
+ code: "INVALID_ADDRESS",
407
+ message: `Invalid address: "${c.args.address}". Use a valid 0x-prefixed 20-byte hex address.`
408
+ });
409
+ }
410
+ const apiKey = c.env.ETHERSCAN_API_KEY;
411
+ const chainId = resolveChainId(c.options.chain);
412
+ const address = normalizeAddress(c.args.address);
413
+ const client = createEtherscanClient(apiKey);
414
+ const txs = await withRateLimit2(
415
+ () => client.call(
416
+ {
417
+ chainid: chainId,
418
+ module: "account",
419
+ action: "txlistinternal",
420
+ address,
421
+ startblock: c.options.startblock,
422
+ endblock: c.options.endblock,
423
+ page: c.options.page,
424
+ offset: c.options.offset,
425
+ sort: c.options.sort
426
+ },
427
+ z3.array(internalTxItemSchema)
428
+ ),
429
+ rateLimiter
430
+ );
431
+ const formatted = txs.map((tx) => ({
432
+ hash: tx.hash,
433
+ from: normalizeAddress(tx.from),
434
+ to: tx.to ? normalizeAddress(tx.to) : "",
435
+ value: tx.value,
436
+ eth: weiToEth(tx.value),
437
+ timestamp: formatTimestamp(Number(tx.timeStamp)),
438
+ block: tx.blockNumber,
439
+ type: tx.type,
440
+ traceId: tx.traceId,
441
+ status: tx.isError === "0" || tx.isError === void 0 ? "success" : "failed",
442
+ gasUsed: tx.gasUsed ?? "0"
443
+ }));
444
+ return c.ok({
445
+ address,
446
+ chain: c.options.chain,
447
+ count: formatted.length,
448
+ transactions: formatted
449
+ });
450
+ }
451
+ });
339
452
  accountCli.command("tokentx", {
340
453
  description: "List ERC-20 token transfers for an address.",
341
454
  args: z3.object({
@@ -380,9 +493,16 @@ accountCli.command("tokentx", {
380
493
  message: `Invalid address: "${c.args.address}". Use a valid 0x-prefixed 20-byte hex address.`
381
494
  });
382
495
  }
496
+ if (c.options.contractaddress && !isAddress(c.options.contractaddress)) {
497
+ return c.error({
498
+ code: "INVALID_ADDRESS",
499
+ message: `Invalid contract address: "${c.options.contractaddress}". Use a valid 0x-prefixed 20-byte hex address.`
500
+ });
501
+ }
383
502
  const apiKey = c.env.ETHERSCAN_API_KEY;
384
503
  const chainId = resolveChainId(c.options.chain);
385
504
  const address = normalizeAddress(c.args.address);
505
+ const contract = c.options.contractaddress ? normalizeAddress(c.options.contractaddress) : void 0;
386
506
  const client = createEtherscanClient(apiKey);
387
507
  const transfers = await withRateLimit2(
388
508
  () => client.call(
@@ -391,7 +511,7 @@ accountCli.command("tokentx", {
391
511
  module: "account",
392
512
  action: "tokentx",
393
513
  address,
394
- contractaddress: c.options.contractaddress,
514
+ contractaddress: contract,
395
515
  page: c.options.page,
396
516
  offset: c.options.offset
397
517
  },
@@ -426,6 +546,194 @@ accountCli.command("tokentx", {
426
546
  );
427
547
  }
428
548
  });
549
+ accountCli.command("nfttx", {
550
+ description: "List ERC-721 NFT transfers for an address.",
551
+ args: z3.object({
552
+ address: z3.string().describe("Wallet address")
553
+ }),
554
+ options: z3.object({
555
+ contractaddress: z3.string().optional().describe("Filter by NFT contract address"),
556
+ startblock: z3.number().optional().default(0).describe("Start block number"),
557
+ endblock: z3.string().optional().default("latest").describe("End block number"),
558
+ page: z3.number().optional().default(1).describe("Page number"),
559
+ offset: z3.number().optional().default(20).describe("Results per page"),
560
+ sort: z3.string().optional().default("asc").describe("Sort order (asc or desc)"),
561
+ chain: chainOption
562
+ }),
563
+ env: etherscanEnv,
564
+ output: z3.object({
565
+ address: z3.string(),
566
+ chain: z3.string(),
567
+ count: z3.number(),
568
+ transfers: z3.array(
569
+ z3.object({
570
+ hash: z3.string(),
571
+ from: z3.string(),
572
+ to: z3.string(),
573
+ tokenId: z3.string(),
574
+ tokenName: z3.string(),
575
+ tokenSymbol: z3.string(),
576
+ timestamp: z3.string(),
577
+ contract: z3.string()
578
+ })
579
+ )
580
+ }),
581
+ examples: [
582
+ {
583
+ args: { address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" },
584
+ options: { chain: "ethereum", offset: 10, sort: "desc" },
585
+ description: "List recent ERC-721 transfers for an address"
586
+ }
587
+ ],
588
+ async run(c) {
589
+ if (!isAddress(c.args.address)) {
590
+ return c.error({
591
+ code: "INVALID_ADDRESS",
592
+ message: `Invalid address: "${c.args.address}". Use a valid 0x-prefixed 20-byte hex address.`
593
+ });
594
+ }
595
+ if (c.options.contractaddress && !isAddress(c.options.contractaddress)) {
596
+ return c.error({
597
+ code: "INVALID_ADDRESS",
598
+ message: `Invalid contract address: "${c.options.contractaddress}". Use a valid 0x-prefixed 20-byte hex address.`
599
+ });
600
+ }
601
+ const apiKey = c.env.ETHERSCAN_API_KEY;
602
+ const chainId = resolveChainId(c.options.chain);
603
+ const address = normalizeAddress(c.args.address);
604
+ const contract = c.options.contractaddress ? normalizeAddress(c.options.contractaddress) : void 0;
605
+ const client = createEtherscanClient(apiKey);
606
+ const transfers = await withRateLimit2(
607
+ () => client.call(
608
+ {
609
+ chainid: chainId,
610
+ module: "account",
611
+ action: "tokennfttx",
612
+ address,
613
+ contractaddress: contract,
614
+ startblock: c.options.startblock,
615
+ endblock: c.options.endblock,
616
+ page: c.options.page,
617
+ offset: c.options.offset,
618
+ sort: c.options.sort
619
+ },
620
+ z3.array(nftTxItemSchema)
621
+ ),
622
+ rateLimiter
623
+ );
624
+ const formatted = transfers.map((tx) => ({
625
+ hash: tx.hash,
626
+ from: normalizeAddress(tx.from),
627
+ to: normalizeAddress(tx.to),
628
+ tokenId: tx.tokenID,
629
+ tokenName: tx.tokenName,
630
+ tokenSymbol: tx.tokenSymbol,
631
+ timestamp: formatTimestamp(Number(tx.timeStamp)),
632
+ contract: normalizeAddress(tx.contractAddress)
633
+ }));
634
+ return c.ok({
635
+ address,
636
+ chain: c.options.chain,
637
+ count: formatted.length,
638
+ transfers: formatted
639
+ });
640
+ }
641
+ });
642
+ accountCli.command("erc1155tx", {
643
+ description: "List ERC-1155 token transfers for an address.",
644
+ args: z3.object({
645
+ address: z3.string().describe("Wallet address")
646
+ }),
647
+ options: z3.object({
648
+ contractaddress: z3.string().optional().describe("Filter by ERC-1155 contract address"),
649
+ startblock: z3.number().optional().default(0).describe("Start block number"),
650
+ endblock: z3.string().optional().default("latest").describe("End block number"),
651
+ page: z3.number().optional().default(1).describe("Page number"),
652
+ offset: z3.number().optional().default(20).describe("Results per page"),
653
+ sort: z3.string().optional().default("asc").describe("Sort order (asc or desc)"),
654
+ chain: chainOption
655
+ }),
656
+ env: etherscanEnv,
657
+ output: z3.object({
658
+ address: z3.string(),
659
+ chain: z3.string(),
660
+ count: z3.number(),
661
+ transfers: z3.array(
662
+ z3.object({
663
+ hash: z3.string(),
664
+ from: z3.string(),
665
+ to: z3.string(),
666
+ tokenId: z3.string(),
667
+ amount: z3.string(),
668
+ tokenName: z3.string(),
669
+ tokenSymbol: z3.string(),
670
+ timestamp: z3.string(),
671
+ contract: z3.string()
672
+ })
673
+ )
674
+ }),
675
+ examples: [
676
+ {
677
+ args: { address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" },
678
+ options: { chain: "ethereum", offset: 10, sort: "desc" },
679
+ description: "List recent ERC-1155 transfers for an address"
680
+ }
681
+ ],
682
+ async run(c) {
683
+ if (!isAddress(c.args.address)) {
684
+ return c.error({
685
+ code: "INVALID_ADDRESS",
686
+ message: `Invalid address: "${c.args.address}". Use a valid 0x-prefixed 20-byte hex address.`
687
+ });
688
+ }
689
+ if (c.options.contractaddress && !isAddress(c.options.contractaddress)) {
690
+ return c.error({
691
+ code: "INVALID_ADDRESS",
692
+ message: `Invalid contract address: "${c.options.contractaddress}". Use a valid 0x-prefixed 20-byte hex address.`
693
+ });
694
+ }
695
+ const apiKey = c.env.ETHERSCAN_API_KEY;
696
+ const chainId = resolveChainId(c.options.chain);
697
+ const address = normalizeAddress(c.args.address);
698
+ const contract = c.options.contractaddress ? normalizeAddress(c.options.contractaddress) : void 0;
699
+ const client = createEtherscanClient(apiKey);
700
+ const transfers = await withRateLimit2(
701
+ () => client.call(
702
+ {
703
+ chainid: chainId,
704
+ module: "account",
705
+ action: "token1155tx",
706
+ address,
707
+ contractaddress: contract,
708
+ startblock: c.options.startblock,
709
+ endblock: c.options.endblock,
710
+ page: c.options.page,
711
+ offset: c.options.offset,
712
+ sort: c.options.sort
713
+ },
714
+ z3.array(nftTxItemSchema)
715
+ ),
716
+ rateLimiter
717
+ );
718
+ const formatted = transfers.map((tx) => ({
719
+ hash: tx.hash,
720
+ from: normalizeAddress(tx.from),
721
+ to: normalizeAddress(tx.to),
722
+ tokenId: tx.tokenID,
723
+ amount: tx.tokenValue ?? "0",
724
+ tokenName: tx.tokenName,
725
+ tokenSymbol: tx.tokenSymbol,
726
+ timestamp: formatTimestamp(Number(tx.timeStamp)),
727
+ contract: normalizeAddress(tx.contractAddress)
728
+ }));
729
+ return c.ok({
730
+ address,
731
+ chain: c.options.chain,
732
+ count: formatted.length,
733
+ transfers: formatted
734
+ });
735
+ }
736
+ });
429
737
  accountCli.command("tokenbalance", {
430
738
  description: "Get ERC-20 token balance for an address.",
431
739
  args: z3.object({
@@ -872,41 +1180,187 @@ gasCli.command("estimate", {
872
1180
  }
873
1181
  });
874
1182
 
875
- // src/commands/stats.ts
876
- import { createRateLimiter as createRateLimiter5, withRateLimit as withRateLimit5 } from "@spectratools/cli-shared";
1183
+ // src/commands/logs.ts
1184
+ import {
1185
+ checksumAddress as checksumAddress3,
1186
+ createRateLimiter as createRateLimiter5,
1187
+ formatTimestamp as formatTimestamp2,
1188
+ isAddress as isAddress3,
1189
+ withRateLimit as withRateLimit5
1190
+ } from "@spectratools/cli-shared";
877
1191
  import { Cli as Cli4, z as z6 } from "incur";
878
1192
  var rateLimiter4 = createRateLimiter5({ requestsPerSecond: 5 });
879
1193
  var chainOption4 = z6.string().default(DEFAULT_CHAIN).describe(
880
1194
  "Chain name (default: abstract). Options: ethereum, base, arbitrum, optimism, polygon, ..."
881
1195
  );
882
- var statsCli = Cli4.create("stats", {
883
- description: "Query ETH price and total supply statistics."
1196
+ var topicOperatorOption = z6.enum(["and", "or"]);
1197
+ var logsItemSchema = z6.object({
1198
+ address: z6.string(),
1199
+ topics: z6.array(z6.string()),
1200
+ data: z6.string(),
1201
+ blockNumber: z6.string(),
1202
+ timeStamp: z6.string(),
1203
+ gasPrice: z6.string().optional(),
1204
+ gasUsed: z6.string().optional(),
1205
+ logIndex: z6.string(),
1206
+ transactionHash: z6.string(),
1207
+ transactionIndex: z6.string().optional()
884
1208
  });
885
- var ethPriceSchema = z6.object({
886
- ethbtc: z6.string(),
887
- ethbtc_timestamp: z6.string(),
888
- ethusd: z6.string(),
889
- ethusd_timestamp: z6.string()
1209
+ function normalizeAddress2(address) {
1210
+ try {
1211
+ return checksumAddress3(address);
1212
+ } catch {
1213
+ return address;
1214
+ }
1215
+ }
1216
+ var logsCli = Cli4.create("logs", {
1217
+ description: "Query event logs with topic, address, and block-range filters."
890
1218
  });
891
- statsCli.command("ethprice", {
892
- description: "Get latest ETH price in USD and BTC.",
1219
+ logsCli.command("get", {
1220
+ description: "Get event logs from Etherscan logs.getLogs.",
1221
+ args: z6.object({}),
893
1222
  options: z6.object({
1223
+ fromblock: z6.string().optional().default("0").describe("Start block number"),
1224
+ toblock: z6.string().optional().default("latest").describe("End block number"),
1225
+ address: z6.string().optional().describe("Filter by contract address"),
1226
+ topic0: z6.string().optional().describe("First indexed topic"),
1227
+ topic1: z6.string().optional().describe("Second indexed topic"),
1228
+ topic2: z6.string().optional().describe("Third indexed topic"),
1229
+ topic3: z6.string().optional().describe("Fourth indexed topic"),
1230
+ topic0_1_opr: topicOperatorOption.optional().describe("Operator between topic0 and topic1"),
1231
+ topic1_2_opr: topicOperatorOption.optional().describe("Operator between topic1 and topic2"),
1232
+ topic2_3_opr: topicOperatorOption.optional().describe("Operator between topic2 and topic3"),
1233
+ page: z6.number().optional().default(1).describe("Page number"),
1234
+ offset: z6.number().optional().default(100).describe("Results per page"),
894
1235
  chain: chainOption4
895
1236
  }),
896
1237
  env: etherscanEnv,
897
1238
  output: z6.object({
898
1239
  chain: z6.string(),
899
- usd: z6.string(),
900
- btc: z6.string(),
901
- usdTimestamp: z6.string(),
902
- btcTimestamp: z6.string()
1240
+ fromBlock: z6.string(),
1241
+ toBlock: z6.string(),
1242
+ address: z6.string().optional(),
1243
+ count: z6.number(),
1244
+ logs: z6.array(
1245
+ z6.object({
1246
+ address: z6.string(),
1247
+ topics: z6.array(z6.string()),
1248
+ data: z6.string(),
1249
+ block: z6.string(),
1250
+ timestamp: z6.string(),
1251
+ transactionHash: z6.string(),
1252
+ logIndex: z6.string(),
1253
+ transactionIndex: z6.string().optional(),
1254
+ gasPrice: z6.string().optional(),
1255
+ gasUsed: z6.string().optional()
1256
+ })
1257
+ )
1258
+ }),
1259
+ examples: [
1260
+ {
1261
+ args: {},
1262
+ options: {
1263
+ chain: "ethereum",
1264
+ address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
1265
+ topic0: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55aebec6f6f3c",
1266
+ fromblock: "20000000",
1267
+ toblock: "latest",
1268
+ offset: 25
1269
+ },
1270
+ description: "Query ERC-20 Transfer logs for USDC"
1271
+ }
1272
+ ],
1273
+ async run(c) {
1274
+ if (c.options.address && !isAddress3(c.options.address)) {
1275
+ return c.error({
1276
+ code: "INVALID_ADDRESS",
1277
+ message: `Invalid contract address: "${c.options.address}". Use a valid 0x-prefixed 20-byte hex address.`
1278
+ });
1279
+ }
1280
+ const apiKey = c.env.ETHERSCAN_API_KEY;
1281
+ const chainId = resolveChainId(c.options.chain);
1282
+ const client = createEtherscanClient(apiKey);
1283
+ const logs = await withRateLimit5(
1284
+ () => client.call(
1285
+ {
1286
+ chainid: chainId,
1287
+ module: "logs",
1288
+ action: "getLogs",
1289
+ fromBlock: c.options.fromblock,
1290
+ toBlock: c.options.toblock,
1291
+ address: c.options.address ? normalizeAddress2(c.options.address) : void 0,
1292
+ topic0: c.options.topic0,
1293
+ topic1: c.options.topic1,
1294
+ topic2: c.options.topic2,
1295
+ topic3: c.options.topic3,
1296
+ topic0_1_opr: c.options.topic0_1_opr,
1297
+ topic1_2_opr: c.options.topic1_2_opr,
1298
+ topic2_3_opr: c.options.topic2_3_opr,
1299
+ page: c.options.page,
1300
+ offset: c.options.offset
1301
+ },
1302
+ z6.array(logsItemSchema)
1303
+ ),
1304
+ rateLimiter4
1305
+ );
1306
+ const formatted = logs.map((log) => ({
1307
+ address: normalizeAddress2(log.address),
1308
+ topics: log.topics,
1309
+ data: log.data,
1310
+ block: log.blockNumber,
1311
+ timestamp: formatTimestamp2(Number(log.timeStamp)),
1312
+ transactionHash: log.transactionHash,
1313
+ logIndex: log.logIndex,
1314
+ transactionIndex: log.transactionIndex,
1315
+ gasPrice: log.gasPrice,
1316
+ gasUsed: log.gasUsed
1317
+ }));
1318
+ return c.ok({
1319
+ chain: c.options.chain,
1320
+ fromBlock: c.options.fromblock,
1321
+ toBlock: c.options.toblock,
1322
+ address: c.options.address ? normalizeAddress2(c.options.address) : void 0,
1323
+ count: formatted.length,
1324
+ logs: formatted
1325
+ });
1326
+ }
1327
+ });
1328
+
1329
+ // src/commands/stats.ts
1330
+ import { createRateLimiter as createRateLimiter6, withRateLimit as withRateLimit6 } from "@spectratools/cli-shared";
1331
+ import { Cli as Cli5, z as z7 } from "incur";
1332
+ var rateLimiter5 = createRateLimiter6({ requestsPerSecond: 5 });
1333
+ var chainOption5 = z7.string().default(DEFAULT_CHAIN).describe(
1334
+ "Chain name (default: abstract). Options: ethereum, base, arbitrum, optimism, polygon, ..."
1335
+ );
1336
+ var statsCli = Cli5.create("stats", {
1337
+ description: "Query ETH price and total supply statistics."
1338
+ });
1339
+ var ethPriceSchema = z7.object({
1340
+ ethbtc: z7.string(),
1341
+ ethbtc_timestamp: z7.string(),
1342
+ ethusd: z7.string(),
1343
+ ethusd_timestamp: z7.string()
1344
+ });
1345
+ statsCli.command("ethprice", {
1346
+ description: "Get latest ETH price in USD and BTC.",
1347
+ options: z7.object({
1348
+ chain: chainOption5
1349
+ }),
1350
+ env: etherscanEnv,
1351
+ output: z7.object({
1352
+ chain: z7.string(),
1353
+ usd: z7.string(),
1354
+ btc: z7.string(),
1355
+ usdTimestamp: z7.string(),
1356
+ btcTimestamp: z7.string()
903
1357
  }),
904
1358
  examples: [{ options: { chain: "ethereum" }, description: "Get ETH spot price on Ethereum" }],
905
1359
  async run(c) {
906
1360
  const apiKey = c.env.ETHERSCAN_API_KEY;
907
1361
  const chainId = resolveChainId(c.options.chain);
908
1362
  const client = createEtherscanClient(apiKey);
909
- const price = await withRateLimit5(
1363
+ const price = await withRateLimit6(
910
1364
  () => client.call(
911
1365
  {
912
1366
  chainid: chainId,
@@ -915,7 +1369,7 @@ statsCli.command("ethprice", {
915
1369
  },
916
1370
  ethPriceSchema
917
1371
  ),
918
- rateLimiter4
1372
+ rateLimiter5
919
1373
  );
920
1374
  return c.ok(
921
1375
  {
@@ -940,29 +1394,29 @@ statsCli.command("ethprice", {
940
1394
  });
941
1395
  statsCli.command("ethsupply", {
942
1396
  description: "Get total ETH supply in wei.",
943
- options: z6.object({
944
- chain: chainOption4
1397
+ options: z7.object({
1398
+ chain: chainOption5
945
1399
  }),
946
1400
  env: etherscanEnv,
947
- output: z6.object({
948
- chain: z6.string(),
949
- totalSupplyWei: z6.string()
1401
+ output: z7.object({
1402
+ chain: z7.string(),
1403
+ totalSupplyWei: z7.string()
950
1404
  }),
951
1405
  examples: [{ options: { chain: "ethereum" }, description: "Get total ETH supply" }],
952
1406
  async run(c) {
953
1407
  const apiKey = c.env.ETHERSCAN_API_KEY;
954
1408
  const chainId = resolveChainId(c.options.chain);
955
1409
  const client = createEtherscanClient(apiKey);
956
- const supply = await withRateLimit5(
1410
+ const supply = await withRateLimit6(
957
1411
  () => client.call(
958
1412
  {
959
1413
  chainid: chainId,
960
1414
  module: "stats",
961
1415
  action: "ethsupply"
962
1416
  },
963
- z6.string()
1417
+ z7.string()
964
1418
  ),
965
- rateLimiter4
1419
+ rateLimiter5
966
1420
  );
967
1421
  return c.ok(
968
1422
  {
@@ -985,68 +1439,68 @@ statsCli.command("ethsupply", {
985
1439
 
986
1440
  // src/commands/token.ts
987
1441
  import {
988
- checksumAddress as checksumAddress3,
989
- createRateLimiter as createRateLimiter6,
990
- isAddress as isAddress3,
991
- withRateLimit as withRateLimit6
1442
+ checksumAddress as checksumAddress4,
1443
+ createRateLimiter as createRateLimiter7,
1444
+ isAddress as isAddress4,
1445
+ withRateLimit as withRateLimit7
992
1446
  } from "@spectratools/cli-shared";
993
- import { Cli as Cli5, z as z7 } from "incur";
994
- var rateLimiter5 = createRateLimiter6({ requestsPerSecond: 5 });
995
- var chainOption5 = z7.string().default(DEFAULT_CHAIN).describe(
1447
+ import { Cli as Cli6, z as z8 } from "incur";
1448
+ var rateLimiter6 = createRateLimiter7({ requestsPerSecond: 5 });
1449
+ var chainOption6 = z8.string().default(DEFAULT_CHAIN).describe(
996
1450
  "Chain name (default: abstract). Options: ethereum, base, arbitrum, optimism, polygon, ..."
997
1451
  );
998
- var tokenCli = Cli5.create("token", {
1452
+ var tokenCli = Cli6.create("token", {
999
1453
  description: "Query token metadata, holders, and supply."
1000
1454
  });
1001
- var tokenInfoSchema = z7.object({
1002
- contractAddress: z7.string(),
1003
- tokenName: z7.string(),
1004
- symbol: z7.string(),
1005
- divisor: z7.string(),
1006
- tokenType: z7.string(),
1007
- totalSupply: z7.string(),
1008
- blueCheckmark: z7.string(),
1009
- description: z7.string(),
1010
- website: z7.string(),
1011
- email: z7.string(),
1012
- blog: z7.string(),
1013
- reddit: z7.string(),
1014
- slack: z7.string(),
1015
- facebook: z7.string(),
1016
- twitter: z7.string(),
1017
- bitcointalk: z7.string(),
1018
- github: z7.string(),
1019
- telegram: z7.string(),
1020
- wechat: z7.string(),
1021
- linkedin: z7.string(),
1022
- discord: z7.string(),
1023
- whitepaper: z7.string(),
1024
- tokenPriceUSD: z7.string()
1455
+ var tokenInfoSchema = z8.object({
1456
+ contractAddress: z8.string(),
1457
+ tokenName: z8.string(),
1458
+ symbol: z8.string(),
1459
+ divisor: z8.string(),
1460
+ tokenType: z8.string(),
1461
+ totalSupply: z8.string(),
1462
+ blueCheckmark: z8.string(),
1463
+ description: z8.string(),
1464
+ website: z8.string(),
1465
+ email: z8.string(),
1466
+ blog: z8.string(),
1467
+ reddit: z8.string(),
1468
+ slack: z8.string(),
1469
+ facebook: z8.string(),
1470
+ twitter: z8.string(),
1471
+ bitcointalk: z8.string(),
1472
+ github: z8.string(),
1473
+ telegram: z8.string(),
1474
+ wechat: z8.string(),
1475
+ linkedin: z8.string(),
1476
+ discord: z8.string(),
1477
+ whitepaper: z8.string(),
1478
+ tokenPriceUSD: z8.string()
1025
1479
  });
1026
- var holderEntrySchema = z7.object({
1027
- TokenHolderAddress: z7.string(),
1028
- TokenHolderQuantity: z7.string()
1480
+ var holderEntrySchema = z8.object({
1481
+ TokenHolderAddress: z8.string(),
1482
+ TokenHolderQuantity: z8.string()
1029
1483
  });
1030
1484
  tokenCli.command("info", {
1031
1485
  description: "Get metadata for a token contract.",
1032
- args: z7.object({
1033
- contractaddress: z7.string().describe("Token contract address")
1486
+ args: z8.object({
1487
+ contractaddress: z8.string().describe("Token contract address")
1034
1488
  }),
1035
- options: z7.object({
1036
- chain: chainOption5
1489
+ options: z8.object({
1490
+ chain: chainOption6
1037
1491
  }),
1038
1492
  env: etherscanEnv,
1039
- output: z7.object({
1040
- address: z7.string(),
1041
- chain: z7.string(),
1042
- name: z7.string(),
1043
- symbol: z7.string(),
1044
- type: z7.string(),
1045
- totalSupply: z7.string(),
1046
- decimals: z7.string(),
1047
- priceUsd: z7.string().optional(),
1048
- website: z7.string().optional(),
1049
- description: z7.string().optional()
1493
+ output: z8.object({
1494
+ address: z8.string(),
1495
+ chain: z8.string(),
1496
+ name: z8.string(),
1497
+ symbol: z8.string(),
1498
+ type: z8.string(),
1499
+ totalSupply: z8.string(),
1500
+ decimals: z8.string(),
1501
+ priceUsd: z8.string().optional(),
1502
+ website: z8.string().optional(),
1503
+ description: z8.string().optional()
1050
1504
  }),
1051
1505
  examples: [
1052
1506
  {
@@ -1056,7 +1510,7 @@ tokenCli.command("info", {
1056
1510
  }
1057
1511
  ],
1058
1512
  async run(c) {
1059
- if (!isAddress3(c.args.contractaddress)) {
1513
+ if (!isAddress4(c.args.contractaddress)) {
1060
1514
  return c.error({
1061
1515
  code: "INVALID_ADDRESS",
1062
1516
  message: `Invalid contract address: "${c.args.contractaddress}". Use a valid 0x-prefixed 20-byte hex address.`
@@ -1064,9 +1518,9 @@ tokenCli.command("info", {
1064
1518
  }
1065
1519
  const apiKey = c.env.ETHERSCAN_API_KEY;
1066
1520
  const chainId = resolveChainId(c.options.chain);
1067
- const address = checksumAddress3(c.args.contractaddress);
1521
+ const address = checksumAddress4(c.args.contractaddress);
1068
1522
  const client = createEtherscanClient(apiKey);
1069
- const results = await withRateLimit6(
1523
+ const results = await withRateLimit7(
1070
1524
  () => client.call(
1071
1525
  {
1072
1526
  chainid: chainId,
@@ -1074,9 +1528,9 @@ tokenCli.command("info", {
1074
1528
  action: "tokeninfo",
1075
1529
  contractaddress: address
1076
1530
  },
1077
- z7.array(tokenInfoSchema)
1531
+ z8.array(tokenInfoSchema)
1078
1532
  ),
1079
- rateLimiter5
1533
+ rateLimiter6
1080
1534
  );
1081
1535
  const info = results[0];
1082
1536
  if (!info) {
@@ -1116,24 +1570,24 @@ tokenCli.command("info", {
1116
1570
  });
1117
1571
  tokenCli.command("holders", {
1118
1572
  description: "List top token holders.",
1119
- args: z7.object({
1120
- contractaddress: z7.string().describe("Token contract address")
1573
+ args: z8.object({
1574
+ contractaddress: z8.string().describe("Token contract address")
1121
1575
  }),
1122
- options: z7.object({
1123
- page: z7.number().optional().default(1).describe("Page number"),
1124
- offset: z7.number().optional().default(10).describe("Results per page"),
1125
- chain: chainOption5
1576
+ options: z8.object({
1577
+ page: z8.number().optional().default(1).describe("Page number"),
1578
+ offset: z8.number().optional().default(10).describe("Results per page"),
1579
+ chain: chainOption6
1126
1580
  }),
1127
1581
  env: etherscanEnv,
1128
- output: z7.object({
1129
- contractAddress: z7.string(),
1130
- chain: z7.string(),
1131
- count: z7.number(),
1132
- holders: z7.array(
1133
- z7.object({
1134
- rank: z7.number(),
1135
- address: z7.string(),
1136
- quantity: z7.string()
1582
+ output: z8.object({
1583
+ contractAddress: z8.string(),
1584
+ chain: z8.string(),
1585
+ count: z8.number(),
1586
+ holders: z8.array(
1587
+ z8.object({
1588
+ rank: z8.number(),
1589
+ address: z8.string(),
1590
+ quantity: z8.string()
1137
1591
  })
1138
1592
  )
1139
1593
  }),
@@ -1145,7 +1599,7 @@ tokenCli.command("holders", {
1145
1599
  }
1146
1600
  ],
1147
1601
  async run(c) {
1148
- if (!isAddress3(c.args.contractaddress)) {
1602
+ if (!isAddress4(c.args.contractaddress)) {
1149
1603
  return c.error({
1150
1604
  code: "INVALID_ADDRESS",
1151
1605
  message: `Invalid contract address: "${c.args.contractaddress}". Use a valid 0x-prefixed 20-byte hex address.`
@@ -1153,9 +1607,9 @@ tokenCli.command("holders", {
1153
1607
  }
1154
1608
  const apiKey = c.env.ETHERSCAN_API_KEY;
1155
1609
  const chainId = resolveChainId(c.options.chain);
1156
- const address = checksumAddress3(c.args.contractaddress);
1610
+ const address = checksumAddress4(c.args.contractaddress);
1157
1611
  const client = createEtherscanClient(apiKey);
1158
- const holders = await withRateLimit6(
1612
+ const holders = await withRateLimit7(
1159
1613
  () => client.call(
1160
1614
  {
1161
1615
  chainid: chainId,
@@ -1165,13 +1619,13 @@ tokenCli.command("holders", {
1165
1619
  page: c.options.page,
1166
1620
  offset: c.options.offset
1167
1621
  },
1168
- z7.array(holderEntrySchema)
1622
+ z8.array(holderEntrySchema)
1169
1623
  ),
1170
- rateLimiter5
1624
+ rateLimiter6
1171
1625
  );
1172
1626
  const formatted = holders.map((h, i) => ({
1173
1627
  rank: (c.options.page - 1) * c.options.offset + i + 1,
1174
- address: checksumAddress3(h.TokenHolderAddress),
1628
+ address: checksumAddress4(h.TokenHolderAddress),
1175
1629
  quantity: h.TokenHolderQuantity
1176
1630
  }));
1177
1631
  return c.ok(
@@ -1197,17 +1651,17 @@ tokenCli.command("holders", {
1197
1651
  });
1198
1652
  tokenCli.command("supply", {
1199
1653
  description: "Get total token supply.",
1200
- args: z7.object({
1201
- contractaddress: z7.string().describe("Token contract address")
1654
+ args: z8.object({
1655
+ contractaddress: z8.string().describe("Token contract address")
1202
1656
  }),
1203
- options: z7.object({
1204
- chain: chainOption5
1657
+ options: z8.object({
1658
+ chain: chainOption6
1205
1659
  }),
1206
1660
  env: etherscanEnv,
1207
- output: z7.object({
1208
- contractAddress: z7.string(),
1209
- chain: z7.string(),
1210
- totalSupply: z7.string()
1661
+ output: z8.object({
1662
+ contractAddress: z8.string(),
1663
+ chain: z8.string(),
1664
+ totalSupply: z8.string()
1211
1665
  }),
1212
1666
  examples: [
1213
1667
  {
@@ -1217,7 +1671,7 @@ tokenCli.command("supply", {
1217
1671
  }
1218
1672
  ],
1219
1673
  async run(c) {
1220
- if (!isAddress3(c.args.contractaddress)) {
1674
+ if (!isAddress4(c.args.contractaddress)) {
1221
1675
  return c.error({
1222
1676
  code: "INVALID_ADDRESS",
1223
1677
  message: `Invalid contract address: "${c.args.contractaddress}". Use a valid 0x-prefixed 20-byte hex address.`
@@ -1225,9 +1679,9 @@ tokenCli.command("supply", {
1225
1679
  }
1226
1680
  const apiKey = c.env.ETHERSCAN_API_KEY;
1227
1681
  const chainId = resolveChainId(c.options.chain);
1228
- const address = checksumAddress3(c.args.contractaddress);
1682
+ const address = checksumAddress4(c.args.contractaddress);
1229
1683
  const client = createEtherscanClient(apiKey);
1230
- const supply = await withRateLimit6(
1684
+ const supply = await withRateLimit7(
1231
1685
  () => client.call(
1232
1686
  {
1233
1687
  chainid: chainId,
@@ -1235,9 +1689,9 @@ tokenCli.command("supply", {
1235
1689
  action: "tokensupply",
1236
1690
  contractaddress: address
1237
1691
  },
1238
- z7.string()
1692
+ z8.string()
1239
1693
  ),
1240
- rateLimiter5
1694
+ rateLimiter6
1241
1695
  );
1242
1696
  return c.ok(
1243
1697
  { contractAddress: address, chain: c.options.chain, totalSupply: supply },
@@ -1257,65 +1711,65 @@ tokenCli.command("supply", {
1257
1711
  });
1258
1712
 
1259
1713
  // src/commands/tx.ts
1260
- import { createRateLimiter as createRateLimiter7, withRateLimit as withRateLimit7 } from "@spectratools/cli-shared";
1261
- import { Cli as Cli6, z as z8 } from "incur";
1262
- var rateLimiter6 = createRateLimiter7({ requestsPerSecond: 5 });
1714
+ import { createRateLimiter as createRateLimiter8, withRateLimit as withRateLimit8 } from "@spectratools/cli-shared";
1715
+ import { Cli as Cli7, z as z9 } from "incur";
1716
+ var rateLimiter7 = createRateLimiter8({ requestsPerSecond: 5 });
1263
1717
  function hexToDecimal(hex) {
1264
1718
  return BigInt(hex).toString();
1265
1719
  }
1266
- var chainOption6 = z8.string().default(DEFAULT_CHAIN).describe(
1720
+ var chainOption7 = z9.string().default(DEFAULT_CHAIN).describe(
1267
1721
  "Chain name (default: abstract). Options: ethereum, base, arbitrum, optimism, polygon, ..."
1268
1722
  );
1269
- var txCli = Cli6.create("tx", {
1723
+ var txCli = Cli7.create("tx", {
1270
1724
  description: "Query transaction details, receipts, and execution status."
1271
1725
  });
1272
- var transactionInfoSchema = z8.object({
1273
- hash: z8.string(),
1274
- from: z8.string(),
1275
- to: z8.string().nullable(),
1276
- value: z8.string(),
1277
- gas: z8.string(),
1278
- gasPrice: z8.string(),
1279
- nonce: z8.string(),
1280
- blockNumber: z8.string(),
1281
- blockHash: z8.string(),
1282
- input: z8.string()
1726
+ var transactionInfoSchema = z9.object({
1727
+ hash: z9.string(),
1728
+ from: z9.string(),
1729
+ to: z9.string().nullable(),
1730
+ value: z9.string(),
1731
+ gas: z9.string(),
1732
+ gasPrice: z9.string(),
1733
+ nonce: z9.string(),
1734
+ blockNumber: z9.string(),
1735
+ blockHash: z9.string(),
1736
+ input: z9.string()
1283
1737
  });
1284
- var transactionReceiptSchema = z8.object({
1285
- transactionHash: z8.string(),
1286
- blockNumber: z8.string(),
1287
- blockHash: z8.string(),
1288
- from: z8.string(),
1289
- to: z8.string().nullable(),
1290
- status: z8.string(),
1291
- gasUsed: z8.string(),
1292
- cumulativeGasUsed: z8.string(),
1293
- contractAddress: z8.string().nullable(),
1294
- logs: z8.array(z8.unknown())
1738
+ var transactionReceiptSchema = z9.object({
1739
+ transactionHash: z9.string(),
1740
+ blockNumber: z9.string(),
1741
+ blockHash: z9.string(),
1742
+ from: z9.string(),
1743
+ to: z9.string().nullable(),
1744
+ status: z9.string(),
1745
+ gasUsed: z9.string(),
1746
+ cumulativeGasUsed: z9.string(),
1747
+ contractAddress: z9.string().nullable(),
1748
+ logs: z9.array(z9.unknown())
1295
1749
  });
1296
- var txStatusSchema = z8.object({
1297
- isError: z8.string(),
1298
- errDescription: z8.string()
1750
+ var txStatusSchema = z9.object({
1751
+ isError: z9.string(),
1752
+ errDescription: z9.string()
1299
1753
  });
1300
1754
  txCli.command("info", {
1301
1755
  description: "Get transaction details by hash.",
1302
- args: z8.object({
1303
- txhash: z8.string().describe("Transaction hash")
1756
+ args: z9.object({
1757
+ txhash: z9.string().describe("Transaction hash")
1304
1758
  }),
1305
- options: z8.object({
1306
- chain: chainOption6
1759
+ options: z9.object({
1760
+ chain: chainOption7
1307
1761
  }),
1308
1762
  env: etherscanEnv,
1309
- output: z8.object({
1310
- hash: z8.string(),
1311
- from: z8.string(),
1312
- to: z8.string().nullable(),
1313
- value: z8.string(),
1314
- gas: z8.string(),
1315
- gasPrice: z8.string(),
1316
- nonce: z8.string(),
1317
- block: z8.string(),
1318
- chain: z8.string()
1763
+ output: z9.object({
1764
+ hash: z9.string(),
1765
+ from: z9.string(),
1766
+ to: z9.string().nullable(),
1767
+ value: z9.string(),
1768
+ gas: z9.string(),
1769
+ gasPrice: z9.string(),
1770
+ nonce: z9.string(),
1771
+ block: z9.string(),
1772
+ chain: z9.string()
1319
1773
  }),
1320
1774
  examples: [
1321
1775
  {
@@ -1328,7 +1782,7 @@ txCli.command("info", {
1328
1782
  const apiKey = c.env.ETHERSCAN_API_KEY;
1329
1783
  const chainId = resolveChainId(c.options.chain);
1330
1784
  const client = createEtherscanClient(apiKey);
1331
- const tx = await withRateLimit7(
1785
+ const tx = await withRateLimit8(
1332
1786
  () => client.callProxy(
1333
1787
  {
1334
1788
  chainid: chainId,
@@ -1338,7 +1792,7 @@ txCli.command("info", {
1338
1792
  },
1339
1793
  transactionInfoSchema
1340
1794
  ),
1341
- rateLimiter6
1795
+ rateLimiter7
1342
1796
  );
1343
1797
  return c.ok(
1344
1798
  {
@@ -1373,23 +1827,23 @@ txCli.command("info", {
1373
1827
  });
1374
1828
  txCli.command("receipt", {
1375
1829
  description: "Get the receipt for a transaction.",
1376
- args: z8.object({
1377
- txhash: z8.string().describe("Transaction hash")
1830
+ args: z9.object({
1831
+ txhash: z9.string().describe("Transaction hash")
1378
1832
  }),
1379
- options: z8.object({
1380
- chain: chainOption6
1833
+ options: z9.object({
1834
+ chain: chainOption7
1381
1835
  }),
1382
1836
  env: etherscanEnv,
1383
- output: z8.object({
1384
- hash: z8.string(),
1385
- block: z8.string(),
1386
- from: z8.string(),
1387
- to: z8.string().nullable(),
1388
- status: z8.string(),
1389
- gasUsed: z8.string(),
1390
- contractAddress: z8.string().nullable(),
1391
- logCount: z8.number(),
1392
- chain: z8.string()
1837
+ output: z9.object({
1838
+ hash: z9.string(),
1839
+ block: z9.string(),
1840
+ from: z9.string(),
1841
+ to: z9.string().nullable(),
1842
+ status: z9.string(),
1843
+ gasUsed: z9.string(),
1844
+ contractAddress: z9.string().nullable(),
1845
+ logCount: z9.number(),
1846
+ chain: z9.string()
1393
1847
  }),
1394
1848
  examples: [
1395
1849
  {
@@ -1402,7 +1856,7 @@ txCli.command("receipt", {
1402
1856
  const apiKey = c.env.ETHERSCAN_API_KEY;
1403
1857
  const chainId = resolveChainId(c.options.chain);
1404
1858
  const client = createEtherscanClient(apiKey);
1405
- const receipt = await withRateLimit7(
1859
+ const receipt = await withRateLimit8(
1406
1860
  () => client.callProxy(
1407
1861
  {
1408
1862
  chainid: chainId,
@@ -1412,7 +1866,7 @@ txCli.command("receipt", {
1412
1866
  },
1413
1867
  transactionReceiptSchema
1414
1868
  ),
1415
- rateLimiter6
1869
+ rateLimiter7
1416
1870
  );
1417
1871
  return c.ok(
1418
1872
  {
@@ -1442,18 +1896,18 @@ txCli.command("receipt", {
1442
1896
  });
1443
1897
  txCli.command("status", {
1444
1898
  description: "Check whether a transaction succeeded or failed.",
1445
- args: z8.object({
1446
- txhash: z8.string().describe("Transaction hash")
1899
+ args: z9.object({
1900
+ txhash: z9.string().describe("Transaction hash")
1447
1901
  }),
1448
- options: z8.object({
1449
- chain: chainOption6
1902
+ options: z9.object({
1903
+ chain: chainOption7
1450
1904
  }),
1451
1905
  env: etherscanEnv,
1452
- output: z8.object({
1453
- hash: z8.string(),
1454
- status: z8.string(),
1455
- error: z8.string().optional(),
1456
- chain: z8.string()
1906
+ output: z9.object({
1907
+ hash: z9.string(),
1908
+ status: z9.string(),
1909
+ error: z9.string().optional(),
1910
+ chain: z9.string()
1457
1911
  }),
1458
1912
  examples: [
1459
1913
  {
@@ -1466,7 +1920,7 @@ txCli.command("status", {
1466
1920
  const apiKey = c.env.ETHERSCAN_API_KEY;
1467
1921
  const chainId = resolveChainId(c.options.chain);
1468
1922
  const client = createEtherscanClient(apiKey);
1469
- const result = await withRateLimit7(
1923
+ const result = await withRateLimit8(
1470
1924
  () => client.call(
1471
1925
  {
1472
1926
  chainid: chainId,
@@ -1476,7 +1930,7 @@ txCli.command("status", {
1476
1930
  },
1477
1931
  txStatusSchema
1478
1932
  ),
1479
- rateLimiter6
1933
+ rateLimiter7
1480
1934
  );
1481
1935
  return c.ok(
1482
1936
  {
@@ -1503,7 +1957,7 @@ txCli.command("status", {
1503
1957
  // src/cli.ts
1504
1958
  var __dirname = dirname(fileURLToPath(import.meta.url));
1505
1959
  var pkg = JSON.parse(readFileSync(resolve(__dirname, "../package.json"), "utf8"));
1506
- var cli = Cli7.create("etherscan", {
1960
+ var cli = Cli8.create("etherscan", {
1507
1961
  version: pkg.version,
1508
1962
  description: "Query Etherscan API data from the command line."
1509
1963
  });
@@ -1513,6 +1967,7 @@ cli.command(txCli);
1513
1967
  cli.command(tokenCli);
1514
1968
  cli.command(gasCli);
1515
1969
  cli.command(statsCli);
1970
+ cli.command(logsCli);
1516
1971
  var isMain = (() => {
1517
1972
  const entrypoint = process.argv[1];
1518
1973
  if (!entrypoint) {