@spectratools/etherscan-cli 0.2.0 → 0.2.2

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 (3) hide show
  1. package/README.md +14 -0
  2. package/dist/cli.js +137 -37
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -21,6 +21,20 @@ etherscan-cli skills add
21
21
  etherscan-cli mcp add
22
22
  ```
23
23
 
24
+ ## ⚠️ Default Chain: Abstract
25
+
26
+ All commands default to the **Abstract** chain (`--chain abstract`) when no `--chain` flag is provided. If you are querying Ethereum mainnet, you must explicitly pass `--chain ethereum`:
27
+
28
+ ```bash
29
+ # Queries Abstract (default)
30
+ etherscan-cli account balance 0x...
31
+
32
+ # Queries Ethereum mainnet (explicit)
33
+ etherscan-cli account balance 0x... --chain ethereum
34
+ ```
35
+
36
+ Supported chains: `abstract`, `ethereum`, `base`, `arbitrum`, `optimism`, `polygon`, `avalanche`, `bsc`, `linea`, `scroll`, `zksync`, `mantle`, `blast`, `mode`, `sepolia`, `goerli`.
37
+
24
38
  ## Configuration
25
39
 
26
40
  ```bash
package/dist/cli.js CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { realpathSync } from "fs";
4
+ import { readFileSync, realpathSync } from "fs";
5
+ import { dirname, resolve } from "path";
5
6
  import { fileURLToPath } from "url";
6
7
  import { Cli as Cli7 } from "incur";
7
8
 
@@ -10,6 +11,7 @@ import {
10
11
  checksumAddress,
11
12
  createRateLimiter as createRateLimiter2,
12
13
  formatTimestamp,
14
+ isAddress,
13
15
  weiToEth,
14
16
  withRateLimit as withRateLimit2
15
17
  } from "@spectratools/cli-shared";
@@ -138,7 +140,9 @@ function resolveChainId(chain) {
138
140
 
139
141
  // src/commands/account.ts
140
142
  var rateLimiter = createRateLimiter2({ requestsPerSecond: 5 });
141
- var chainOption = z3.string().default(DEFAULT_CHAIN).describe("Chain name (abstract, ethereum, base, arbitrum, ...)");
143
+ var chainOption = z3.string().default(DEFAULT_CHAIN).describe(
144
+ "Chain name (default: abstract). Options: ethereum, base, arbitrum, optimism, polygon, ..."
145
+ );
142
146
  var txListItemSchema = z3.object({
143
147
  hash: z3.string(),
144
148
  from: z3.string(),
@@ -193,6 +197,12 @@ accountCli.command("balance", {
193
197
  }
194
198
  ],
195
199
  async run(c) {
200
+ if (!isAddress(c.args.address)) {
201
+ return c.error({
202
+ code: "INVALID_ADDRESS",
203
+ message: `Invalid address: "${c.args.address}". Use a valid 0x-prefixed 20-byte hex address.`
204
+ });
205
+ }
196
206
  const apiKey = c.env.ETHERSCAN_API_KEY;
197
207
  const chainId = resolveChainId(c.options.chain);
198
208
  const address = normalizeAddress(c.args.address);
@@ -212,10 +222,14 @@ accountCli.command("balance", {
212
222
  );
213
223
  return c.ok(
214
224
  { address, wei, eth: weiToEth(wei), chain: c.options.chain },
215
- {
225
+ c.format === "json" || c.format === "jsonl" ? void 0 : {
216
226
  cta: {
217
227
  commands: [
218
- { command: "account txlist", args: { address }, description: "List transactions" },
228
+ {
229
+ command: "account txlist",
230
+ args: { address },
231
+ description: "List transactions"
232
+ },
219
233
  {
220
234
  command: "account tokentx",
221
235
  args: { address },
@@ -267,6 +281,12 @@ accountCli.command("txlist", {
267
281
  }
268
282
  ],
269
283
  async run(c) {
284
+ if (!isAddress(c.args.address)) {
285
+ return c.error({
286
+ code: "INVALID_ADDRESS",
287
+ message: `Invalid address: "${c.args.address}". Use a valid 0x-prefixed 20-byte hex address.`
288
+ });
289
+ }
270
290
  const apiKey = c.env.ETHERSCAN_API_KEY;
271
291
  const chainId = resolveChainId(c.options.chain);
272
292
  const address = normalizeAddress(c.args.address);
@@ -302,7 +322,7 @@ accountCli.command("txlist", {
302
322
  const firstHash = formatted[0]?.hash;
303
323
  return c.ok(
304
324
  { address, chain: c.options.chain, count: formatted.length, transactions: formatted },
305
- {
325
+ c.format === "json" || c.format === "jsonl" ? void 0 : {
306
326
  cta: {
307
327
  commands: firstHash ? [
308
328
  {
@@ -354,6 +374,12 @@ accountCli.command("tokentx", {
354
374
  }
355
375
  ],
356
376
  async run(c) {
377
+ if (!isAddress(c.args.address)) {
378
+ return c.error({
379
+ code: "INVALID_ADDRESS",
380
+ message: `Invalid address: "${c.args.address}". Use a valid 0x-prefixed 20-byte hex address.`
381
+ });
382
+ }
357
383
  const apiKey = c.env.ETHERSCAN_API_KEY;
358
384
  const chainId = resolveChainId(c.options.chain);
359
385
  const address = normalizeAddress(c.args.address);
@@ -386,7 +412,7 @@ accountCli.command("tokentx", {
386
412
  }));
387
413
  return c.ok(
388
414
  { address, chain: c.options.chain, count: formatted.length, transfers: formatted },
389
- {
415
+ c.format === "json" || c.format === "jsonl" ? void 0 : {
390
416
  cta: {
391
417
  commands: [
392
418
  {
@@ -424,6 +450,18 @@ accountCli.command("tokenbalance", {
424
450
  }
425
451
  ],
426
452
  async run(c) {
453
+ if (!isAddress(c.args.address)) {
454
+ return c.error({
455
+ code: "INVALID_ADDRESS",
456
+ message: `Invalid address: "${c.args.address}". Use a valid 0x-prefixed 20-byte hex address.`
457
+ });
458
+ }
459
+ if (!isAddress(c.options.contractaddress)) {
460
+ return c.error({
461
+ code: "INVALID_ADDRESS",
462
+ message: `Invalid contract address: "${c.options.contractaddress}". Use a valid 0x-prefixed 20-byte hex address.`
463
+ });
464
+ }
427
465
  const apiKey = c.env.ETHERSCAN_API_KEY;
428
466
  const chainId = resolveChainId(c.options.chain);
429
467
  const address = normalizeAddress(c.args.address);
@@ -445,7 +483,7 @@ accountCli.command("tokenbalance", {
445
483
  );
446
484
  return c.ok(
447
485
  { address, contract, balance, chain: c.options.chain },
448
- {
486
+ c.format === "json" || c.format === "jsonl" ? void 0 : {
449
487
  cta: {
450
488
  commands: [
451
489
  {
@@ -461,10 +499,17 @@ accountCli.command("tokenbalance", {
461
499
  });
462
500
 
463
501
  // src/commands/contract.ts
464
- import { checksumAddress as checksumAddress2, createRateLimiter as createRateLimiter3, withRateLimit as withRateLimit3 } from "@spectratools/cli-shared";
502
+ import {
503
+ checksumAddress as checksumAddress2,
504
+ createRateLimiter as createRateLimiter3,
505
+ isAddress as isAddress2,
506
+ withRateLimit as withRateLimit3
507
+ } from "@spectratools/cli-shared";
465
508
  import { Cli as Cli2, z as z4 } from "incur";
466
509
  var rateLimiter2 = createRateLimiter3({ requestsPerSecond: 5 });
467
- var chainOption2 = z4.string().default(DEFAULT_CHAIN).describe("Chain name (abstract, ethereum, base, arbitrum, ...)");
510
+ var chainOption2 = z4.string().default(DEFAULT_CHAIN).describe(
511
+ "Chain name (default: abstract). Options: ethereum, base, arbitrum, optimism, polygon, ..."
512
+ );
468
513
  var contractCli = Cli2.create("contract", {
469
514
  description: "Query contract ABI, source code, and deployment metadata."
470
515
  });
@@ -507,6 +552,12 @@ contractCli.command("abi", {
507
552
  }
508
553
  ],
509
554
  async run(c) {
555
+ if (!isAddress2(c.args.address)) {
556
+ return c.error({
557
+ code: "INVALID_ADDRESS",
558
+ message: `Invalid address: "${c.args.address}". Use a valid 0x-prefixed 20-byte hex address.`
559
+ });
560
+ }
510
561
  const apiKey = c.env.ETHERSCAN_API_KEY;
511
562
  const chainId = resolveChainId(c.options.chain);
512
563
  const address = checksumAddress2(c.args.address);
@@ -525,7 +576,7 @@ contractCli.command("abi", {
525
576
  );
526
577
  return c.ok(
527
578
  { address, chain: c.options.chain, abi: JSON.parse(abi) },
528
- {
579
+ c.format === "json" || c.format === "jsonl" ? void 0 : {
529
580
  cta: {
530
581
  commands: [
531
582
  {
@@ -569,6 +620,12 @@ contractCli.command("source", {
569
620
  }
570
621
  ],
571
622
  async run(c) {
623
+ if (!isAddress2(c.args.address)) {
624
+ return c.error({
625
+ code: "INVALID_ADDRESS",
626
+ message: `Invalid address: "${c.args.address}". Use a valid 0x-prefixed 20-byte hex address.`
627
+ });
628
+ }
572
629
  const apiKey = c.env.ETHERSCAN_API_KEY;
573
630
  const chainId = resolveChainId(c.options.chain);
574
631
  const address = checksumAddress2(c.args.address);
@@ -603,7 +660,7 @@ contractCli.command("source", {
603
660
  sourceCode: result.SourceCode,
604
661
  constructorArguments: result.ConstructorArguments
605
662
  },
606
- {
663
+ c.format === "json" || c.format === "jsonl" ? void 0 : {
607
664
  cta: {
608
665
  commands: [
609
666
  {
@@ -640,6 +697,12 @@ contractCli.command("creation", {
640
697
  }
641
698
  ],
642
699
  async run(c) {
700
+ if (!isAddress2(c.args.address)) {
701
+ return c.error({
702
+ code: "INVALID_ADDRESS",
703
+ message: `Invalid address: "${c.args.address}". Use a valid 0x-prefixed 20-byte hex address.`
704
+ });
705
+ }
643
706
  const apiKey = c.env.ETHERSCAN_API_KEY;
644
707
  const chainId = resolveChainId(c.options.chain);
645
708
  const address = checksumAddress2(c.args.address);
@@ -667,7 +730,7 @@ contractCli.command("creation", {
667
730
  txHash: result.txHash,
668
731
  chain: c.options.chain
669
732
  },
670
- {
733
+ c.format === "json" || c.format === "jsonl" ? void 0 : {
671
734
  cta: {
672
735
  commands: [
673
736
  {
@@ -686,7 +749,9 @@ contractCli.command("creation", {
686
749
  import { createRateLimiter as createRateLimiter4, withRateLimit as withRateLimit4 } from "@spectratools/cli-shared";
687
750
  import { Cli as Cli3, z as z5 } from "incur";
688
751
  var rateLimiter3 = createRateLimiter4({ requestsPerSecond: 5 });
689
- var chainOption3 = z5.string().default(DEFAULT_CHAIN).describe("Chain name (abstract, ethereum, base, arbitrum, ...)");
752
+ var chainOption3 = z5.string().default(DEFAULT_CHAIN).describe(
753
+ "Chain name (default: abstract). Options: ethereum, base, arbitrum, optimism, polygon, ..."
754
+ );
690
755
  var gasCli = Cli3.create("gas", {
691
756
  description: "Query gas oracle data and estimate confirmation latency."
692
757
  });
@@ -739,7 +804,7 @@ gasCli.command("oracle", {
739
804
  baseFee: `${oracle.suggestBaseFee} Gwei`,
740
805
  gasUsedRatio: oracle.gasUsedRatio
741
806
  },
742
- {
807
+ c.format === "json" || c.format === "jsonl" ? void 0 : {
743
808
  cta: {
744
809
  commands: [
745
810
  {
@@ -793,7 +858,7 @@ gasCli.command("estimate", {
793
858
  gasprice: c.options.gasprice,
794
859
  estimatedSeconds: estimate
795
860
  },
796
- {
861
+ c.format === "json" || c.format === "jsonl" ? void 0 : {
797
862
  cta: {
798
863
  commands: [
799
864
  {
@@ -811,7 +876,9 @@ gasCli.command("estimate", {
811
876
  import { createRateLimiter as createRateLimiter5, withRateLimit as withRateLimit5 } from "@spectratools/cli-shared";
812
877
  import { Cli as Cli4, z as z6 } from "incur";
813
878
  var rateLimiter4 = createRateLimiter5({ requestsPerSecond: 5 });
814
- var chainOption4 = z6.string().default(DEFAULT_CHAIN).describe("Chain name (abstract, ethereum, base, arbitrum, ...)");
879
+ var chainOption4 = z6.string().default(DEFAULT_CHAIN).describe(
880
+ "Chain name (default: abstract). Options: ethereum, base, arbitrum, optimism, polygon, ..."
881
+ );
815
882
  var statsCli = Cli4.create("stats", {
816
883
  description: "Query ETH price and total supply statistics."
817
884
  });
@@ -858,7 +925,7 @@ statsCli.command("ethprice", {
858
925
  usdTimestamp: new Date(Number(price.ethusd_timestamp) * 1e3).toISOString(),
859
926
  btcTimestamp: new Date(Number(price.ethbtc_timestamp) * 1e3).toISOString()
860
927
  },
861
- {
928
+ c.format === "json" || c.format === "jsonl" ? void 0 : {
862
929
  cta: {
863
930
  commands: [
864
931
  {
@@ -902,7 +969,7 @@ statsCli.command("ethsupply", {
902
969
  chain: c.options.chain,
903
970
  totalSupplyWei: supply
904
971
  },
905
- {
972
+ c.format === "json" || c.format === "jsonl" ? void 0 : {
906
973
  cta: {
907
974
  commands: [
908
975
  {
@@ -917,10 +984,17 @@ statsCli.command("ethsupply", {
917
984
  });
918
985
 
919
986
  // src/commands/token.ts
920
- import { checksumAddress as checksumAddress3, createRateLimiter as createRateLimiter6, withRateLimit as withRateLimit6 } from "@spectratools/cli-shared";
987
+ import {
988
+ checksumAddress as checksumAddress3,
989
+ createRateLimiter as createRateLimiter6,
990
+ isAddress as isAddress3,
991
+ withRateLimit as withRateLimit6
992
+ } from "@spectratools/cli-shared";
921
993
  import { Cli as Cli5, z as z7 } from "incur";
922
994
  var rateLimiter5 = createRateLimiter6({ requestsPerSecond: 5 });
923
- var chainOption5 = z7.string().default(DEFAULT_CHAIN).describe("Chain name (abstract, ethereum, base, arbitrum, ...)");
995
+ var chainOption5 = z7.string().default(DEFAULT_CHAIN).describe(
996
+ "Chain name (default: abstract). Options: ethereum, base, arbitrum, optimism, polygon, ..."
997
+ );
924
998
  var tokenCli = Cli5.create("token", {
925
999
  description: "Query token metadata, holders, and supply."
926
1000
  });
@@ -982,6 +1056,12 @@ tokenCli.command("info", {
982
1056
  }
983
1057
  ],
984
1058
  async run(c) {
1059
+ if (!isAddress3(c.args.contractaddress)) {
1060
+ return c.error({
1061
+ code: "INVALID_ADDRESS",
1062
+ message: `Invalid contract address: "${c.args.contractaddress}". Use a valid 0x-prefixed 20-byte hex address.`
1063
+ });
1064
+ }
985
1065
  const apiKey = c.env.ETHERSCAN_API_KEY;
986
1066
  const chainId = resolveChainId(c.options.chain);
987
1067
  const address = checksumAddress3(c.args.contractaddress);
@@ -1015,7 +1095,7 @@ tokenCli.command("info", {
1015
1095
  website: info.website || void 0,
1016
1096
  description: info.description || void 0
1017
1097
  },
1018
- {
1098
+ c.format === "json" || c.format === "jsonl" ? void 0 : {
1019
1099
  cta: {
1020
1100
  commands: [
1021
1101
  {
@@ -1065,6 +1145,12 @@ tokenCli.command("holders", {
1065
1145
  }
1066
1146
  ],
1067
1147
  async run(c) {
1148
+ if (!isAddress3(c.args.contractaddress)) {
1149
+ return c.error({
1150
+ code: "INVALID_ADDRESS",
1151
+ message: `Invalid contract address: "${c.args.contractaddress}". Use a valid 0x-prefixed 20-byte hex address.`
1152
+ });
1153
+ }
1068
1154
  const apiKey = c.env.ETHERSCAN_API_KEY;
1069
1155
  const chainId = resolveChainId(c.options.chain);
1070
1156
  const address = checksumAddress3(c.args.contractaddress);
@@ -1095,7 +1181,7 @@ tokenCli.command("holders", {
1095
1181
  count: formatted.length,
1096
1182
  holders: formatted
1097
1183
  },
1098
- {
1184
+ c.format === "json" || c.format === "jsonl" ? void 0 : {
1099
1185
  cta: {
1100
1186
  commands: [
1101
1187
  {
@@ -1131,6 +1217,12 @@ tokenCli.command("supply", {
1131
1217
  }
1132
1218
  ],
1133
1219
  async run(c) {
1220
+ if (!isAddress3(c.args.contractaddress)) {
1221
+ return c.error({
1222
+ code: "INVALID_ADDRESS",
1223
+ message: `Invalid contract address: "${c.args.contractaddress}". Use a valid 0x-prefixed 20-byte hex address.`
1224
+ });
1225
+ }
1134
1226
  const apiKey = c.env.ETHERSCAN_API_KEY;
1135
1227
  const chainId = resolveChainId(c.options.chain);
1136
1228
  const address = checksumAddress3(c.args.contractaddress);
@@ -1149,7 +1241,7 @@ tokenCli.command("supply", {
1149
1241
  );
1150
1242
  return c.ok(
1151
1243
  { contractAddress: address, chain: c.options.chain, totalSupply: supply },
1152
- {
1244
+ c.format === "json" || c.format === "jsonl" ? void 0 : {
1153
1245
  cta: {
1154
1246
  commands: [
1155
1247
  {
@@ -1168,7 +1260,12 @@ tokenCli.command("supply", {
1168
1260
  import { createRateLimiter as createRateLimiter7, withRateLimit as withRateLimit7 } from "@spectratools/cli-shared";
1169
1261
  import { Cli as Cli6, z as z8 } from "incur";
1170
1262
  var rateLimiter6 = createRateLimiter7({ requestsPerSecond: 5 });
1171
- var chainOption6 = z8.string().default(DEFAULT_CHAIN).describe("Chain name (abstract, ethereum, base, arbitrum, ...)");
1263
+ function hexToDecimal(hex) {
1264
+ return BigInt(hex).toString();
1265
+ }
1266
+ var chainOption6 = z8.string().default(DEFAULT_CHAIN).describe(
1267
+ "Chain name (default: abstract). Options: ethereum, base, arbitrum, optimism, polygon, ..."
1268
+ );
1172
1269
  var txCli = Cli6.create("tx", {
1173
1270
  description: "Query transaction details, receipts, and execution status."
1174
1271
  });
@@ -1197,7 +1294,7 @@ var transactionReceiptSchema = z8.object({
1197
1294
  logs: z8.array(z8.unknown())
1198
1295
  });
1199
1296
  var txStatusSchema = z8.object({
1200
- status: z8.string(),
1297
+ isError: z8.string(),
1201
1298
  errDescription: z8.string()
1202
1299
  });
1203
1300
  txCli.command("info", {
@@ -1248,14 +1345,14 @@ txCli.command("info", {
1248
1345
  hash: tx.hash,
1249
1346
  from: tx.from,
1250
1347
  to: tx.to,
1251
- value: tx.value,
1252
- gas: tx.gas,
1253
- gasPrice: tx.gasPrice,
1254
- nonce: tx.nonce,
1255
- block: tx.blockNumber,
1348
+ value: hexToDecimal(tx.value),
1349
+ gas: hexToDecimal(tx.gas),
1350
+ gasPrice: hexToDecimal(tx.gasPrice),
1351
+ nonce: hexToDecimal(tx.nonce),
1352
+ block: hexToDecimal(tx.blockNumber),
1256
1353
  chain: c.options.chain
1257
1354
  },
1258
- {
1355
+ c.format === "json" || c.format === "jsonl" ? void 0 : {
1259
1356
  cta: {
1260
1357
  commands: [
1261
1358
  {
@@ -1320,16 +1417,16 @@ txCli.command("receipt", {
1320
1417
  return c.ok(
1321
1418
  {
1322
1419
  hash: receipt.transactionHash,
1323
- block: receipt.blockNumber,
1420
+ block: hexToDecimal(receipt.blockNumber),
1324
1421
  from: receipt.from,
1325
1422
  to: receipt.to,
1326
1423
  status: receipt.status === "0x1" ? "success" : "failed",
1327
- gasUsed: receipt.gasUsed,
1424
+ gasUsed: hexToDecimal(receipt.gasUsed),
1328
1425
  contractAddress: receipt.contractAddress,
1329
1426
  logCount: receipt.logs.length,
1330
1427
  chain: c.options.chain
1331
1428
  },
1332
- {
1429
+ c.format === "json" || c.format === "jsonl" ? void 0 : {
1333
1430
  cta: {
1334
1431
  commands: [
1335
1432
  {
@@ -1374,7 +1471,7 @@ txCli.command("status", {
1374
1471
  {
1375
1472
  chainid: chainId,
1376
1473
  module: "transaction",
1377
- action: "gettxreceiptstatus",
1474
+ action: "getstatus",
1378
1475
  txhash: c.args.txhash
1379
1476
  },
1380
1477
  txStatusSchema
@@ -1384,11 +1481,11 @@ txCli.command("status", {
1384
1481
  return c.ok(
1385
1482
  {
1386
1483
  hash: c.args.txhash,
1387
- status: result.status === "1" ? "success" : "failed",
1484
+ status: result.isError === "0" ? "success" : "failed",
1388
1485
  error: result.errDescription || void 0,
1389
1486
  chain: c.options.chain
1390
1487
  },
1391
- {
1488
+ c.format === "json" || c.format === "jsonl" ? void 0 : {
1392
1489
  cta: {
1393
1490
  commands: [
1394
1491
  {
@@ -1404,7 +1501,10 @@ txCli.command("status", {
1404
1501
  });
1405
1502
 
1406
1503
  // src/cli.ts
1504
+ var __dirname = dirname(fileURLToPath(import.meta.url));
1505
+ var pkg = JSON.parse(readFileSync(resolve(__dirname, "../package.json"), "utf8"));
1407
1506
  var cli = Cli7.create("etherscan", {
1507
+ version: pkg.version,
1408
1508
  description: "Query Etherscan API data from the command line."
1409
1509
  });
1410
1510
  cli.command(accountCli);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spectratools/etherscan-cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Etherscan API CLI for spectra-the-bot",
5
5
  "type": "module",
6
6
  "license": "MIT",