@spectratools/etherscan-cli 0.2.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -5
- package/dist/cli.js +645 -190
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,12 +43,13 @@ export ETHERSCAN_API_KEY=your_api_key
|
|
|
43
43
|
|
|
44
44
|
## Command Group Intent Summary
|
|
45
45
|
|
|
46
|
-
- `account` — Wallet balances, normal tx history,
|
|
46
|
+
- `account` — Wallet balances, normal/internal tx history, ERC-20/ERC-721/ERC-1155 transfers
|
|
47
47
|
- `contract` — ABI, verified source, and deployment transaction metadata
|
|
48
48
|
- `tx` — Transaction detail, receipt, and pass/fail status checks
|
|
49
49
|
- `token` — Token metadata, holder distribution, and supply
|
|
50
50
|
- `gas` — Current gas oracle and time-to-confirmation estimates
|
|
51
51
|
- `stats` — ETH price and supply snapshots
|
|
52
|
+
- `logs` — Event log queries with address/topic/block filters
|
|
52
53
|
|
|
53
54
|
## Agent-Oriented Examples
|
|
54
55
|
|
|
@@ -56,20 +57,28 @@ export ETHERSCAN_API_KEY=your_api_key
|
|
|
56
57
|
# 1) Wallet risk scan: balance + recent txs
|
|
57
58
|
etherscan-cli account balance 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --chain ethereum --format json
|
|
58
59
|
etherscan-cli account txlist 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --chain ethereum --sort desc --offset 20 --format json
|
|
60
|
+
etherscan-cli account internaltx 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --chain ethereum --sort desc --offset 20 --format json
|
|
59
61
|
|
|
60
|
-
# 2)
|
|
62
|
+
# 2) NFT transfer monitoring
|
|
63
|
+
etherscan-cli account nfttx 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --chain ethereum --sort desc --offset 20 --format json
|
|
64
|
+
etherscan-cli account erc1155tx 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --chain ethereum --sort desc --offset 20 --format json
|
|
65
|
+
|
|
66
|
+
# 3) Contract triage for unknown addresses
|
|
61
67
|
etherscan-cli contract creation 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 --chain ethereum --format json
|
|
62
68
|
etherscan-cli contract source 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 --chain ethereum --format json
|
|
63
69
|
|
|
64
|
-
#
|
|
70
|
+
# 4) Tx execution diagnosis
|
|
65
71
|
etherscan-cli tx info 0x1234...abcd --chain abstract --format json
|
|
66
72
|
etherscan-cli tx receipt 0x1234...abcd --chain abstract --format json
|
|
67
73
|
|
|
68
|
-
#
|
|
74
|
+
# 5) Token monitoring loop
|
|
69
75
|
etherscan-cli token info 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 --chain ethereum --format json
|
|
70
76
|
etherscan-cli token holders 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 --offset 25 --chain ethereum --format json
|
|
71
77
|
|
|
72
|
-
#
|
|
78
|
+
# 6) Event log indexing
|
|
79
|
+
etherscan-cli logs get --chain ethereum --address 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 --topic0 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55aebec6f6f3c --fromblock 20000000 --toblock latest --offset 25 --format json
|
|
80
|
+
|
|
81
|
+
# 7) Gas-aware scheduling
|
|
73
82
|
etherscan-cli gas oracle --chain abstract --format json
|
|
74
83
|
etherscan-cli gas estimate --gasprice 1000000000 --chain abstract --format json
|
|
75
84
|
```
|
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
|
|
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:
|
|
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/
|
|
876
|
-
import {
|
|
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
|
|
883
|
-
|
|
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
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
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
|
-
|
|
892
|
-
description: "Get
|
|
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
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
944
|
-
chain:
|
|
1397
|
+
options: z7.object({
|
|
1398
|
+
chain: chainOption5
|
|
945
1399
|
}),
|
|
946
1400
|
env: etherscanEnv,
|
|
947
|
-
output:
|
|
948
|
-
chain:
|
|
949
|
-
totalSupplyWei:
|
|
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
|
|
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
|
-
|
|
1417
|
+
z7.string()
|
|
964
1418
|
),
|
|
965
|
-
|
|
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
|
|
989
|
-
createRateLimiter as
|
|
990
|
-
isAddress as
|
|
991
|
-
withRateLimit as
|
|
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
|
|
994
|
-
var
|
|
995
|
-
var
|
|
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 =
|
|
1452
|
+
var tokenCli = Cli6.create("token", {
|
|
999
1453
|
description: "Query token metadata, holders, and supply."
|
|
1000
1454
|
});
|
|
1001
|
-
var tokenInfoSchema =
|
|
1002
|
-
contractAddress:
|
|
1003
|
-
tokenName:
|
|
1004
|
-
symbol:
|
|
1005
|
-
divisor:
|
|
1006
|
-
tokenType:
|
|
1007
|
-
totalSupply:
|
|
1008
|
-
blueCheckmark:
|
|
1009
|
-
description:
|
|
1010
|
-
website:
|
|
1011
|
-
email:
|
|
1012
|
-
blog:
|
|
1013
|
-
reddit:
|
|
1014
|
-
slack:
|
|
1015
|
-
facebook:
|
|
1016
|
-
twitter:
|
|
1017
|
-
bitcointalk:
|
|
1018
|
-
github:
|
|
1019
|
-
telegram:
|
|
1020
|
-
wechat:
|
|
1021
|
-
linkedin:
|
|
1022
|
-
discord:
|
|
1023
|
-
whitepaper:
|
|
1024
|
-
tokenPriceUSD:
|
|
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 =
|
|
1027
|
-
TokenHolderAddress:
|
|
1028
|
-
TokenHolderQuantity:
|
|
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:
|
|
1033
|
-
contractaddress:
|
|
1486
|
+
args: z8.object({
|
|
1487
|
+
contractaddress: z8.string().describe("Token contract address")
|
|
1034
1488
|
}),
|
|
1035
|
-
options:
|
|
1036
|
-
chain:
|
|
1489
|
+
options: z8.object({
|
|
1490
|
+
chain: chainOption6
|
|
1037
1491
|
}),
|
|
1038
1492
|
env: etherscanEnv,
|
|
1039
|
-
output:
|
|
1040
|
-
address:
|
|
1041
|
-
chain:
|
|
1042
|
-
name:
|
|
1043
|
-
symbol:
|
|
1044
|
-
type:
|
|
1045
|
-
totalSupply:
|
|
1046
|
-
decimals:
|
|
1047
|
-
priceUsd:
|
|
1048
|
-
website:
|
|
1049
|
-
description:
|
|
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 (!
|
|
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 =
|
|
1521
|
+
const address = checksumAddress4(c.args.contractaddress);
|
|
1068
1522
|
const client = createEtherscanClient(apiKey);
|
|
1069
|
-
const results = await
|
|
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
|
-
|
|
1531
|
+
z8.array(tokenInfoSchema)
|
|
1078
1532
|
),
|
|
1079
|
-
|
|
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:
|
|
1120
|
-
contractaddress:
|
|
1573
|
+
args: z8.object({
|
|
1574
|
+
contractaddress: z8.string().describe("Token contract address")
|
|
1121
1575
|
}),
|
|
1122
|
-
options:
|
|
1123
|
-
page:
|
|
1124
|
-
offset:
|
|
1125
|
-
chain:
|
|
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:
|
|
1129
|
-
contractAddress:
|
|
1130
|
-
chain:
|
|
1131
|
-
count:
|
|
1132
|
-
holders:
|
|
1133
|
-
|
|
1134
|
-
rank:
|
|
1135
|
-
address:
|
|
1136
|
-
quantity:
|
|
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 (!
|
|
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 =
|
|
1610
|
+
const address = checksumAddress4(c.args.contractaddress);
|
|
1157
1611
|
const client = createEtherscanClient(apiKey);
|
|
1158
|
-
const holders = await
|
|
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
|
-
|
|
1622
|
+
z8.array(holderEntrySchema)
|
|
1169
1623
|
),
|
|
1170
|
-
|
|
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:
|
|
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:
|
|
1201
|
-
contractaddress:
|
|
1654
|
+
args: z8.object({
|
|
1655
|
+
contractaddress: z8.string().describe("Token contract address")
|
|
1202
1656
|
}),
|
|
1203
|
-
options:
|
|
1204
|
-
chain:
|
|
1657
|
+
options: z8.object({
|
|
1658
|
+
chain: chainOption6
|
|
1205
1659
|
}),
|
|
1206
1660
|
env: etherscanEnv,
|
|
1207
|
-
output:
|
|
1208
|
-
contractAddress:
|
|
1209
|
-
chain:
|
|
1210
|
-
totalSupply:
|
|
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 (!
|
|
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 =
|
|
1682
|
+
const address = checksumAddress4(c.args.contractaddress);
|
|
1229
1683
|
const client = createEtherscanClient(apiKey);
|
|
1230
|
-
const supply = await
|
|
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
|
-
|
|
1692
|
+
z8.string()
|
|
1239
1693
|
),
|
|
1240
|
-
|
|
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
|
|
1261
|
-
import { Cli as
|
|
1262
|
-
var
|
|
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
|
|
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 =
|
|
1723
|
+
var txCli = Cli7.create("tx", {
|
|
1270
1724
|
description: "Query transaction details, receipts, and execution status."
|
|
1271
1725
|
});
|
|
1272
|
-
var transactionInfoSchema =
|
|
1273
|
-
hash:
|
|
1274
|
-
from:
|
|
1275
|
-
to:
|
|
1276
|
-
value:
|
|
1277
|
-
gas:
|
|
1278
|
-
gasPrice:
|
|
1279
|
-
nonce:
|
|
1280
|
-
blockNumber:
|
|
1281
|
-
blockHash:
|
|
1282
|
-
input:
|
|
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 =
|
|
1285
|
-
transactionHash:
|
|
1286
|
-
blockNumber:
|
|
1287
|
-
blockHash:
|
|
1288
|
-
from:
|
|
1289
|
-
to:
|
|
1290
|
-
status:
|
|
1291
|
-
gasUsed:
|
|
1292
|
-
cumulativeGasUsed:
|
|
1293
|
-
contractAddress:
|
|
1294
|
-
logs:
|
|
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 =
|
|
1297
|
-
isError:
|
|
1298
|
-
errDescription:
|
|
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:
|
|
1303
|
-
txhash:
|
|
1756
|
+
args: z9.object({
|
|
1757
|
+
txhash: z9.string().describe("Transaction hash")
|
|
1304
1758
|
}),
|
|
1305
|
-
options:
|
|
1306
|
-
chain:
|
|
1759
|
+
options: z9.object({
|
|
1760
|
+
chain: chainOption7
|
|
1307
1761
|
}),
|
|
1308
1762
|
env: etherscanEnv,
|
|
1309
|
-
output:
|
|
1310
|
-
hash:
|
|
1311
|
-
from:
|
|
1312
|
-
to:
|
|
1313
|
-
value:
|
|
1314
|
-
gas:
|
|
1315
|
-
gasPrice:
|
|
1316
|
-
nonce:
|
|
1317
|
-
block:
|
|
1318
|
-
chain:
|
|
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
|
|
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
|
-
|
|
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:
|
|
1377
|
-
txhash:
|
|
1830
|
+
args: z9.object({
|
|
1831
|
+
txhash: z9.string().describe("Transaction hash")
|
|
1378
1832
|
}),
|
|
1379
|
-
options:
|
|
1380
|
-
chain:
|
|
1833
|
+
options: z9.object({
|
|
1834
|
+
chain: chainOption7
|
|
1381
1835
|
}),
|
|
1382
1836
|
env: etherscanEnv,
|
|
1383
|
-
output:
|
|
1384
|
-
hash:
|
|
1385
|
-
block:
|
|
1386
|
-
from:
|
|
1387
|
-
to:
|
|
1388
|
-
status:
|
|
1389
|
-
gasUsed:
|
|
1390
|
-
contractAddress:
|
|
1391
|
-
logCount:
|
|
1392
|
-
chain:
|
|
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
|
|
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
|
-
|
|
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:
|
|
1446
|
-
txhash:
|
|
1899
|
+
args: z9.object({
|
|
1900
|
+
txhash: z9.string().describe("Transaction hash")
|
|
1447
1901
|
}),
|
|
1448
|
-
options:
|
|
1449
|
-
chain:
|
|
1902
|
+
options: z9.object({
|
|
1903
|
+
chain: chainOption7
|
|
1450
1904
|
}),
|
|
1451
1905
|
env: etherscanEnv,
|
|
1452
|
-
output:
|
|
1453
|
-
hash:
|
|
1454
|
-
status:
|
|
1455
|
-
error:
|
|
1456
|
-
chain:
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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) {
|