@spectratools/etherscan-cli 0.1.0 → 0.1.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.
- package/README.md +38 -77
- package/dist/cli.js +725 -246
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
+
import { realpathSync } from "fs";
|
|
4
5
|
import { fileURLToPath } from "url";
|
|
5
6
|
import { Cli as Cli7 } from "incur";
|
|
6
7
|
|
|
7
8
|
// src/commands/account.ts
|
|
8
9
|
import {
|
|
9
|
-
apiKeyAuth,
|
|
10
10
|
checksumAddress,
|
|
11
11
|
createRateLimiter as createRateLimiter2,
|
|
12
12
|
formatTimestamp,
|
|
13
13
|
weiToEth,
|
|
14
14
|
withRateLimit as withRateLimit2
|
|
15
15
|
} from "@spectratools/cli-shared";
|
|
16
|
-
import { Cli, z } from "incur";
|
|
16
|
+
import { Cli, z as z3 } from "incur";
|
|
17
17
|
|
|
18
18
|
// src/api.ts
|
|
19
19
|
import {
|
|
@@ -22,14 +22,49 @@ import {
|
|
|
22
22
|
withRateLimit,
|
|
23
23
|
withRetry
|
|
24
24
|
} from "@spectratools/cli-shared";
|
|
25
|
+
import { z } from "incur";
|
|
25
26
|
var DEFAULT_BASE_URL = "https://api.etherscan.io/v2/api";
|
|
26
27
|
var RETRY_OPTIONS = { maxRetries: 3, baseMs: 500, maxMs: 1e4 };
|
|
28
|
+
var etherscanResponseSchema = z.object({
|
|
29
|
+
status: z.string(),
|
|
30
|
+
message: z.string(),
|
|
31
|
+
result: z.unknown()
|
|
32
|
+
});
|
|
33
|
+
var proxyResponseSchema = z.object({
|
|
34
|
+
jsonrpc: z.string(),
|
|
35
|
+
id: z.union([z.number(), z.string()]),
|
|
36
|
+
result: z.unknown()
|
|
37
|
+
});
|
|
27
38
|
var EtherscanError = class extends Error {
|
|
28
|
-
constructor(message) {
|
|
39
|
+
constructor(message, details) {
|
|
29
40
|
super(message);
|
|
41
|
+
this.details = details;
|
|
30
42
|
this.name = "EtherscanError";
|
|
31
43
|
}
|
|
32
44
|
};
|
|
45
|
+
var EtherscanValidationError = class extends EtherscanError {
|
|
46
|
+
constructor(debug) {
|
|
47
|
+
super("Etherscan response validation failed", {
|
|
48
|
+
code: "INVALID_API_RESPONSE",
|
|
49
|
+
mode: debug.mode,
|
|
50
|
+
params: debug.params,
|
|
51
|
+
issues: debug.issues,
|
|
52
|
+
response: debug.response
|
|
53
|
+
});
|
|
54
|
+
this.debug = debug;
|
|
55
|
+
this.name = "EtherscanValidationError";
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
function parseWithSchema(schema, data, mode, params) {
|
|
59
|
+
const parsed = schema.safeParse(data);
|
|
60
|
+
if (parsed.success) return parsed.data;
|
|
61
|
+
throw new EtherscanValidationError({
|
|
62
|
+
mode,
|
|
63
|
+
params,
|
|
64
|
+
issues: parsed.error.issues,
|
|
65
|
+
response: data
|
|
66
|
+
});
|
|
67
|
+
}
|
|
33
68
|
function createEtherscanClient(apiKey, baseUrl = DEFAULT_BASE_URL) {
|
|
34
69
|
const http = createHttpClient({ baseUrl });
|
|
35
70
|
const acquire = createRateLimiter({ requestsPerSecond: 5 });
|
|
@@ -44,21 +79,33 @@ function createEtherscanClient(apiKey, baseUrl = DEFAULT_BASE_URL) {
|
|
|
44
79
|
RETRY_OPTIONS
|
|
45
80
|
);
|
|
46
81
|
}
|
|
47
|
-
async function call(params) {
|
|
48
|
-
const
|
|
82
|
+
async function call(params, resultSchema) {
|
|
83
|
+
const rawResponse = await request(params);
|
|
84
|
+
const response = parseWithSchema(etherscanResponseSchema, rawResponse, "rest", params);
|
|
49
85
|
if (response.status === "0") {
|
|
50
86
|
const msg = typeof response.result === "string" ? response.result : response.message;
|
|
51
|
-
throw new EtherscanError(msg
|
|
87
|
+
throw new EtherscanError(msg, {
|
|
88
|
+
code: "ETHERSCAN_API_ERROR",
|
|
89
|
+
params,
|
|
90
|
+
response
|
|
91
|
+
});
|
|
52
92
|
}
|
|
53
|
-
return response.result;
|
|
93
|
+
return parseWithSchema(resultSchema, response.result, "rest", params);
|
|
54
94
|
}
|
|
55
|
-
async function callProxy(params) {
|
|
56
|
-
const
|
|
57
|
-
|
|
95
|
+
async function callProxy(params, resultSchema) {
|
|
96
|
+
const rawResponse = await request(params);
|
|
97
|
+
const response = parseWithSchema(proxyResponseSchema, rawResponse, "proxy", params);
|
|
98
|
+
return parseWithSchema(resultSchema, response.result, "proxy", params);
|
|
58
99
|
}
|
|
59
100
|
return { call, callProxy };
|
|
60
101
|
}
|
|
61
102
|
|
|
103
|
+
// src/auth.ts
|
|
104
|
+
import { z as z2 } from "incur";
|
|
105
|
+
var etherscanEnv = z2.object({
|
|
106
|
+
ETHERSCAN_API_KEY: z2.string().describe("Etherscan V2 API key")
|
|
107
|
+
});
|
|
108
|
+
|
|
62
109
|
// src/chains.ts
|
|
63
110
|
var CHAIN_IDS = {
|
|
64
111
|
abstract: 2741,
|
|
@@ -91,7 +138,28 @@ function resolveChainId(chain) {
|
|
|
91
138
|
|
|
92
139
|
// src/commands/account.ts
|
|
93
140
|
var rateLimiter = createRateLimiter2({ requestsPerSecond: 5 });
|
|
94
|
-
var chainOption =
|
|
141
|
+
var chainOption = z3.string().default(DEFAULT_CHAIN).describe("Chain name (abstract, ethereum, base, arbitrum, ...)");
|
|
142
|
+
var txListItemSchema = z3.object({
|
|
143
|
+
hash: z3.string(),
|
|
144
|
+
from: z3.string(),
|
|
145
|
+
to: z3.string(),
|
|
146
|
+
value: z3.string(),
|
|
147
|
+
timeStamp: z3.string(),
|
|
148
|
+
blockNumber: z3.string(),
|
|
149
|
+
isError: z3.string(),
|
|
150
|
+
gasUsed: z3.string()
|
|
151
|
+
});
|
|
152
|
+
var tokenTxItemSchema = z3.object({
|
|
153
|
+
hash: z3.string(),
|
|
154
|
+
from: z3.string(),
|
|
155
|
+
to: z3.string(),
|
|
156
|
+
value: z3.string(),
|
|
157
|
+
tokenName: z3.string(),
|
|
158
|
+
tokenSymbol: z3.string(),
|
|
159
|
+
tokenDecimal: z3.string(),
|
|
160
|
+
timeStamp: z3.string(),
|
|
161
|
+
contractAddress: z3.string()
|
|
162
|
+
});
|
|
95
163
|
function normalizeAddress(address) {
|
|
96
164
|
try {
|
|
97
165
|
return checksumAddress(address);
|
|
@@ -100,29 +168,46 @@ function normalizeAddress(address) {
|
|
|
100
168
|
}
|
|
101
169
|
}
|
|
102
170
|
var accountCli = Cli.create("account", {
|
|
103
|
-
description: "Query account balances, transactions, and token transfers"
|
|
171
|
+
description: "Query account balances, transactions, and token transfers."
|
|
104
172
|
});
|
|
105
173
|
accountCli.command("balance", {
|
|
106
|
-
description: "Get the
|
|
107
|
-
args:
|
|
108
|
-
address:
|
|
174
|
+
description: "Get the native-token balance of an address.",
|
|
175
|
+
args: z3.object({
|
|
176
|
+
address: z3.string().describe("Wallet address")
|
|
109
177
|
}),
|
|
110
|
-
options:
|
|
178
|
+
options: z3.object({
|
|
111
179
|
chain: chainOption
|
|
112
180
|
}),
|
|
181
|
+
env: etherscanEnv,
|
|
182
|
+
output: z3.object({
|
|
183
|
+
address: z3.string(),
|
|
184
|
+
wei: z3.string(),
|
|
185
|
+
eth: z3.string(),
|
|
186
|
+
chain: z3.string()
|
|
187
|
+
}),
|
|
188
|
+
examples: [
|
|
189
|
+
{
|
|
190
|
+
args: { address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" },
|
|
191
|
+
options: { chain: "abstract" },
|
|
192
|
+
description: "Get ETH balance on Abstract"
|
|
193
|
+
}
|
|
194
|
+
],
|
|
113
195
|
async run(c) {
|
|
114
|
-
const
|
|
196
|
+
const apiKey = c.env.ETHERSCAN_API_KEY;
|
|
115
197
|
const chainId = resolveChainId(c.options.chain);
|
|
116
198
|
const address = normalizeAddress(c.args.address);
|
|
117
199
|
const client = createEtherscanClient(apiKey);
|
|
118
200
|
const wei = await withRateLimit2(
|
|
119
|
-
() => client.call(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
201
|
+
() => client.call(
|
|
202
|
+
{
|
|
203
|
+
chainid: chainId,
|
|
204
|
+
module: "account",
|
|
205
|
+
action: "balance",
|
|
206
|
+
address,
|
|
207
|
+
tag: "latest"
|
|
208
|
+
},
|
|
209
|
+
z3.string()
|
|
210
|
+
),
|
|
126
211
|
rateLimiter
|
|
127
212
|
);
|
|
128
213
|
return c.ok(
|
|
@@ -143,35 +228,64 @@ accountCli.command("balance", {
|
|
|
143
228
|
}
|
|
144
229
|
});
|
|
145
230
|
accountCli.command("txlist", {
|
|
146
|
-
description: "List normal transactions for an address",
|
|
147
|
-
args:
|
|
148
|
-
address:
|
|
149
|
-
}),
|
|
150
|
-
options:
|
|
151
|
-
startblock:
|
|
152
|
-
endblock:
|
|
153
|
-
page:
|
|
154
|
-
offset:
|
|
155
|
-
sort:
|
|
231
|
+
description: "List normal transactions for an address.",
|
|
232
|
+
args: z3.object({
|
|
233
|
+
address: z3.string().describe("Wallet address")
|
|
234
|
+
}),
|
|
235
|
+
options: z3.object({
|
|
236
|
+
startblock: z3.number().optional().default(0).describe("Start block number"),
|
|
237
|
+
endblock: z3.string().optional().default("latest").describe("End block number"),
|
|
238
|
+
page: z3.number().optional().default(1).describe("Page number"),
|
|
239
|
+
offset: z3.number().optional().default(10).describe("Number of results per page"),
|
|
240
|
+
sort: z3.string().optional().default("asc").describe("Sort order (asc or desc)"),
|
|
156
241
|
chain: chainOption
|
|
157
242
|
}),
|
|
243
|
+
env: etherscanEnv,
|
|
244
|
+
output: z3.object({
|
|
245
|
+
address: z3.string(),
|
|
246
|
+
chain: z3.string(),
|
|
247
|
+
count: z3.number(),
|
|
248
|
+
transactions: z3.array(
|
|
249
|
+
z3.object({
|
|
250
|
+
hash: z3.string(),
|
|
251
|
+
from: z3.string(),
|
|
252
|
+
to: z3.string(),
|
|
253
|
+
value: z3.string(),
|
|
254
|
+
eth: z3.string(),
|
|
255
|
+
timestamp: z3.string(),
|
|
256
|
+
block: z3.string(),
|
|
257
|
+
status: z3.string(),
|
|
258
|
+
gasUsed: z3.string()
|
|
259
|
+
})
|
|
260
|
+
)
|
|
261
|
+
}),
|
|
262
|
+
examples: [
|
|
263
|
+
{
|
|
264
|
+
args: { address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" },
|
|
265
|
+
options: { chain: "ethereum", sort: "desc", offset: 5 },
|
|
266
|
+
description: "List most recent transactions for an address"
|
|
267
|
+
}
|
|
268
|
+
],
|
|
158
269
|
async run(c) {
|
|
159
|
-
const
|
|
270
|
+
const apiKey = c.env.ETHERSCAN_API_KEY;
|
|
160
271
|
const chainId = resolveChainId(c.options.chain);
|
|
161
272
|
const address = normalizeAddress(c.args.address);
|
|
162
273
|
const client = createEtherscanClient(apiKey);
|
|
163
274
|
const txs = await withRateLimit2(
|
|
164
|
-
() => client.call(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
275
|
+
() => client.call(
|
|
276
|
+
{
|
|
277
|
+
chainid: chainId,
|
|
278
|
+
module: "account",
|
|
279
|
+
action: "txlist",
|
|
280
|
+
address,
|
|
281
|
+
startblock: c.options.startblock,
|
|
282
|
+
endblock: c.options.endblock,
|
|
283
|
+
page: c.options.page,
|
|
284
|
+
offset: c.options.offset,
|
|
285
|
+
sort: c.options.sort
|
|
286
|
+
},
|
|
287
|
+
z3.array(txListItemSchema)
|
|
288
|
+
),
|
|
175
289
|
rateLimiter
|
|
176
290
|
);
|
|
177
291
|
const formatted = txs.map((tx) => ({
|
|
@@ -203,31 +317,60 @@ accountCli.command("txlist", {
|
|
|
203
317
|
}
|
|
204
318
|
});
|
|
205
319
|
accountCli.command("tokentx", {
|
|
206
|
-
description: "List ERC-20 token transfers for an address",
|
|
207
|
-
args:
|
|
208
|
-
address:
|
|
209
|
-
}),
|
|
210
|
-
options:
|
|
211
|
-
contractaddress:
|
|
212
|
-
page:
|
|
213
|
-
offset:
|
|
320
|
+
description: "List ERC-20 token transfers for an address.",
|
|
321
|
+
args: z3.object({
|
|
322
|
+
address: z3.string().describe("Wallet address")
|
|
323
|
+
}),
|
|
324
|
+
options: z3.object({
|
|
325
|
+
contractaddress: z3.string().optional().describe("Filter by token contract address"),
|
|
326
|
+
page: z3.number().optional().default(1).describe("Page number"),
|
|
327
|
+
offset: z3.number().optional().default(20).describe("Results per page"),
|
|
214
328
|
chain: chainOption
|
|
215
329
|
}),
|
|
330
|
+
env: etherscanEnv,
|
|
331
|
+
output: z3.object({
|
|
332
|
+
address: z3.string(),
|
|
333
|
+
chain: z3.string(),
|
|
334
|
+
count: z3.number(),
|
|
335
|
+
transfers: z3.array(
|
|
336
|
+
z3.object({
|
|
337
|
+
hash: z3.string(),
|
|
338
|
+
from: z3.string(),
|
|
339
|
+
to: z3.string(),
|
|
340
|
+
value: z3.string(),
|
|
341
|
+
token: z3.string(),
|
|
342
|
+
tokenName: z3.string(),
|
|
343
|
+
decimals: z3.string(),
|
|
344
|
+
timestamp: z3.string(),
|
|
345
|
+
contract: z3.string()
|
|
346
|
+
})
|
|
347
|
+
)
|
|
348
|
+
}),
|
|
349
|
+
examples: [
|
|
350
|
+
{
|
|
351
|
+
args: { address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" },
|
|
352
|
+
options: { chain: "base", offset: 10 },
|
|
353
|
+
description: "List recent ERC-20 transfers for an address"
|
|
354
|
+
}
|
|
355
|
+
],
|
|
216
356
|
async run(c) {
|
|
217
|
-
const
|
|
357
|
+
const apiKey = c.env.ETHERSCAN_API_KEY;
|
|
218
358
|
const chainId = resolveChainId(c.options.chain);
|
|
219
359
|
const address = normalizeAddress(c.args.address);
|
|
220
360
|
const client = createEtherscanClient(apiKey);
|
|
221
361
|
const transfers = await withRateLimit2(
|
|
222
|
-
() => client.call(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
362
|
+
() => client.call(
|
|
363
|
+
{
|
|
364
|
+
chainid: chainId,
|
|
365
|
+
module: "account",
|
|
366
|
+
action: "tokentx",
|
|
367
|
+
address,
|
|
368
|
+
contractaddress: c.options.contractaddress,
|
|
369
|
+
page: c.options.page,
|
|
370
|
+
offset: c.options.offset
|
|
371
|
+
},
|
|
372
|
+
z3.array(tokenTxItemSchema)
|
|
373
|
+
),
|
|
231
374
|
rateLimiter
|
|
232
375
|
);
|
|
233
376
|
const formatted = transfers.map((tx) => ({
|
|
@@ -258,29 +401,46 @@ accountCli.command("tokentx", {
|
|
|
258
401
|
}
|
|
259
402
|
});
|
|
260
403
|
accountCli.command("tokenbalance", {
|
|
261
|
-
description: "Get ERC-20 token balance for an address",
|
|
262
|
-
args:
|
|
263
|
-
address:
|
|
404
|
+
description: "Get ERC-20 token balance for an address.",
|
|
405
|
+
args: z3.object({
|
|
406
|
+
address: z3.string().describe("Wallet address")
|
|
264
407
|
}),
|
|
265
|
-
options:
|
|
266
|
-
contractaddress:
|
|
408
|
+
options: z3.object({
|
|
409
|
+
contractaddress: z3.string().describe("Token contract address"),
|
|
267
410
|
chain: chainOption
|
|
268
411
|
}),
|
|
412
|
+
env: etherscanEnv,
|
|
413
|
+
output: z3.object({
|
|
414
|
+
address: z3.string(),
|
|
415
|
+
contract: z3.string(),
|
|
416
|
+
balance: z3.string(),
|
|
417
|
+
chain: z3.string()
|
|
418
|
+
}),
|
|
419
|
+
examples: [
|
|
420
|
+
{
|
|
421
|
+
args: { address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" },
|
|
422
|
+
options: { contractaddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", chain: "ethereum" },
|
|
423
|
+
description: "Get token balance for a wallet + token pair"
|
|
424
|
+
}
|
|
425
|
+
],
|
|
269
426
|
async run(c) {
|
|
270
|
-
const
|
|
427
|
+
const apiKey = c.env.ETHERSCAN_API_KEY;
|
|
271
428
|
const chainId = resolveChainId(c.options.chain);
|
|
272
429
|
const address = normalizeAddress(c.args.address);
|
|
273
430
|
const contract = normalizeAddress(c.options.contractaddress);
|
|
274
431
|
const client = createEtherscanClient(apiKey);
|
|
275
432
|
const balance = await withRateLimit2(
|
|
276
|
-
() => client.call(
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
433
|
+
() => client.call(
|
|
434
|
+
{
|
|
435
|
+
chainid: chainId,
|
|
436
|
+
module: "account",
|
|
437
|
+
action: "tokenbalance",
|
|
438
|
+
address,
|
|
439
|
+
contractaddress: contract,
|
|
440
|
+
tag: "latest"
|
|
441
|
+
},
|
|
442
|
+
z3.string()
|
|
443
|
+
),
|
|
284
444
|
rateLimiter
|
|
285
445
|
);
|
|
286
446
|
return c.ok(
|
|
@@ -301,38 +461,66 @@ accountCli.command("tokenbalance", {
|
|
|
301
461
|
});
|
|
302
462
|
|
|
303
463
|
// src/commands/contract.ts
|
|
304
|
-
import {
|
|
305
|
-
|
|
306
|
-
checksumAddress as checksumAddress2,
|
|
307
|
-
createRateLimiter as createRateLimiter3,
|
|
308
|
-
withRateLimit as withRateLimit3
|
|
309
|
-
} from "@spectratools/cli-shared";
|
|
310
|
-
import { Cli as Cli2, z as z2 } from "incur";
|
|
464
|
+
import { checksumAddress as checksumAddress2, createRateLimiter as createRateLimiter3, withRateLimit as withRateLimit3 } from "@spectratools/cli-shared";
|
|
465
|
+
import { Cli as Cli2, z as z4 } from "incur";
|
|
311
466
|
var rateLimiter2 = createRateLimiter3({ requestsPerSecond: 5 });
|
|
312
|
-
var chainOption2 =
|
|
467
|
+
var chainOption2 = z4.string().default(DEFAULT_CHAIN).describe("Chain name (abstract, ethereum, base, arbitrum, ...)");
|
|
313
468
|
var contractCli = Cli2.create("contract", {
|
|
314
|
-
description: "Query contract ABI, source code, and deployment
|
|
469
|
+
description: "Query contract ABI, source code, and deployment metadata."
|
|
470
|
+
});
|
|
471
|
+
var sourceResultSchema = z4.object({
|
|
472
|
+
SourceCode: z4.string(),
|
|
473
|
+
ABI: z4.string(),
|
|
474
|
+
ContractName: z4.string(),
|
|
475
|
+
CompilerVersion: z4.string(),
|
|
476
|
+
OptimizationUsed: z4.string(),
|
|
477
|
+
Runs: z4.string(),
|
|
478
|
+
ConstructorArguments: z4.string(),
|
|
479
|
+
LicenseType: z4.string(),
|
|
480
|
+
Proxy: z4.string(),
|
|
481
|
+
Implementation: z4.string()
|
|
482
|
+
});
|
|
483
|
+
var creationResultSchema = z4.object({
|
|
484
|
+
contractAddress: z4.string(),
|
|
485
|
+
contractCreator: z4.string(),
|
|
486
|
+
txHash: z4.string()
|
|
315
487
|
});
|
|
316
488
|
contractCli.command("abi", {
|
|
317
|
-
description: "Get the ABI for a verified contract",
|
|
318
|
-
args:
|
|
319
|
-
address:
|
|
489
|
+
description: "Get the ABI for a verified contract.",
|
|
490
|
+
args: z4.object({
|
|
491
|
+
address: z4.string().describe("Contract address")
|
|
320
492
|
}),
|
|
321
|
-
options:
|
|
493
|
+
options: z4.object({
|
|
322
494
|
chain: chainOption2
|
|
323
495
|
}),
|
|
496
|
+
env: etherscanEnv,
|
|
497
|
+
output: z4.object({
|
|
498
|
+
address: z4.string(),
|
|
499
|
+
chain: z4.string(),
|
|
500
|
+
abi: z4.array(z4.unknown())
|
|
501
|
+
}),
|
|
502
|
+
examples: [
|
|
503
|
+
{
|
|
504
|
+
args: { address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" },
|
|
505
|
+
options: { chain: "ethereum" },
|
|
506
|
+
description: "Fetch ABI for a verified ERC-20 contract"
|
|
507
|
+
}
|
|
508
|
+
],
|
|
324
509
|
async run(c) {
|
|
325
|
-
const
|
|
510
|
+
const apiKey = c.env.ETHERSCAN_API_KEY;
|
|
326
511
|
const chainId = resolveChainId(c.options.chain);
|
|
327
512
|
const address = checksumAddress2(c.args.address);
|
|
328
513
|
const client = createEtherscanClient(apiKey);
|
|
329
514
|
const abi = await withRateLimit3(
|
|
330
|
-
() => client.call(
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
515
|
+
() => client.call(
|
|
516
|
+
{
|
|
517
|
+
chainid: chainId,
|
|
518
|
+
module: "contract",
|
|
519
|
+
action: "getabi",
|
|
520
|
+
address
|
|
521
|
+
},
|
|
522
|
+
z4.string()
|
|
523
|
+
),
|
|
336
524
|
rateLimiter2
|
|
337
525
|
);
|
|
338
526
|
return c.ok(
|
|
@@ -352,25 +540,49 @@ contractCli.command("abi", {
|
|
|
352
540
|
}
|
|
353
541
|
});
|
|
354
542
|
contractCli.command("source", {
|
|
355
|
-
description: "Get verified source code for a contract",
|
|
356
|
-
args:
|
|
357
|
-
address:
|
|
543
|
+
description: "Get verified source code for a contract.",
|
|
544
|
+
args: z4.object({
|
|
545
|
+
address: z4.string().describe("Contract address")
|
|
358
546
|
}),
|
|
359
|
-
options:
|
|
547
|
+
options: z4.object({
|
|
360
548
|
chain: chainOption2
|
|
361
549
|
}),
|
|
550
|
+
env: etherscanEnv,
|
|
551
|
+
output: z4.object({
|
|
552
|
+
address: z4.string(),
|
|
553
|
+
chain: z4.string(),
|
|
554
|
+
name: z4.string(),
|
|
555
|
+
compiler: z4.string(),
|
|
556
|
+
optimized: z4.boolean(),
|
|
557
|
+
runs: z4.string(),
|
|
558
|
+
license: z4.string(),
|
|
559
|
+
proxy: z4.boolean(),
|
|
560
|
+
implementation: z4.string().optional(),
|
|
561
|
+
sourceCode: z4.string(),
|
|
562
|
+
constructorArguments: z4.string()
|
|
563
|
+
}),
|
|
564
|
+
examples: [
|
|
565
|
+
{
|
|
566
|
+
args: { address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" },
|
|
567
|
+
options: { chain: "ethereum" },
|
|
568
|
+
description: "Fetch verified source code metadata"
|
|
569
|
+
}
|
|
570
|
+
],
|
|
362
571
|
async run(c) {
|
|
363
|
-
const
|
|
572
|
+
const apiKey = c.env.ETHERSCAN_API_KEY;
|
|
364
573
|
const chainId = resolveChainId(c.options.chain);
|
|
365
574
|
const address = checksumAddress2(c.args.address);
|
|
366
575
|
const client = createEtherscanClient(apiKey);
|
|
367
576
|
const results = await withRateLimit3(
|
|
368
|
-
() => client.call(
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
577
|
+
() => client.call(
|
|
578
|
+
{
|
|
579
|
+
chainid: chainId,
|
|
580
|
+
module: "contract",
|
|
581
|
+
action: "getsourcecode",
|
|
582
|
+
address
|
|
583
|
+
},
|
|
584
|
+
z4.array(sourceResultSchema)
|
|
585
|
+
),
|
|
374
586
|
rateLimiter2
|
|
375
587
|
);
|
|
376
588
|
const result = results[0];
|
|
@@ -406,25 +618,42 @@ contractCli.command("source", {
|
|
|
406
618
|
}
|
|
407
619
|
});
|
|
408
620
|
contractCli.command("creation", {
|
|
409
|
-
description: "Get the
|
|
410
|
-
args:
|
|
411
|
-
address:
|
|
621
|
+
description: "Get the deployment transaction and creator for a contract.",
|
|
622
|
+
args: z4.object({
|
|
623
|
+
address: z4.string().describe("Contract address")
|
|
412
624
|
}),
|
|
413
|
-
options:
|
|
625
|
+
options: z4.object({
|
|
414
626
|
chain: chainOption2
|
|
415
627
|
}),
|
|
628
|
+
env: etherscanEnv,
|
|
629
|
+
output: z4.object({
|
|
630
|
+
address: z4.string(),
|
|
631
|
+
creator: z4.string(),
|
|
632
|
+
txHash: z4.string(),
|
|
633
|
+
chain: z4.string()
|
|
634
|
+
}),
|
|
635
|
+
examples: [
|
|
636
|
+
{
|
|
637
|
+
args: { address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" },
|
|
638
|
+
options: { chain: "ethereum" },
|
|
639
|
+
description: "Find deployment tx for a contract"
|
|
640
|
+
}
|
|
641
|
+
],
|
|
416
642
|
async run(c) {
|
|
417
|
-
const
|
|
643
|
+
const apiKey = c.env.ETHERSCAN_API_KEY;
|
|
418
644
|
const chainId = resolveChainId(c.options.chain);
|
|
419
645
|
const address = checksumAddress2(c.args.address);
|
|
420
646
|
const client = createEtherscanClient(apiKey);
|
|
421
647
|
const results = await withRateLimit3(
|
|
422
|
-
() => client.call(
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
648
|
+
() => client.call(
|
|
649
|
+
{
|
|
650
|
+
chainid: chainId,
|
|
651
|
+
module: "contract",
|
|
652
|
+
action: "getcontractcreation",
|
|
653
|
+
contractaddresses: address
|
|
654
|
+
},
|
|
655
|
+
z4.array(creationResultSchema)
|
|
656
|
+
),
|
|
428
657
|
rateLimiter2
|
|
429
658
|
);
|
|
430
659
|
const result = results[0];
|
|
@@ -454,28 +683,50 @@ contractCli.command("creation", {
|
|
|
454
683
|
});
|
|
455
684
|
|
|
456
685
|
// src/commands/gas.ts
|
|
457
|
-
import {
|
|
458
|
-
import { Cli as Cli3, z as
|
|
686
|
+
import { createRateLimiter as createRateLimiter4, withRateLimit as withRateLimit4 } from "@spectratools/cli-shared";
|
|
687
|
+
import { Cli as Cli3, z as z5 } from "incur";
|
|
459
688
|
var rateLimiter3 = createRateLimiter4({ requestsPerSecond: 5 });
|
|
460
|
-
var chainOption3 =
|
|
689
|
+
var chainOption3 = z5.string().default(DEFAULT_CHAIN).describe("Chain name (abstract, ethereum, base, arbitrum, ...)");
|
|
461
690
|
var gasCli = Cli3.create("gas", {
|
|
462
|
-
description: "Query gas oracle and estimate
|
|
691
|
+
description: "Query gas oracle data and estimate confirmation latency."
|
|
692
|
+
});
|
|
693
|
+
var gasOracleSchema = z5.object({
|
|
694
|
+
LastBlock: z5.string(),
|
|
695
|
+
SafeGasPrice: z5.string(),
|
|
696
|
+
ProposeGasPrice: z5.string(),
|
|
697
|
+
FastGasPrice: z5.string(),
|
|
698
|
+
suggestBaseFee: z5.string(),
|
|
699
|
+
gasUsedRatio: z5.string()
|
|
463
700
|
});
|
|
464
701
|
gasCli.command("oracle", {
|
|
465
|
-
description: "Get current gas price recommendations",
|
|
466
|
-
options:
|
|
702
|
+
description: "Get current gas price recommendations.",
|
|
703
|
+
options: z5.object({
|
|
467
704
|
chain: chainOption3
|
|
468
705
|
}),
|
|
706
|
+
env: etherscanEnv,
|
|
707
|
+
output: z5.object({
|
|
708
|
+
chain: z5.string(),
|
|
709
|
+
lastBlock: z5.string(),
|
|
710
|
+
slow: z5.string(),
|
|
711
|
+
standard: z5.string(),
|
|
712
|
+
fast: z5.string(),
|
|
713
|
+
baseFee: z5.string(),
|
|
714
|
+
gasUsedRatio: z5.string()
|
|
715
|
+
}),
|
|
716
|
+
examples: [{ options: { chain: "abstract" }, description: "Get gas oracle on Abstract" }],
|
|
469
717
|
async run(c) {
|
|
470
|
-
const
|
|
718
|
+
const apiKey = c.env.ETHERSCAN_API_KEY;
|
|
471
719
|
const chainId = resolveChainId(c.options.chain);
|
|
472
720
|
const client = createEtherscanClient(apiKey);
|
|
473
721
|
const oracle = await withRateLimit4(
|
|
474
|
-
() => client.call(
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
722
|
+
() => client.call(
|
|
723
|
+
{
|
|
724
|
+
chainid: chainId,
|
|
725
|
+
module: "gastracker",
|
|
726
|
+
action: "gasoracle"
|
|
727
|
+
},
|
|
728
|
+
gasOracleSchema
|
|
729
|
+
),
|
|
479
730
|
rateLimiter3
|
|
480
731
|
);
|
|
481
732
|
return c.ok(
|
|
@@ -503,22 +754,37 @@ gasCli.command("oracle", {
|
|
|
503
754
|
}
|
|
504
755
|
});
|
|
505
756
|
gasCli.command("estimate", {
|
|
506
|
-
description: "Estimate
|
|
507
|
-
options:
|
|
508
|
-
gasprice:
|
|
757
|
+
description: "Estimate confirmation time in seconds for a gas price (wei).",
|
|
758
|
+
options: z5.object({
|
|
759
|
+
gasprice: z5.string().describe("Gas price in wei"),
|
|
509
760
|
chain: chainOption3
|
|
510
761
|
}),
|
|
762
|
+
env: etherscanEnv,
|
|
763
|
+
output: z5.object({
|
|
764
|
+
chain: z5.string(),
|
|
765
|
+
gasprice: z5.string(),
|
|
766
|
+
estimatedSeconds: z5.string()
|
|
767
|
+
}),
|
|
768
|
+
examples: [
|
|
769
|
+
{
|
|
770
|
+
options: { gasprice: "1000000000", chain: "ethereum" },
|
|
771
|
+
description: "Estimate confirmation time at 1 gwei"
|
|
772
|
+
}
|
|
773
|
+
],
|
|
511
774
|
async run(c) {
|
|
512
|
-
const
|
|
775
|
+
const apiKey = c.env.ETHERSCAN_API_KEY;
|
|
513
776
|
const chainId = resolveChainId(c.options.chain);
|
|
514
777
|
const client = createEtherscanClient(apiKey);
|
|
515
778
|
const estimate = await withRateLimit4(
|
|
516
|
-
() => client.call(
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
779
|
+
() => client.call(
|
|
780
|
+
{
|
|
781
|
+
chainid: chainId,
|
|
782
|
+
module: "gastracker",
|
|
783
|
+
action: "gasestimate",
|
|
784
|
+
gasprice: c.options.gasprice
|
|
785
|
+
},
|
|
786
|
+
z5.string()
|
|
787
|
+
),
|
|
522
788
|
rateLimiter3
|
|
523
789
|
);
|
|
524
790
|
return c.ok(
|
|
@@ -542,28 +808,46 @@ gasCli.command("estimate", {
|
|
|
542
808
|
});
|
|
543
809
|
|
|
544
810
|
// src/commands/stats.ts
|
|
545
|
-
import {
|
|
546
|
-
import { Cli as Cli4, z as
|
|
811
|
+
import { createRateLimiter as createRateLimiter5, withRateLimit as withRateLimit5 } from "@spectratools/cli-shared";
|
|
812
|
+
import { Cli as Cli4, z as z6 } from "incur";
|
|
547
813
|
var rateLimiter4 = createRateLimiter5({ requestsPerSecond: 5 });
|
|
548
|
-
var chainOption4 =
|
|
814
|
+
var chainOption4 = z6.string().default(DEFAULT_CHAIN).describe("Chain name (abstract, ethereum, base, arbitrum, ...)");
|
|
549
815
|
var statsCli = Cli4.create("stats", {
|
|
550
|
-
description: "Query ETH price and supply statistics"
|
|
816
|
+
description: "Query ETH price and total supply statistics."
|
|
817
|
+
});
|
|
818
|
+
var ethPriceSchema = z6.object({
|
|
819
|
+
ethbtc: z6.string(),
|
|
820
|
+
ethbtc_timestamp: z6.string(),
|
|
821
|
+
ethusd: z6.string(),
|
|
822
|
+
ethusd_timestamp: z6.string()
|
|
551
823
|
});
|
|
552
824
|
statsCli.command("ethprice", {
|
|
553
|
-
description: "Get
|
|
554
|
-
options:
|
|
825
|
+
description: "Get latest ETH price in USD and BTC.",
|
|
826
|
+
options: z6.object({
|
|
555
827
|
chain: chainOption4
|
|
556
828
|
}),
|
|
829
|
+
env: etherscanEnv,
|
|
830
|
+
output: z6.object({
|
|
831
|
+
chain: z6.string(),
|
|
832
|
+
usd: z6.string(),
|
|
833
|
+
btc: z6.string(),
|
|
834
|
+
usdTimestamp: z6.string(),
|
|
835
|
+
btcTimestamp: z6.string()
|
|
836
|
+
}),
|
|
837
|
+
examples: [{ options: { chain: "ethereum" }, description: "Get ETH spot price on Ethereum" }],
|
|
557
838
|
async run(c) {
|
|
558
|
-
const
|
|
839
|
+
const apiKey = c.env.ETHERSCAN_API_KEY;
|
|
559
840
|
const chainId = resolveChainId(c.options.chain);
|
|
560
841
|
const client = createEtherscanClient(apiKey);
|
|
561
842
|
const price = await withRateLimit5(
|
|
562
|
-
() => client.call(
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
843
|
+
() => client.call(
|
|
844
|
+
{
|
|
845
|
+
chainid: chainId,
|
|
846
|
+
module: "stats",
|
|
847
|
+
action: "ethprice"
|
|
848
|
+
},
|
|
849
|
+
ethPriceSchema
|
|
850
|
+
),
|
|
567
851
|
rateLimiter4
|
|
568
852
|
);
|
|
569
853
|
return c.ok(
|
|
@@ -588,20 +872,29 @@ statsCli.command("ethprice", {
|
|
|
588
872
|
}
|
|
589
873
|
});
|
|
590
874
|
statsCli.command("ethsupply", {
|
|
591
|
-
description: "Get
|
|
592
|
-
options:
|
|
875
|
+
description: "Get total ETH supply in wei.",
|
|
876
|
+
options: z6.object({
|
|
593
877
|
chain: chainOption4
|
|
594
878
|
}),
|
|
879
|
+
env: etherscanEnv,
|
|
880
|
+
output: z6.object({
|
|
881
|
+
chain: z6.string(),
|
|
882
|
+
totalSupplyWei: z6.string()
|
|
883
|
+
}),
|
|
884
|
+
examples: [{ options: { chain: "ethereum" }, description: "Get total ETH supply" }],
|
|
595
885
|
async run(c) {
|
|
596
|
-
const
|
|
886
|
+
const apiKey = c.env.ETHERSCAN_API_KEY;
|
|
597
887
|
const chainId = resolveChainId(c.options.chain);
|
|
598
888
|
const client = createEtherscanClient(apiKey);
|
|
599
889
|
const supply = await withRateLimit5(
|
|
600
|
-
() => client.call(
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
890
|
+
() => client.call(
|
|
891
|
+
{
|
|
892
|
+
chainid: chainId,
|
|
893
|
+
module: "stats",
|
|
894
|
+
action: "ethsupply"
|
|
895
|
+
},
|
|
896
|
+
z6.string()
|
|
897
|
+
),
|
|
605
898
|
rateLimiter4
|
|
606
899
|
);
|
|
607
900
|
return c.ok(
|
|
@@ -624,38 +917,85 @@ statsCli.command("ethsupply", {
|
|
|
624
917
|
});
|
|
625
918
|
|
|
626
919
|
// src/commands/token.ts
|
|
627
|
-
import {
|
|
628
|
-
|
|
629
|
-
checksumAddress as checksumAddress3,
|
|
630
|
-
createRateLimiter as createRateLimiter6,
|
|
631
|
-
withRateLimit as withRateLimit6
|
|
632
|
-
} from "@spectratools/cli-shared";
|
|
633
|
-
import { Cli as Cli5, z as z5 } from "incur";
|
|
920
|
+
import { checksumAddress as checksumAddress3, createRateLimiter as createRateLimiter6, withRateLimit as withRateLimit6 } from "@spectratools/cli-shared";
|
|
921
|
+
import { Cli as Cli5, z as z7 } from "incur";
|
|
634
922
|
var rateLimiter5 = createRateLimiter6({ requestsPerSecond: 5 });
|
|
635
|
-
var chainOption5 =
|
|
923
|
+
var chainOption5 = z7.string().default(DEFAULT_CHAIN).describe("Chain name (abstract, ethereum, base, arbitrum, ...)");
|
|
636
924
|
var tokenCli = Cli5.create("token", {
|
|
637
|
-
description: "Query token
|
|
925
|
+
description: "Query token metadata, holders, and supply."
|
|
926
|
+
});
|
|
927
|
+
var tokenInfoSchema = z7.object({
|
|
928
|
+
contractAddress: z7.string(),
|
|
929
|
+
tokenName: z7.string(),
|
|
930
|
+
symbol: z7.string(),
|
|
931
|
+
divisor: z7.string(),
|
|
932
|
+
tokenType: z7.string(),
|
|
933
|
+
totalSupply: z7.string(),
|
|
934
|
+
blueCheckmark: z7.string(),
|
|
935
|
+
description: z7.string(),
|
|
936
|
+
website: z7.string(),
|
|
937
|
+
email: z7.string(),
|
|
938
|
+
blog: z7.string(),
|
|
939
|
+
reddit: z7.string(),
|
|
940
|
+
slack: z7.string(),
|
|
941
|
+
facebook: z7.string(),
|
|
942
|
+
twitter: z7.string(),
|
|
943
|
+
bitcointalk: z7.string(),
|
|
944
|
+
github: z7.string(),
|
|
945
|
+
telegram: z7.string(),
|
|
946
|
+
wechat: z7.string(),
|
|
947
|
+
linkedin: z7.string(),
|
|
948
|
+
discord: z7.string(),
|
|
949
|
+
whitepaper: z7.string(),
|
|
950
|
+
tokenPriceUSD: z7.string()
|
|
951
|
+
});
|
|
952
|
+
var holderEntrySchema = z7.object({
|
|
953
|
+
TokenHolderAddress: z7.string(),
|
|
954
|
+
TokenHolderQuantity: z7.string()
|
|
638
955
|
});
|
|
639
956
|
tokenCli.command("info", {
|
|
640
|
-
description: "Get
|
|
641
|
-
args:
|
|
642
|
-
contractaddress:
|
|
957
|
+
description: "Get metadata for a token contract.",
|
|
958
|
+
args: z7.object({
|
|
959
|
+
contractaddress: z7.string().describe("Token contract address")
|
|
643
960
|
}),
|
|
644
|
-
options:
|
|
961
|
+
options: z7.object({
|
|
645
962
|
chain: chainOption5
|
|
646
963
|
}),
|
|
964
|
+
env: etherscanEnv,
|
|
965
|
+
output: z7.object({
|
|
966
|
+
address: z7.string(),
|
|
967
|
+
chain: z7.string(),
|
|
968
|
+
name: z7.string(),
|
|
969
|
+
symbol: z7.string(),
|
|
970
|
+
type: z7.string(),
|
|
971
|
+
totalSupply: z7.string(),
|
|
972
|
+
decimals: z7.string(),
|
|
973
|
+
priceUsd: z7.string().optional(),
|
|
974
|
+
website: z7.string().optional(),
|
|
975
|
+
description: z7.string().optional()
|
|
976
|
+
}),
|
|
977
|
+
examples: [
|
|
978
|
+
{
|
|
979
|
+
args: { contractaddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" },
|
|
980
|
+
options: { chain: "ethereum" },
|
|
981
|
+
description: "Get token metadata for USDC"
|
|
982
|
+
}
|
|
983
|
+
],
|
|
647
984
|
async run(c) {
|
|
648
|
-
const
|
|
985
|
+
const apiKey = c.env.ETHERSCAN_API_KEY;
|
|
649
986
|
const chainId = resolveChainId(c.options.chain);
|
|
650
987
|
const address = checksumAddress3(c.args.contractaddress);
|
|
651
988
|
const client = createEtherscanClient(apiKey);
|
|
652
989
|
const results = await withRateLimit6(
|
|
653
|
-
() => client.call(
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
990
|
+
() => client.call(
|
|
991
|
+
{
|
|
992
|
+
chainid: chainId,
|
|
993
|
+
module: "token",
|
|
994
|
+
action: "tokeninfo",
|
|
995
|
+
contractaddress: address
|
|
996
|
+
},
|
|
997
|
+
z7.array(tokenInfoSchema)
|
|
998
|
+
),
|
|
659
999
|
rateLimiter5
|
|
660
1000
|
);
|
|
661
1001
|
const info = results[0];
|
|
@@ -695,29 +1035,52 @@ tokenCli.command("info", {
|
|
|
695
1035
|
}
|
|
696
1036
|
});
|
|
697
1037
|
tokenCli.command("holders", {
|
|
698
|
-
description: "List top token holders",
|
|
699
|
-
args:
|
|
700
|
-
contractaddress:
|
|
1038
|
+
description: "List top token holders.",
|
|
1039
|
+
args: z7.object({
|
|
1040
|
+
contractaddress: z7.string().describe("Token contract address")
|
|
701
1041
|
}),
|
|
702
|
-
options:
|
|
703
|
-
page:
|
|
704
|
-
offset:
|
|
1042
|
+
options: z7.object({
|
|
1043
|
+
page: z7.number().optional().default(1).describe("Page number"),
|
|
1044
|
+
offset: z7.number().optional().default(10).describe("Results per page"),
|
|
705
1045
|
chain: chainOption5
|
|
706
1046
|
}),
|
|
1047
|
+
env: etherscanEnv,
|
|
1048
|
+
output: z7.object({
|
|
1049
|
+
contractAddress: z7.string(),
|
|
1050
|
+
chain: z7.string(),
|
|
1051
|
+
count: z7.number(),
|
|
1052
|
+
holders: z7.array(
|
|
1053
|
+
z7.object({
|
|
1054
|
+
rank: z7.number(),
|
|
1055
|
+
address: z7.string(),
|
|
1056
|
+
quantity: z7.string()
|
|
1057
|
+
})
|
|
1058
|
+
)
|
|
1059
|
+
}),
|
|
1060
|
+
examples: [
|
|
1061
|
+
{
|
|
1062
|
+
args: { contractaddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" },
|
|
1063
|
+
options: { page: 1, offset: 20, chain: "ethereum" },
|
|
1064
|
+
description: "List top 20 holders for a token"
|
|
1065
|
+
}
|
|
1066
|
+
],
|
|
707
1067
|
async run(c) {
|
|
708
|
-
const
|
|
1068
|
+
const apiKey = c.env.ETHERSCAN_API_KEY;
|
|
709
1069
|
const chainId = resolveChainId(c.options.chain);
|
|
710
1070
|
const address = checksumAddress3(c.args.contractaddress);
|
|
711
1071
|
const client = createEtherscanClient(apiKey);
|
|
712
1072
|
const holders = await withRateLimit6(
|
|
713
|
-
() => client.call(
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
1073
|
+
() => client.call(
|
|
1074
|
+
{
|
|
1075
|
+
chainid: chainId,
|
|
1076
|
+
module: "token",
|
|
1077
|
+
action: "tokenholderlist",
|
|
1078
|
+
contractaddress: address,
|
|
1079
|
+
page: c.options.page,
|
|
1080
|
+
offset: c.options.offset
|
|
1081
|
+
},
|
|
1082
|
+
z7.array(holderEntrySchema)
|
|
1083
|
+
),
|
|
721
1084
|
rateLimiter5
|
|
722
1085
|
);
|
|
723
1086
|
const formatted = holders.map((h, i) => ({
|
|
@@ -747,25 +1110,41 @@ tokenCli.command("holders", {
|
|
|
747
1110
|
}
|
|
748
1111
|
});
|
|
749
1112
|
tokenCli.command("supply", {
|
|
750
|
-
description: "Get
|
|
751
|
-
args:
|
|
752
|
-
contractaddress:
|
|
1113
|
+
description: "Get total token supply.",
|
|
1114
|
+
args: z7.object({
|
|
1115
|
+
contractaddress: z7.string().describe("Token contract address")
|
|
753
1116
|
}),
|
|
754
|
-
options:
|
|
1117
|
+
options: z7.object({
|
|
755
1118
|
chain: chainOption5
|
|
756
1119
|
}),
|
|
1120
|
+
env: etherscanEnv,
|
|
1121
|
+
output: z7.object({
|
|
1122
|
+
contractAddress: z7.string(),
|
|
1123
|
+
chain: z7.string(),
|
|
1124
|
+
totalSupply: z7.string()
|
|
1125
|
+
}),
|
|
1126
|
+
examples: [
|
|
1127
|
+
{
|
|
1128
|
+
args: { contractaddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" },
|
|
1129
|
+
options: { chain: "ethereum" },
|
|
1130
|
+
description: "Get total supply for a token"
|
|
1131
|
+
}
|
|
1132
|
+
],
|
|
757
1133
|
async run(c) {
|
|
758
|
-
const
|
|
1134
|
+
const apiKey = c.env.ETHERSCAN_API_KEY;
|
|
759
1135
|
const chainId = resolveChainId(c.options.chain);
|
|
760
1136
|
const address = checksumAddress3(c.args.contractaddress);
|
|
761
1137
|
const client = createEtherscanClient(apiKey);
|
|
762
1138
|
const supply = await withRateLimit6(
|
|
763
|
-
() => client.call(
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
1139
|
+
() => client.call(
|
|
1140
|
+
{
|
|
1141
|
+
chainid: chainId,
|
|
1142
|
+
module: "stats",
|
|
1143
|
+
action: "tokensupply",
|
|
1144
|
+
contractaddress: address
|
|
1145
|
+
},
|
|
1146
|
+
z7.string()
|
|
1147
|
+
),
|
|
769
1148
|
rateLimiter5
|
|
770
1149
|
);
|
|
771
1150
|
return c.ok(
|
|
@@ -786,32 +1165,82 @@ tokenCli.command("supply", {
|
|
|
786
1165
|
});
|
|
787
1166
|
|
|
788
1167
|
// src/commands/tx.ts
|
|
789
|
-
import {
|
|
790
|
-
import { Cli as Cli6, z as
|
|
1168
|
+
import { createRateLimiter as createRateLimiter7, withRateLimit as withRateLimit7 } from "@spectratools/cli-shared";
|
|
1169
|
+
import { Cli as Cli6, z as z8 } from "incur";
|
|
791
1170
|
var rateLimiter6 = createRateLimiter7({ requestsPerSecond: 5 });
|
|
792
|
-
var chainOption6 =
|
|
1171
|
+
var chainOption6 = z8.string().default(DEFAULT_CHAIN).describe("Chain name (abstract, ethereum, base, arbitrum, ...)");
|
|
793
1172
|
var txCli = Cli6.create("tx", {
|
|
794
|
-
description: "Query transaction details, receipts, and status"
|
|
1173
|
+
description: "Query transaction details, receipts, and execution status."
|
|
1174
|
+
});
|
|
1175
|
+
var transactionInfoSchema = z8.object({
|
|
1176
|
+
hash: z8.string(),
|
|
1177
|
+
from: z8.string(),
|
|
1178
|
+
to: z8.string().nullable(),
|
|
1179
|
+
value: z8.string(),
|
|
1180
|
+
gas: z8.string(),
|
|
1181
|
+
gasPrice: z8.string(),
|
|
1182
|
+
nonce: z8.string(),
|
|
1183
|
+
blockNumber: z8.string(),
|
|
1184
|
+
blockHash: z8.string(),
|
|
1185
|
+
input: z8.string()
|
|
1186
|
+
});
|
|
1187
|
+
var transactionReceiptSchema = z8.object({
|
|
1188
|
+
transactionHash: z8.string(),
|
|
1189
|
+
blockNumber: z8.string(),
|
|
1190
|
+
blockHash: z8.string(),
|
|
1191
|
+
from: z8.string(),
|
|
1192
|
+
to: z8.string().nullable(),
|
|
1193
|
+
status: z8.string(),
|
|
1194
|
+
gasUsed: z8.string(),
|
|
1195
|
+
cumulativeGasUsed: z8.string(),
|
|
1196
|
+
contractAddress: z8.string().nullable(),
|
|
1197
|
+
logs: z8.array(z8.unknown())
|
|
1198
|
+
});
|
|
1199
|
+
var txStatusSchema = z8.object({
|
|
1200
|
+
status: z8.string(),
|
|
1201
|
+
errDescription: z8.string()
|
|
795
1202
|
});
|
|
796
1203
|
txCli.command("info", {
|
|
797
|
-
description: "Get transaction details by hash",
|
|
798
|
-
args:
|
|
799
|
-
txhash:
|
|
1204
|
+
description: "Get transaction details by hash.",
|
|
1205
|
+
args: z8.object({
|
|
1206
|
+
txhash: z8.string().describe("Transaction hash")
|
|
800
1207
|
}),
|
|
801
|
-
options:
|
|
1208
|
+
options: z8.object({
|
|
802
1209
|
chain: chainOption6
|
|
803
1210
|
}),
|
|
1211
|
+
env: etherscanEnv,
|
|
1212
|
+
output: z8.object({
|
|
1213
|
+
hash: z8.string(),
|
|
1214
|
+
from: z8.string(),
|
|
1215
|
+
to: z8.string().nullable(),
|
|
1216
|
+
value: z8.string(),
|
|
1217
|
+
gas: z8.string(),
|
|
1218
|
+
gasPrice: z8.string(),
|
|
1219
|
+
nonce: z8.string(),
|
|
1220
|
+
block: z8.string(),
|
|
1221
|
+
chain: z8.string()
|
|
1222
|
+
}),
|
|
1223
|
+
examples: [
|
|
1224
|
+
{
|
|
1225
|
+
args: { txhash: "0x1234...abcd" },
|
|
1226
|
+
options: { chain: "abstract" },
|
|
1227
|
+
description: "Inspect one transaction on Abstract"
|
|
1228
|
+
}
|
|
1229
|
+
],
|
|
804
1230
|
async run(c) {
|
|
805
|
-
const
|
|
1231
|
+
const apiKey = c.env.ETHERSCAN_API_KEY;
|
|
806
1232
|
const chainId = resolveChainId(c.options.chain);
|
|
807
1233
|
const client = createEtherscanClient(apiKey);
|
|
808
1234
|
const tx = await withRateLimit7(
|
|
809
|
-
() => client.callProxy(
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
1235
|
+
() => client.callProxy(
|
|
1236
|
+
{
|
|
1237
|
+
chainid: chainId,
|
|
1238
|
+
module: "proxy",
|
|
1239
|
+
action: "eth_getTransactionByHash",
|
|
1240
|
+
txhash: c.args.txhash
|
|
1241
|
+
},
|
|
1242
|
+
transactionInfoSchema
|
|
1243
|
+
),
|
|
815
1244
|
rateLimiter6
|
|
816
1245
|
);
|
|
817
1246
|
return c.ok(
|
|
@@ -846,24 +1275,46 @@ txCli.command("info", {
|
|
|
846
1275
|
}
|
|
847
1276
|
});
|
|
848
1277
|
txCli.command("receipt", {
|
|
849
|
-
description: "Get the receipt for a transaction",
|
|
850
|
-
args:
|
|
851
|
-
txhash:
|
|
1278
|
+
description: "Get the receipt for a transaction.",
|
|
1279
|
+
args: z8.object({
|
|
1280
|
+
txhash: z8.string().describe("Transaction hash")
|
|
852
1281
|
}),
|
|
853
|
-
options:
|
|
1282
|
+
options: z8.object({
|
|
854
1283
|
chain: chainOption6
|
|
855
1284
|
}),
|
|
1285
|
+
env: etherscanEnv,
|
|
1286
|
+
output: z8.object({
|
|
1287
|
+
hash: z8.string(),
|
|
1288
|
+
block: z8.string(),
|
|
1289
|
+
from: z8.string(),
|
|
1290
|
+
to: z8.string().nullable(),
|
|
1291
|
+
status: z8.string(),
|
|
1292
|
+
gasUsed: z8.string(),
|
|
1293
|
+
contractAddress: z8.string().nullable(),
|
|
1294
|
+
logCount: z8.number(),
|
|
1295
|
+
chain: z8.string()
|
|
1296
|
+
}),
|
|
1297
|
+
examples: [
|
|
1298
|
+
{
|
|
1299
|
+
args: { txhash: "0x1234...abcd" },
|
|
1300
|
+
options: { chain: "ethereum" },
|
|
1301
|
+
description: "Get receipt details including status and logs"
|
|
1302
|
+
}
|
|
1303
|
+
],
|
|
856
1304
|
async run(c) {
|
|
857
|
-
const
|
|
1305
|
+
const apiKey = c.env.ETHERSCAN_API_KEY;
|
|
858
1306
|
const chainId = resolveChainId(c.options.chain);
|
|
859
1307
|
const client = createEtherscanClient(apiKey);
|
|
860
1308
|
const receipt = await withRateLimit7(
|
|
861
|
-
() => client.callProxy(
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
1309
|
+
() => client.callProxy(
|
|
1310
|
+
{
|
|
1311
|
+
chainid: chainId,
|
|
1312
|
+
module: "proxy",
|
|
1313
|
+
action: "eth_getTransactionReceipt",
|
|
1314
|
+
txhash: c.args.txhash
|
|
1315
|
+
},
|
|
1316
|
+
transactionReceiptSchema
|
|
1317
|
+
),
|
|
867
1318
|
rateLimiter6
|
|
868
1319
|
);
|
|
869
1320
|
return c.ok(
|
|
@@ -893,24 +1344,41 @@ txCli.command("receipt", {
|
|
|
893
1344
|
}
|
|
894
1345
|
});
|
|
895
1346
|
txCli.command("status", {
|
|
896
|
-
description: "Check whether a transaction succeeded or failed",
|
|
897
|
-
args:
|
|
898
|
-
txhash:
|
|
1347
|
+
description: "Check whether a transaction succeeded or failed.",
|
|
1348
|
+
args: z8.object({
|
|
1349
|
+
txhash: z8.string().describe("Transaction hash")
|
|
899
1350
|
}),
|
|
900
|
-
options:
|
|
1351
|
+
options: z8.object({
|
|
901
1352
|
chain: chainOption6
|
|
902
1353
|
}),
|
|
1354
|
+
env: etherscanEnv,
|
|
1355
|
+
output: z8.object({
|
|
1356
|
+
hash: z8.string(),
|
|
1357
|
+
status: z8.string(),
|
|
1358
|
+
error: z8.string().optional(),
|
|
1359
|
+
chain: z8.string()
|
|
1360
|
+
}),
|
|
1361
|
+
examples: [
|
|
1362
|
+
{
|
|
1363
|
+
args: { txhash: "0x1234...abcd" },
|
|
1364
|
+
options: { chain: "base" },
|
|
1365
|
+
description: "Get pass/fail status for a transaction"
|
|
1366
|
+
}
|
|
1367
|
+
],
|
|
903
1368
|
async run(c) {
|
|
904
|
-
const
|
|
1369
|
+
const apiKey = c.env.ETHERSCAN_API_KEY;
|
|
905
1370
|
const chainId = resolveChainId(c.options.chain);
|
|
906
1371
|
const client = createEtherscanClient(apiKey);
|
|
907
1372
|
const result = await withRateLimit7(
|
|
908
|
-
() => client.call(
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
1373
|
+
() => client.call(
|
|
1374
|
+
{
|
|
1375
|
+
chainid: chainId,
|
|
1376
|
+
module: "transaction",
|
|
1377
|
+
action: "gettxreceiptstatus",
|
|
1378
|
+
txhash: c.args.txhash
|
|
1379
|
+
},
|
|
1380
|
+
txStatusSchema
|
|
1381
|
+
),
|
|
914
1382
|
rateLimiter6
|
|
915
1383
|
);
|
|
916
1384
|
return c.ok(
|
|
@@ -945,7 +1413,18 @@ cli.command(txCli);
|
|
|
945
1413
|
cli.command(tokenCli);
|
|
946
1414
|
cli.command(gasCli);
|
|
947
1415
|
cli.command(statsCli);
|
|
948
|
-
|
|
1416
|
+
var isMain = (() => {
|
|
1417
|
+
const entrypoint = process.argv[1];
|
|
1418
|
+
if (!entrypoint) {
|
|
1419
|
+
return false;
|
|
1420
|
+
}
|
|
1421
|
+
try {
|
|
1422
|
+
return realpathSync(entrypoint) === realpathSync(fileURLToPath(import.meta.url));
|
|
1423
|
+
} catch {
|
|
1424
|
+
return false;
|
|
1425
|
+
}
|
|
1426
|
+
})();
|
|
1427
|
+
if (isMain) {
|
|
949
1428
|
cli.serve();
|
|
950
1429
|
}
|
|
951
1430
|
export {
|