@etherscan-npm/cli 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Etherscan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,440 @@
1
+ <h1 align="center">Etherscan CLI</h1>
2
+
3
+ <p align="center">
4
+ <strong>Explore EVM chains from your terminal.</strong><br>
5
+ One API key for balances, transactions, tokens, contracts, logs, gas, stats, and more.
6
+ </p>
7
+
8
+ <p align="center">
9
+ <img width="100%" alt="Etherscan CLI interactive explorer" src="https://github.com/user-attachments/assets/98332d40-dda7-415c-8664-8e9c16fa71f4">
10
+ </p>
11
+
12
+ <p align="center">
13
+ <a href="#install">Install</a> ·
14
+ <a href="#get-started">Get started</a> ·
15
+ <a href="#practical-workflows">Examples</a> ·
16
+ <a href="#command-reference">Command reference</a> ·
17
+ <a href="https://docs.etherscan.io/">API documentation</a>
18
+ </p>
19
+
20
+ The official command-line client and interactive explorer for the [Etherscan V2 API](https://docs.etherscan.io/). Use it interactively, pipe clean JSON into scripts, export transactions to CSV, or give an AI agent a predictable interface to on-chain data.
21
+
22
+ ## Why Etherscan CLI?
23
+
24
+ - **Explore interactively** — browse endpoints, fill parameters, switch chains, and inspect results without memorizing commands.
25
+ - **Use one multichain interface** — query Ethereum and supported EVM chains by name or chain ID.
26
+ - **Work with humans or machines** — emit clean JSON by default for scripts and agents, or switch to tables and CSV when a human or a spreadsheet is reading.
27
+ - **Reach broad API coverage** — access accounts, contracts, tokens, logs, blocks, gas, stats, name tags, and proxy methods, with automatic pagination for list endpoints.
28
+
29
+ ## Install
30
+
31
+ Prebuilt releases support macOS, Linux, and Windows on amd64 and arm64. After installing with any persistent method, run `etherscan version` to verify that the binary is on your `PATH`.
32
+
33
+ ### Homebrew — macOS and Linux
34
+
35
+ ```sh
36
+ brew install etherscan/etherscan-cli/etherscan
37
+ etherscan version
38
+ ```
39
+
40
+ ### npm — macOS, Linux, and Windows
41
+
42
+ Install the CLI globally:
43
+
44
+ ```sh
45
+ npm install -g @etherscan-npm/cli
46
+ etherscan version
47
+ ```
48
+
49
+ Or run it once without keeping a global installation:
50
+
51
+ ```sh
52
+ npx @etherscan-npm/cli version
53
+ ```
54
+
55
+ The npm package downloads the matching native release archive and verifies its SHA-256 checksum during installation. Lifecycle scripts must be enabled.
56
+
57
+ ### Installation script — macOS and Linux
58
+
59
+ ```sh
60
+ curl -fsSL https://raw.githubusercontent.com/etherscan/etherscan-cli/master/scripts/install.sh | sh
61
+ ```
62
+
63
+ The script selects the correct amd64 or arm64 archive, verifies its SHA-256 checksum, installs to `~/.local/bin` by default, and adds that directory to your shell profile when needed. Open a new terminal if `etherscan` is not immediately available. Run the installer with `--help` to see version, install-directory, and `PATH` options.
64
+
65
+ ### PowerShell — Windows
66
+
67
+ Run in PowerShell:
68
+
69
+ ```powershell
70
+ irm https://raw.githubusercontent.com/etherscan/etherscan-cli/master/scripts/install.ps1 | iex
71
+ ```
72
+
73
+ Or from Command Prompt:
74
+
75
+ ```bat
76
+ powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://raw.githubusercontent.com/etherscan/etherscan-cli/master/scripts/install.ps1 | iex"
77
+ ```
78
+
79
+ The installer selects the correct x64 or arm64 archive, verifies its SHA-256 checksum, installs to `%LOCALAPPDATA%\Programs\Etherscan\bin` by default, and adds that directory to your user `PATH`. Open a new terminal if `etherscan` is not immediately available.
80
+
81
+ ### Go
82
+
83
+ Go 1.25 or newer is required.
84
+
85
+ ```sh
86
+ go install github.com/etherscan/etherscan-cli/cmd/etherscan@latest
87
+ ```
88
+
89
+ Ensure your Go binary directory (`GOBIN`, or `GOPATH/bin` by default) is on `PATH`.
90
+
91
+ ### Manual
92
+
93
+ Download the archive for your operating system and architecture plus `checksums.txt` from [GitHub Releases](https://github.com/etherscan/etherscan-cli/releases/latest). Verify the archive's SHA-256 checksum, extract it, and place `etherscan` (or `etherscan.exe`) on your `PATH`.
94
+
95
+ ## Get started
96
+
97
+ ### 1. Create an API key
98
+
99
+ Create an API key in your [Etherscan API dashboard](https://etherscan.io/myapikey). One Etherscan V2 key works across supported chains, subject to your API plan.
100
+
101
+ ### 2. Authenticate
102
+
103
+ Validate and save the key locally, then confirm the active identity:
104
+
105
+ ```sh
106
+ etherscan login
107
+ etherscan whoami
108
+ ```
109
+
110
+ For CI or a temporary shell session, set `ETHERSCAN_API_KEY` instead of saving the key.
111
+
112
+ macOS or Linux:
113
+
114
+ ```sh
115
+ export ETHERSCAN_API_KEY="YOUR_API_KEY"
116
+ ```
117
+
118
+ PowerShell:
119
+
120
+ ```powershell
121
+ $env:ETHERSCAN_API_KEY = "YOUR_API_KEY"
122
+ ```
123
+
124
+ Command Prompt:
125
+
126
+ ```bat
127
+ set "ETHERSCAN_API_KEY=YOUR_API_KEY"
128
+ ```
129
+
130
+ ### 3. Make your first request
131
+
132
+ ```sh
133
+ etherscan account balance 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
134
+ ```
135
+
136
+ ### Explore interactively
137
+
138
+ Run `etherscan tui` to open the full-screen endpoint explorer:
139
+
140
+ ```sh
141
+ etherscan tui
142
+ ```
143
+
144
+ The explorer can be opened before authentication and asks you to validate and save a key when you submit an API-backed endpoint. Running `etherscan` with no command prints the Quick Start guide.
145
+
146
+ ## Practical workflows
147
+
148
+ ### Follow wallet activity
149
+
150
+ ```sh
151
+ # Recent normal transactions
152
+ etherscan account txlist 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --page 1 --offset 10
153
+
154
+ # ERC-20 transfers as a readable table instead of the default JSON
155
+ etherscan account tokentx 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 -o table
156
+
157
+ # Collect multiple pages for analysis
158
+ etherscan account txlist 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --all --max-pages 50 -o csv
159
+ ```
160
+
161
+ ### Inspect a smart contract
162
+
163
+ ```sh
164
+ # WETH contract ABI and verified source metadata
165
+ etherscan contract getabi 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
166
+ etherscan contract getsourcecode 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
167
+ ```
168
+
169
+ ### Switch chains
170
+
171
+ ```sh
172
+ # Chain names and numeric IDs both work
173
+ etherscan --chain base gastracker oracle
174
+ etherscan --chain 8453 account balance 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
175
+
176
+ # See every chain built into this release
177
+ etherscan chains
178
+ ```
179
+
180
+ ### Build scripts and agent workflows
181
+
182
+ ```sh
183
+ # Clean JSON for jq, Python, or an AI agent
184
+ etherscan account txlist 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --compact | jq '.[0]'
185
+
186
+ # CSV for spreadsheets and data pipelines
187
+ etherscan account tokentx 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 -o csv > token-transfers.csv
188
+ ```
189
+
190
+ ## Output and Pagination
191
+
192
+ JSON is the default, matching the Etherscan API's own `application/json` responses. Use `-o table` for a readable terminal view of row-shaped results. API results are written to stdout; progress messages, warnings, diagnostics, and errors go to stderr so stdout remains suitable for pipelines and redirection.
193
+
194
+ | Flag | Purpose |
195
+ | --- | --- |
196
+ | `--output <format>`, `-o <format>` | Select `json` (default), `table`, or `csv` |
197
+ | `--compact` | Remove indentation from JSON output |
198
+ | `--all` | Automatically follow pages for supported list commands |
199
+ | `--max-pages <n>` | Stop `--all` after at most `n` pages (default: `20`) |
200
+
201
+ If `--all` reaches `--max-pages`, the CLI prints a warning to stderr and the result may be truncated. Increase the limit or narrow the request with supported filters when you need more results.
202
+
203
+ ## Command reference
204
+
205
+ Every command includes built-in parameter and usage help:
206
+
207
+ ```sh
208
+ etherscan --help
209
+ etherscan account txlist --help
210
+ etherscan contract verify --help
211
+ ```
212
+
213
+ <details>
214
+ <summary><strong>Browse all commands</strong></summary>
215
+
216
+ ### CLI utilities
217
+
218
+ | Command | Description |
219
+ | --- | --- |
220
+ | `etherscan` | Show the Quick Start guide |
221
+ | `etherscan tui` | Launch the interactive explorer |
222
+ | `etherscan login` | Validate and store an API key |
223
+ | `etherscan logout` | Remove the stored API key |
224
+ | `etherscan uninstall` | Remove all CLI configuration |
225
+ | `etherscan update` | Update a Homebrew or installer-script installation |
226
+ | `etherscan whoami` | Show the active chain and masked API key |
227
+ | `etherscan config` | Get, list, or set CLI configuration |
228
+ | `etherscan chains` | List chains built into this CLI release |
229
+ | `etherscan completion` | Generate shell completion |
230
+ | `etherscan version` | Print the CLI version |
231
+ | `etherscan --help` | Show command usage and available options |
232
+
233
+ <!-- BEGIN GENERATED COMMAND INDEX -->
234
+ ### Account
235
+
236
+ | Command | Description | API docs |
237
+ | --- | --- | --- |
238
+ | `etherscan account balance` | Get the native balance of an address | [balance](https://docs.etherscan.io/api-reference/endpoint/balance.md) |
239
+ | `etherscan account balancemulti` | Get native balances for multiple addresses | [balancemulti](https://docs.etherscan.io/api-reference/endpoint/balancemulti.md) |
240
+ | `etherscan account txlist` | List normal transactions for an address or advanced filter | [txlist](https://docs.etherscan.io/api-reference/endpoint/txlist.md), [advanced-filter-txlist](https://docs.etherscan.io/api-reference/endpoint/advanced-filter-txlist.md) |
241
+ | `etherscan account txlistinternal` | List internal transactions by address, transaction hash, block range, or advanced filter | [txlistinternal](https://docs.etherscan.io/api-reference/endpoint/txlistinternal.md), [txlistinternal-blockrange](https://docs.etherscan.io/api-reference/endpoint/txlistinternal-blockrange.md), [txlistinternal-txhash](https://docs.etherscan.io/api-reference/endpoint/txlistinternal-txhash.md), [advanced-filter-txlistinternal](https://docs.etherscan.io/api-reference/endpoint/advanced-filter-txlistinternal.md) |
242
+ | `etherscan account tokentx` | List ERC-20 token transfers | [tokentx](https://docs.etherscan.io/api-reference/endpoint/tokentx.md), [advanced-filter-tokentx](https://docs.etherscan.io/api-reference/endpoint/advanced-filter-tokentx.md) |
243
+ | `etherscan account tokennfttx` | List ERC-721 token transfers | [tokennfttx](https://docs.etherscan.io/api-reference/endpoint/tokennfttx.md), [advanced-filter-tokennfttx](https://docs.etherscan.io/api-reference/endpoint/advanced-filter-tokennfttx.md) |
244
+ | `etherscan account token1155tx` | List ERC-1155 token transfers | [token1155tx](https://docs.etherscan.io/api-reference/endpoint/token1155tx.md), [advanced-filter-token1155tx](https://docs.etherscan.io/api-reference/endpoint/advanced-filter-token1155tx.md) |
245
+ | `etherscan account getminedblocks` | List blocks or uncles mined by an address | [getminedblocks](https://docs.etherscan.io/api-reference/endpoint/getminedblocks.md) |
246
+ | `etherscan account balancehistory` | Get an address's native balance at a block | [balancehistory](https://docs.etherscan.io/api-reference/endpoint/balancehistory.md) |
247
+ | `etherscan account tokenbalance` | Get an address's ERC-20 token balance | [tokenbalance](https://docs.etherscan.io/api-reference/endpoint/tokenbalance.md) |
248
+ | `etherscan account tokenbalancehistory` | Get an address's token balance at a block | [tokenbalancehistory](https://docs.etherscan.io/api-reference/endpoint/tokenbalancehistory.md) |
249
+ | `etherscan account addresstokenbalance` | List ERC-20 holdings for an address | [addresstokenbalance](https://docs.etherscan.io/api-reference/endpoint/addresstokenbalance.md) |
250
+ | `etherscan account addresstokennftbalance` | List NFT holdings for an address | [addresstokennftbalance](https://docs.etherscan.io/api-reference/endpoint/addresstokennftbalance.md) |
251
+ | `etherscan account addresstokennftinventory` | List an address's inventory for an NFT contract | [addresstokennftinventory](https://docs.etherscan.io/api-reference/endpoint/addresstokennftinventory.md) |
252
+ | `etherscan account getdeposittxs` | List L2 deposit transactions | [getdeposittxs](https://docs.etherscan.io/api-reference/endpoint/getdeposittxs.md) |
253
+ | `etherscan account getwithdrawaltxs` | List L2 withdrawal transactions | [getwithdrawaltxs](https://docs.etherscan.io/api-reference/endpoint/getwithdrawaltxs.md) |
254
+ | `etherscan account txsBeaconWithdrawal` | List Ethereum beacon withdrawals | [txsbeaconwithdrawal](https://docs.etherscan.io/api-reference/endpoint/txsbeaconwithdrawal.md) |
255
+ | `etherscan account fundedby` | Find the address that likely funded an account | [fundedby](https://docs.etherscan.io/api-reference/endpoint/fundedby.md) |
256
+ | `etherscan account txnbridge` | List bridge transactions for an address | [txnbridge](https://docs.etherscan.io/api-reference/endpoint/txnbridge.md) |
257
+
258
+ ### Contract
259
+
260
+ | Command | Description | API docs |
261
+ | --- | --- | --- |
262
+ | `etherscan contract getabi` | Get a verified contract's ABI | [getabi](https://docs.etherscan.io/api-reference/endpoint/getabi.md) |
263
+ | `etherscan contract getsourcecode` | Get verified source code and contract metadata | [getsourcecode](https://docs.etherscan.io/api-reference/endpoint/getsourcecode.md) |
264
+ | `etherscan contract getcontractcreation` | Get creator and creation transaction data for contracts | [getcontractcreation](https://docs.etherscan.io/api-reference/endpoint/getcontractcreation.md) |
265
+ | `etherscan contract verify` | Submit contract source code for verification | [verifysourcecode](https://docs.etherscan.io/api-reference/endpoint/verifysourcecode.md) |
266
+ | `etherscan contract verify-status` | Check a source verification submission | [checkverifystatus](https://docs.etherscan.io/api-reference/endpoint/checkverifystatus.md) |
267
+ | `etherscan contract verify-proxy` | Submit a proxy contract for verification | [verifyproxycontract](https://docs.etherscan.io/api-reference/endpoint/verifyproxycontract.md) |
268
+ | `etherscan contract check-proxy` | Check a proxy verification submission | [checkproxyverification](https://docs.etherscan.io/api-reference/endpoint/checkproxyverification.md) |
269
+
270
+ ### Transaction
271
+
272
+ | Command | Description | API docs |
273
+ | --- | --- | --- |
274
+ | `etherscan transaction status` | Get a transaction's execution status and error description | [getstatus](https://docs.etherscan.io/api-reference/endpoint/getstatus.md) |
275
+ | `etherscan transaction receipt-status` | Get a transaction receipt's success or failure status | [gettxreceiptstatus](https://docs.etherscan.io/api-reference/endpoint/gettxreceiptstatus.md) |
276
+
277
+ ### Block
278
+
279
+ | Command | Description | API docs |
280
+ | --- | --- | --- |
281
+ | `etherscan block reward` | Get block and uncle rewards | [getblockreward](https://docs.etherscan.io/api-reference/endpoint/getblockreward.md) |
282
+ | `etherscan block countdown` | Estimate the time remaining until a block | [getblockcountdown](https://docs.etherscan.io/api-reference/endpoint/getblockcountdown.md) |
283
+ | `etherscan block txcount` | Get the number of transactions in a block | [getblocktxnscount](https://docs.etherscan.io/api-reference/endpoint/getblocktxnscount.md) |
284
+ | `etherscan block bytime` | Find the closest block before or after a timestamp | [getblocknobytime](https://docs.etherscan.io/api-reference/endpoint/getblocknobytime.md) |
285
+
286
+ ### Logs
287
+
288
+ | Command | Description | API docs |
289
+ | --- | --- | --- |
290
+ | `etherscan logs get` | Query event logs by block range, address, and topics | [getlogs](https://docs.etherscan.io/api-reference/endpoint/getlogs.md), [getlogs-address-topics](https://docs.etherscan.io/api-reference/endpoint/getlogs-address-topics.md), [getlogs-topics](https://docs.etherscan.io/api-reference/endpoint/getlogs-topics.md) |
291
+
292
+ ### Stats
293
+
294
+ | Command | Description | API docs |
295
+ | --- | --- | --- |
296
+ | `etherscan stats ethsupply` | Get the total ETH supply | [ethsupply](https://docs.etherscan.io/api-reference/endpoint/ethsupply.md) |
297
+ | `etherscan stats ethsupply2` | Get the extended ETH supply breakdown | [ethsupply2](https://docs.etherscan.io/api-reference/endpoint/ethsupply2.md) |
298
+ | `etherscan stats ethprice` | Get the latest ETH price | [ethprice](https://docs.etherscan.io/api-reference/endpoint/ethprice.md) |
299
+ | `etherscan stats chainsize` | Get historical Ethereum chain size data | [chainsize](https://docs.etherscan.io/api-reference/endpoint/chainsize.md) |
300
+ | `etherscan stats nodecount` | Get the total Ethereum node count | [nodecount](https://docs.etherscan.io/api-reference/endpoint/nodecount.md) |
301
+ | `etherscan stats tokensupply` | Get an ERC-20 token's total supply | [tokensupply](https://docs.etherscan.io/api-reference/endpoint/tokensupply.md) |
302
+ | `etherscan stats tokensupplyhistory` | Get a token's total supply at a block | [tokensupplyhistory](https://docs.etherscan.io/api-reference/endpoint/tokensupplyhistory.md) |
303
+ | `etherscan stats ethdailyprice` | Get historical daily ETH prices | [ethdailyprice](https://docs.etherscan.io/api-reference/endpoint/ethdailyprice.md) |
304
+ | `etherscan stats dailytx` | Get historical daily transaction counts | [dailytx](https://docs.etherscan.io/api-reference/endpoint/dailytx.md) |
305
+ | `etherscan stats dailynewaddress` | Get historical daily new-address counts | [dailynewaddress](https://docs.etherscan.io/api-reference/endpoint/dailynewaddress.md) |
306
+ | `etherscan stats dailyavgblocksize` | Get historical average daily block size | [dailyavgblocksize](https://docs.etherscan.io/api-reference/endpoint/dailyavgblocksize.md) |
307
+ | `etherscan stats dailyavgblocktime` | Get historical average daily block time | [dailyavgblocktime](https://docs.etherscan.io/api-reference/endpoint/dailyavgblocktime.md) |
308
+ | `etherscan stats dailyavggasprice` | Get historical average daily gas price | [dailyavggasprice](https://docs.etherscan.io/api-reference/endpoint/dailyavggasprice.md) |
309
+ | `etherscan stats dailyavggaslimit` | Get historical average daily gas limit | [dailyavggaslimit](https://docs.etherscan.io/api-reference/endpoint/dailyavggaslimit.md) |
310
+ | `etherscan stats dailygasused` | Get historical total daily gas used | [dailygasused](https://docs.etherscan.io/api-reference/endpoint/dailygasused.md) |
311
+ | `etherscan stats dailyblockrewards` | Get historical daily block rewards | [dailyblockrewards](https://docs.etherscan.io/api-reference/endpoint/dailyblockrewards.md) |
312
+ | `etherscan stats dailyblkcount` | Get historical daily block counts | [dailyblkcount](https://docs.etherscan.io/api-reference/endpoint/dailyblkcount.md) |
313
+ | `etherscan stats dailytxnfee` | Get historical daily transaction fees | [dailytxnfee](https://docs.etherscan.io/api-reference/endpoint/dailytxnfee.md) |
314
+ | `etherscan stats dailynetutilization` | Get historical daily network utilization | [dailynetutilization](https://docs.etherscan.io/api-reference/endpoint/dailynetutilization.md) |
315
+ | `etherscan stats dailyuncleblkcount` | Get historical daily uncle block counts | [dailyuncleblkcount](https://docs.etherscan.io/api-reference/endpoint/dailyuncleblkcount.md) |
316
+ | `etherscan stats dailyavghashrate` | Get historical average daily network hash rate | [dailyavghashrate](https://docs.etherscan.io/api-reference/endpoint/dailyavghashrate.md) |
317
+ | `etherscan stats dailyavgnetdifficulty` | Get historical average daily network difficulty | [dailyavgnetdifficulty](https://docs.etherscan.io/api-reference/endpoint/dailyavgnetdifficulty.md) |
318
+ | `etherscan stats dailyensregister` | Get historical daily ENS registration counts | [dailyensregister](https://docs.etherscan.io/api-reference/endpoint/dailyensregister.md) |
319
+ | `etherscan stats nodecounthistory` | Get historical Ethereum node counts | [nodecounthistory](https://docs.etherscan.io/api-reference/endpoint/nodecounthistory.md) |
320
+
321
+ ### Token
322
+
323
+ | Command | Description | API docs |
324
+ | --- | --- | --- |
325
+ | `etherscan token info` | Get token metadata such as name, symbol, type, and supply | [tokeninfo](https://docs.etherscan.io/api-reference/endpoint/tokeninfo.md) |
326
+ | `etherscan token tokenholderlist` | List token holders and their balances | [tokenholderlist](https://docs.etherscan.io/api-reference/endpoint/tokenholderlist.md) |
327
+ | `etherscan token tokenholdercount` | Get a token's holder count | [tokenholdercount](https://docs.etherscan.io/api-reference/endpoint/tokenholdercount.md) |
328
+ | `etherscan token topholders` | Get the largest token holders | [topholders](https://docs.etherscan.io/api-reference/endpoint/topholders.md) |
329
+
330
+ ### Gas Tracker
331
+
332
+ | Command | Description | API docs |
333
+ | --- | --- | --- |
334
+ | `etherscan gastracker oracle` | Get safe, proposed, and fast gas prices | [gasoracle](https://docs.etherscan.io/api-reference/endpoint/gasoracle.md) |
335
+ | `etherscan gastracker estimate` | Estimate confirmation time for a gas price | [gasestimate](https://docs.etherscan.io/api-reference/endpoint/gasestimate.md) |
336
+
337
+ ### Nametag
338
+
339
+ | Command | Description | API docs |
340
+ | --- | --- | --- |
341
+ | `etherscan nametag getaddresstag` | Get name tags and metadata for addresses (Pro Plus) | [getaddresstag](https://docs.etherscan.io/api-reference/endpoint/getaddresstag.md) |
342
+
343
+ ### Proxy
344
+
345
+ | Command | Description | API docs |
346
+ | --- | --- | --- |
347
+ | `etherscan proxy eth_blockNumber` | Get the latest block number | [ethblocknumber](https://docs.etherscan.io/api-reference/endpoint/ethblocknumber.md) |
348
+ | `etherscan proxy eth_getBlockByNumber` | Get a block by number or tag | [ethgetblockbynumber](https://docs.etherscan.io/api-reference/endpoint/ethgetblockbynumber.md) |
349
+ | `etherscan proxy eth_getTransactionByHash` | Get a transaction by hash | [ethgettransactionbyhash](https://docs.etherscan.io/api-reference/endpoint/ethgettransactionbyhash.md) |
350
+ | `etherscan proxy eth_getTransactionByBlockNumberAndIndex` | Get a transaction by block number and index | [ethgettransactionbyblocknumberandindex](https://docs.etherscan.io/api-reference/endpoint/ethgettransactionbyblocknumberandindex.md) |
351
+ | `etherscan proxy eth_getTransactionCount` | Get an address's transaction count (nonce) | [ethgettransactioncount](https://docs.etherscan.io/api-reference/endpoint/ethgettransactioncount.md) |
352
+ | `etherscan proxy eth_getBlockTransactionCountByNumber` | Get a block's transaction count | [ethgetblocktransactioncountbynumber](https://docs.etherscan.io/api-reference/endpoint/ethgetblocktransactioncountbynumber.md) |
353
+ | `etherscan proxy eth_getUncleByBlockNumberAndIndex` | Get an uncle by block number and index | [ethgetunclebyblocknumberandindex](https://docs.etherscan.io/api-reference/endpoint/ethgetunclebyblocknumberandindex.md) |
354
+ | `etherscan proxy eth_sendRawTransaction` | Broadcast a signed raw transaction | [ethsendrawtransaction](https://docs.etherscan.io/api-reference/endpoint/ethsendrawtransaction.md) |
355
+ | `etherscan proxy eth_call` | Execute a read-only contract call | [ethcall](https://docs.etherscan.io/api-reference/endpoint/ethcall.md) |
356
+ | `etherscan proxy eth_estimateGas` | Estimate the gas required for a transaction | [ethestimategas](https://docs.etherscan.io/api-reference/endpoint/ethestimategas.md) |
357
+ | `etherscan proxy eth_getTransactionReceipt` | Get a transaction receipt by hash | [ethgettransactionreceipt](https://docs.etherscan.io/api-reference/endpoint/ethgettransactionreceipt.md) |
358
+ | `etherscan proxy eth_getCode` | Get the code stored at an address | [ethgetcode](https://docs.etherscan.io/api-reference/endpoint/ethgetcode.md) |
359
+ | `etherscan proxy eth_getStorageAt` | Get a value from a contract storage position | [ethgetstorageat](https://docs.etherscan.io/api-reference/endpoint/ethgetstorageat.md) |
360
+ | `etherscan proxy eth_gasPrice` | Get the current gas price | [ethgasprice](https://docs.etherscan.io/api-reference/endpoint/ethgasprice.md) |
361
+
362
+ ### API Usage
363
+
364
+ | Command | Description | API docs |
365
+ | --- | --- | --- |
366
+ | `etherscan apilimit` | Show used, available, and total API credits | [getapilimit](https://docs.etherscan.io/api-reference/endpoint/getapilimit.md) |
367
+ <!-- END GENERATED COMMAND INDEX -->
368
+
369
+ </details>
370
+
371
+ ## Configuration and authentication
372
+
373
+ Authentication is resolved in this order: `--api-key` for the current command, `ETHERSCAN_API_KEY`, then the key saved by `etherscan login`.
374
+
375
+ `etherscan login` and the TUI key-setup prompt store the key as plaintext in `$XDG_CONFIG_HOME/etherscan/config.toml` when `XDG_CONFIG_HOME` is set, or `~/.etherscan/config.toml` otherwise. The directory and file are created with restrictive permissions where the operating system supports them. Treat this file as a secret, never commit API keys, and prefer the environment variable or `--api-key` for CI and temporary sessions.
376
+
377
+ `etherscan logout` removes only the saved key. If `ETHERSCAN_API_KEY` is set, it remains active until you unset it in that shell or environment.
378
+
379
+ Manage non-secret defaults with:
380
+
381
+ ```sh
382
+ etherscan config list
383
+ etherscan config set default_chain=base
384
+ etherscan config set default_output=table
385
+ ```
386
+
387
+ For the active chain, `--chain` takes precedence over `ETHERSCAN_CHAIN`, which takes precedence over `default_chain` in the configuration file.
388
+
389
+ ## Updating
390
+
391
+ Update through the same channel used to install the CLI:
392
+
393
+ | Installation channel | Update command |
394
+ | --- | --- |
395
+ | Homebrew | `brew upgrade etherscan/etherscan-cli/etherscan` |
396
+ | npm | `npm install -g @etherscan-npm/cli@latest` |
397
+ | macOS/Linux or Windows installer script | `etherscan update` |
398
+ | Go | `go install github.com/etherscan/etherscan-cli/cmd/etherscan@latest` |
399
+ | Manual release archive | Download and verify the new archive from [GitHub Releases](https://github.com/etherscan/etherscan-cli/releases/latest) |
400
+
401
+ Use the channel-specific command above rather than mixing update mechanisms. For an npm-managed installation, `etherscan update` prints the npm command without modifying files under `node_modules`.
402
+
403
+ ## Shell completion
404
+
405
+ Generate completion for bash, zsh, fish, or PowerShell, then load or save the output according to your shell's completion setup:
406
+
407
+ ```sh
408
+ etherscan completion bash
409
+ etherscan completion zsh
410
+ etherscan completion fish
411
+ etherscan completion powershell
412
+ ```
413
+
414
+ ## Development
415
+
416
+ Go 1.25 or newer is required to build the CLI.
417
+
418
+ ```sh
419
+ # Run the Go test suite
420
+ go test ./...
421
+
422
+ # Build the CLI
423
+ go build -o etherscan ./cmd/etherscan
424
+ ```
425
+
426
+ Installer changes can be checked with `sh scripts/test-install.sh` on macOS/Linux or `./scripts/test-install.ps1` in PowerShell on Windows.
427
+
428
+ The npm distribution can be checked with `sh scripts/test-npm.sh` on macOS/Linux or `./scripts/test-npm.ps1` in PowerShell. These tests pack and install the package against local fixture release archives; they do not publish to npm.
429
+
430
+ For the first npm release, publish the GitHub release assets before publishing the package. From an exact release-tag checkout, run `npm version --no-git-tag-version <version>` followed by `npm publish --access public`. Then configure npm trusted publishing for `etherscan/etherscan-cli` and `.github/workflows/release.yml`, and set the `NPM_PUBLISH_ENABLED` repository variable to `true` for later tagged releases.
431
+
432
+ ## API coverage and support
433
+
434
+ Endpoint, chain, and plan availability can differ. The Etherscan documentation is authoritative for [supported chains](https://docs.etherscan.io/supported-chains), [rate limits](https://docs.etherscan.io/resources/rate-limits), [PRO endpoints](https://docs.etherscan.io/resources/pro-endpoints), parameters, responses, and [API errors](https://docs.etherscan.io/resources/common-error-messages).
435
+
436
+ Report CLI bugs and feature requests in [GitHub Issues](https://github.com/etherscan/etherscan-cli/issues). For API-key, account, billing, or endpoint-support questions, use the [Etherscan support form](https://etherscan.io/contactus?id=11).
437
+
438
+ ## License
439
+
440
+ [MIT](LICENSE)
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+
3
+ "use strict";
4
+
5
+ const fs = require("node:fs");
6
+ const path = require("node:path");
7
+ const { spawnSync } = require("node:child_process");
8
+
9
+ const packageRoot = path.resolve(__dirname, "..", "..");
10
+ const executable = path.join(
11
+ packageRoot,
12
+ "vendor",
13
+ process.platform === "win32" ? "etherscan.exe" : "etherscan",
14
+ );
15
+
16
+ if (!fs.existsSync(executable)) {
17
+ console.error(
18
+ "Etherscan CLI is not installed in this npm package. " +
19
+ "Reinstall @etherscan-npm/cli without --ignore-scripts.",
20
+ );
21
+ process.exit(1);
22
+ }
23
+
24
+ const result = spawnSync(executable, process.argv.slice(2), {
25
+ stdio: "inherit",
26
+ env: {
27
+ ...process.env,
28
+ ETHERSCAN_INSTALL_METHOD: "npm",
29
+ },
30
+ });
31
+
32
+ if (result.error) {
33
+ console.error(`Failed to start Etherscan CLI: ${result.error.message}`);
34
+ process.exit(1);
35
+ }
36
+
37
+ if (result.signal) {
38
+ console.error(`Etherscan CLI terminated by signal ${result.signal}.`);
39
+ process.exit(1);
40
+ }
41
+
42
+ process.exit(result.status === null ? 1 : result.status);
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+ const { spawnSync } = require("node:child_process");
6
+ const packageInfo = require("../package.json");
7
+
8
+ const packageRoot = path.resolve(__dirname, "..");
9
+ const installDir = path.join(packageRoot, "vendor");
10
+ const version = `v${packageInfo.version}`;
11
+
12
+ let command;
13
+ let args;
14
+ if (process.platform === "win32") {
15
+ command = "powershell.exe";
16
+ args = [
17
+ "-NoLogo",
18
+ "-NoProfile",
19
+ "-NonInteractive",
20
+ "-ExecutionPolicy",
21
+ "Bypass",
22
+ "-File",
23
+ path.join(packageRoot, "scripts", "install.ps1"),
24
+ "-Version",
25
+ version,
26
+ "-InstallDir",
27
+ installDir,
28
+ "-NoPathUpdate",
29
+ ];
30
+ } else {
31
+ command = "sh";
32
+ args = [
33
+ path.join(packageRoot, "scripts", "install.sh"),
34
+ "--version",
35
+ version,
36
+ "--install-dir",
37
+ installDir,
38
+ "--no-path-update",
39
+ ];
40
+ }
41
+
42
+ const result = spawnSync(command, args, { stdio: "inherit", env: process.env });
43
+ if (result.error) {
44
+ console.error(`Unable to run the Etherscan CLI installer: ${result.error.message}`);
45
+ process.exit(1);
46
+ }
47
+ if (result.status !== 0) {
48
+ process.exit(result.status === null ? 1 : result.status);
49
+ }
50
+
51
+ const executable = path.join(
52
+ installDir,
53
+ process.platform === "win32" ? "etherscan.exe" : "etherscan",
54
+ );
55
+ if (!fs.existsSync(executable)) {
56
+ console.error(`The installer did not create ${executable}.`);
57
+ process.exit(1);
58
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+
3
+ const packageInfo = require("../package.json");
4
+
5
+ try {
6
+ if (packageInfo.version === "0.0.0-development") {
7
+ throw new Error("the development placeholder version cannot be published");
8
+ }
9
+ if (!/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(packageInfo.version)) {
10
+ throw new Error(`invalid release version: ${packageInfo.version}`);
11
+ }
12
+ } catch (error) {
13
+ console.error(`Refusing to publish @etherscan/cli: ${error.message}`);
14
+ process.exit(1);
15
+ }
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@etherscan-npm/cli",
3
+ "version": "1.0.1",
4
+ "description": "Command-line client and interactive explorer for the Etherscan V2 API",
5
+ "license": "MIT",
6
+ "homepage": "https://github.com/etherscan/etherscan-cli",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/etherscan/etherscan-cli.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/etherscan/etherscan-cli/issues"
13
+ },
14
+ "type": "commonjs",
15
+ "bin": {
16
+ "etherscan": "npm/bin/etherscan.js"
17
+ },
18
+ "scripts": {
19
+ "postinstall": "node npm/postinstall.js",
20
+ "prepublishOnly": "node npm/prepublish-check.js",
21
+ "test:package": "npm pack --dry-run",
22
+ "test:release": "node npm/publish.test.js"
23
+ },
24
+ "files": [
25
+ "npm/bin/etherscan.js",
26
+ "npm/postinstall.js",
27
+ "npm/prepublish-check.js",
28
+ "scripts/install.sh",
29
+ "scripts/install.ps1",
30
+ "README.md",
31
+ "LICENSE"
32
+ ],
33
+ "engines": {
34
+ "node": ">=18"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ }
39
+ }
@@ -0,0 +1,262 @@
1
+ [CmdletBinding()]
2
+ param(
3
+ [string]$Version = $env:ETHERSCAN_VERSION,
4
+ [string]$InstallDir = $env:ETHERSCAN_INSTALL_DIR,
5
+ [switch]$NoPathUpdate,
6
+ [int]$WaitForProcessId = 0,
7
+ [switch]$CleanupScript
8
+ )
9
+
10
+ $ErrorActionPreference = "Stop"
11
+ $ProgressPreference = "SilentlyContinue"
12
+
13
+ $Repository = "etherscan/etherscan-cli"
14
+ $DownloadBaseUrl = $env:ETHERSCAN_INSTALL_TEST_DOWNLOAD_BASE_URL
15
+
16
+ function Get-EtherscanArchitecture {
17
+ $architecture = $env:PROCESSOR_ARCHITEW6432
18
+ if ([string]::IsNullOrWhiteSpace($architecture)) {
19
+ $architecture = $env:PROCESSOR_ARCHITECTURE
20
+ }
21
+
22
+ switch -Regex ($architecture) {
23
+ "^(AMD64|x86_64)$" { return "amd64" }
24
+ "^(ARM64|aarch64)$" { return "arm64" }
25
+ default { throw "Unsupported Windows architecture: $architecture. Etherscan CLI supports amd64 and arm64." }
26
+ }
27
+ }
28
+
29
+ function Get-GitHubApiHeaders {
30
+ $headers = @{
31
+ Accept = "application/vnd.github+json"
32
+ "User-Agent" = "etherscan-cli-installer"
33
+ "X-GitHub-Api-Version" = "2022-11-28"
34
+ }
35
+ if (-not [string]::IsNullOrWhiteSpace($env:ETHERSCAN_GITHUB_TOKEN)) {
36
+ $headers.Authorization = "Bearer $($env:ETHERSCAN_GITHUB_TOKEN)"
37
+ }
38
+ return $headers
39
+ }
40
+
41
+ function Resolve-EtherscanVersion {
42
+ param([string]$RequestedVersion)
43
+
44
+ if (-not [string]::IsNullOrWhiteSpace($RequestedVersion) -and $RequestedVersion -ne "latest") {
45
+ $tag = if ($RequestedVersion.StartsWith("v")) { $RequestedVersion } else { "v$RequestedVersion" }
46
+ }
47
+ else {
48
+ if (-not [string]::IsNullOrWhiteSpace($DownloadBaseUrl)) {
49
+ throw "A version is required when the installer test download source is used."
50
+ }
51
+ $release = Invoke-RestMethod `
52
+ -Uri "https://api.github.com/repos/$Repository/releases/latest" `
53
+ -Headers (Get-GitHubApiHeaders)
54
+ $tag = [string]$release.tag_name
55
+ }
56
+
57
+ if ($tag -notmatch '^v[0-9]+\.[0-9]+\.[0-9]+(?:[-+][0-9A-Za-z.-]+)?$') {
58
+ throw "Invalid release version: $tag"
59
+ }
60
+
61
+ return @{
62
+ Tag = $tag
63
+ Version = $tag.Substring(1)
64
+ }
65
+ }
66
+
67
+ function Copy-InstallerFile {
68
+ param(
69
+ [string]$Base,
70
+ [string]$Name,
71
+ [string]$Destination
72
+ )
73
+
74
+ if (Test-Path -LiteralPath $Base -PathType Container) {
75
+ Copy-Item -LiteralPath (Join-Path $Base $Name) -Destination $Destination
76
+ return
77
+ }
78
+
79
+ $uri = "$($Base.TrimEnd('/'))/$Name"
80
+ $parsedUri = [Uri]$uri
81
+ if ($parsedUri.Scheme -ne "https") {
82
+ throw "Remote downloads must use HTTPS: $uri"
83
+ }
84
+
85
+ $headers = @{
86
+ "User-Agent" = "etherscan-cli-installer"
87
+ }
88
+ if ($parsedUri.Host -in @("github.com", "api.github.com") -and
89
+ -not [string]::IsNullOrWhiteSpace($env:ETHERSCAN_GITHUB_TOKEN)) {
90
+ $headers.Authorization = "Bearer $($env:ETHERSCAN_GITHUB_TOKEN)"
91
+ }
92
+
93
+ Invoke-WebRequest -Uri $uri -OutFile $Destination -Headers $headers -UseBasicParsing
94
+ }
95
+
96
+ function Get-SHA256FileHash {
97
+ param([string]$Path)
98
+
99
+ $sha256 = [Security.Cryptography.SHA256]::Create()
100
+ try {
101
+ $stream = [IO.File]::OpenRead($Path)
102
+ try {
103
+ return ([BitConverter]::ToString($sha256.ComputeHash($stream))).Replace("-", "").ToLowerInvariant()
104
+ }
105
+ finally {
106
+ $stream.Dispose()
107
+ }
108
+ }
109
+ finally {
110
+ $sha256.Dispose()
111
+ }
112
+ }
113
+
114
+ function Add-EtherscanToUserPath {
115
+ param([string]$Directory)
116
+
117
+ $fullDirectory = [IO.Path]::GetFullPath($Directory).TrimEnd('\')
118
+ $userPath = [Environment]::GetEnvironmentVariable("Path", "User")
119
+ $entries = @($userPath -split ';' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
120
+ $alreadyPresent = $entries | Where-Object {
121
+ try {
122
+ $expandedEntry = [Environment]::ExpandEnvironmentVariables($_)
123
+ [IO.Path]::GetFullPath($expandedEntry).TrimEnd('\').Equals($fullDirectory, [StringComparison]::OrdinalIgnoreCase)
124
+ }
125
+ catch {
126
+ $_.TrimEnd('\').Equals($fullDirectory, [StringComparison]::OrdinalIgnoreCase)
127
+ }
128
+ }
129
+
130
+ if (-not $alreadyPresent) {
131
+ $newUserPath = if ([string]::IsNullOrWhiteSpace($userPath)) {
132
+ $fullDirectory
133
+ }
134
+ else {
135
+ "$($userPath.TrimEnd(';'));$fullDirectory"
136
+ }
137
+ [Environment]::SetEnvironmentVariable("Path", $newUserPath, "User")
138
+ Write-Host "Added $fullDirectory to your user PATH."
139
+ }
140
+
141
+ $processEntries = @($env:Path -split ';')
142
+ if (-not ($processEntries | Where-Object { $_.TrimEnd('\').Equals($fullDirectory, [StringComparison]::OrdinalIgnoreCase) })) {
143
+ $env:Path = "$env:Path;$fullDirectory"
144
+ }
145
+ }
146
+
147
+ if ($env:OS -ne "Windows_NT") {
148
+ throw "This installer supports Windows only. Use install.sh on macOS or Linux."
149
+ }
150
+
151
+ if ([string]::IsNullOrWhiteSpace($InstallDir)) {
152
+ $localAppData = [Environment]::GetFolderPath([Environment+SpecialFolder]::LocalApplicationData)
153
+ $InstallDir = Join-Path $localAppData "Programs\Etherscan\bin"
154
+ }
155
+ if ($InstallDir.Contains(';')) {
156
+ throw "The installation directory cannot contain a semicolon."
157
+ }
158
+ if ($InstallDir.IndexOfAny([char[]]"`r`n") -ge 0) {
159
+ throw "The installation directory cannot contain a line break."
160
+ }
161
+ if ($WaitForProcessId -gt 0) {
162
+ Wait-Process -Id $WaitForProcessId -ErrorAction SilentlyContinue
163
+ }
164
+
165
+ $resolved = Resolve-EtherscanVersion -RequestedVersion $Version
166
+ $architecture = Get-EtherscanArchitecture
167
+ $archiveName = "etherscan_$($resolved.Version)_windows_$architecture.zip"
168
+ $baseUrl = if ([string]::IsNullOrWhiteSpace($DownloadBaseUrl)) {
169
+ "https://github.com/$Repository/releases/download/$($resolved.Tag)"
170
+ }
171
+ else {
172
+ $DownloadBaseUrl
173
+ }
174
+
175
+ $tempDirectory = Join-Path ([IO.Path]::GetTempPath()) "etherscan-install-$PID-$([Guid]::NewGuid().ToString('N'))"
176
+ $archivePath = Join-Path $tempDirectory $archiveName
177
+ $checksumPath = Join-Path $tempDirectory "checksums.txt"
178
+ $sourceExecutable = Join-Path $tempDirectory "etherscan.exe"
179
+
180
+ try {
181
+ New-Item -ItemType Directory -Path $tempDirectory -Force | Out-Null
182
+
183
+ Write-Host "Downloading Etherscan CLI $($resolved.Version) for windows/$architecture..."
184
+ Copy-InstallerFile -Base $baseUrl -Name $archiveName -Destination $archivePath
185
+ Copy-InstallerFile -Base $baseUrl -Name "checksums.txt" -Destination $checksumPath
186
+
187
+ $pattern = '^([0-9A-Fa-f]{64})\s+\*?' + [Regex]::Escape($archiveName) + '$'
188
+ $checksumLine = Get-Content -LiteralPath $checksumPath | Where-Object { $_ -match $pattern } | Select-Object -First 1
189
+ if (-not $checksumLine -or $checksumLine -notmatch $pattern) {
190
+ throw "No checksum was published for $archiveName."
191
+ }
192
+
193
+ $expectedHash = $Matches[1].ToLowerInvariant()
194
+ $actualHash = Get-SHA256FileHash -Path $archivePath
195
+ if ($actualHash -ne $expectedHash) {
196
+ throw "Checksum verification failed for $archiveName. Expected $expectedHash, received $actualHash."
197
+ }
198
+
199
+ Add-Type -AssemblyName System.IO.Compression.FileSystem
200
+ $zip = [IO.Compression.ZipFile]::OpenRead($archivePath)
201
+ try {
202
+ $executableEntries = @($zip.Entries | Where-Object { $_.FullName.Replace('\', '/') -eq "etherscan.exe" })
203
+ if ($executableEntries.Count -ne 1) {
204
+ throw "$archiveName must contain exactly one root-level etherscan.exe."
205
+ }
206
+
207
+ $inputStream = $executableEntries[0].Open()
208
+ $outputStream = [IO.File]::Create($sourceExecutable)
209
+ try {
210
+ $inputStream.CopyTo($outputStream)
211
+ }
212
+ finally {
213
+ $outputStream.Dispose()
214
+ $inputStream.Dispose()
215
+ }
216
+ }
217
+ finally {
218
+ $zip.Dispose()
219
+ }
220
+
221
+ New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
222
+ $targetExecutable = Join-Path $InstallDir "etherscan.exe"
223
+ $stagedExecutable = Join-Path $InstallDir ".etherscan.exe.new-$PID"
224
+ $backupExecutable = Join-Path $InstallDir ".etherscan.exe.old-$PID"
225
+ Copy-Item -LiteralPath $sourceExecutable -Destination $stagedExecutable -Force
226
+
227
+ try {
228
+ if (Test-Path -LiteralPath $targetExecutable) {
229
+ Move-Item -LiteralPath $targetExecutable -Destination $backupExecutable -Force
230
+ }
231
+ Move-Item -LiteralPath $stagedExecutable -Destination $targetExecutable -Force
232
+ Remove-Item -LiteralPath $backupExecutable -Force -ErrorAction SilentlyContinue
233
+ }
234
+ catch {
235
+ Remove-Item -LiteralPath $stagedExecutable -Force -ErrorAction SilentlyContinue
236
+ if ((Test-Path -LiteralPath $backupExecutable) -and -not (Test-Path -LiteralPath $targetExecutable)) {
237
+ Move-Item -LiteralPath $backupExecutable -Destination $targetExecutable -Force
238
+ }
239
+ throw
240
+ }
241
+
242
+ if (-not $NoPathUpdate) {
243
+ Add-EtherscanToUserPath -Directory $InstallDir
244
+ }
245
+
246
+ Write-Host ""
247
+ Write-Host "Etherscan CLI $($resolved.Version) installed successfully."
248
+ Write-Host "Installed to: $targetExecutable"
249
+ if ($NoPathUpdate) {
250
+ Write-Host "Add $InstallDir to PATH to run etherscan from any directory."
251
+ }
252
+ else {
253
+ Write-Host "Run 'etherscan version' to verify the installation."
254
+ Write-Host "Open a new terminal if the command is not yet available."
255
+ }
256
+ }
257
+ finally {
258
+ Remove-Item -LiteralPath $tempDirectory -Recurse -Force -ErrorAction SilentlyContinue
259
+ if ($CleanupScript -and -not [string]::IsNullOrWhiteSpace($PSCommandPath)) {
260
+ Remove-Item -LiteralPath $PSCommandPath -Force -ErrorAction SilentlyContinue
261
+ }
262
+ }
@@ -0,0 +1,211 @@
1
+ #!/bin/sh
2
+
3
+ set -eu
4
+
5
+ repository="etherscan/etherscan-cli"
6
+ version=${ETHERSCAN_VERSION:-}
7
+ install_dir=${ETHERSCAN_INSTALL_DIR:-"$HOME/.local/bin"}
8
+ download_base=${ETHERSCAN_INSTALL_TEST_DOWNLOAD_BASE_URL:-}
9
+ update_path=1
10
+
11
+ usage() {
12
+ cat <<'EOF'
13
+ Install Etherscan CLI.
14
+
15
+ Usage: install.sh [options]
16
+
17
+ Options:
18
+ --version VERSION Install a specific version (for example, v1.1.0).
19
+ --install-dir DIRECTORY Install into DIRECTORY (default: ~/.local/bin).
20
+ --no-path-update Do not update the shell profile.
21
+ -h, --help Show this help.
22
+ EOF
23
+ }
24
+
25
+ die() {
26
+ printf 'error: %s\n' "$*" >&2
27
+ exit 1
28
+ }
29
+
30
+ while [ "$#" -gt 0 ]; do
31
+ case "$1" in
32
+ --version)
33
+ [ "$#" -ge 2 ] || die "--version requires a value"
34
+ version=$2
35
+ shift 2
36
+ ;;
37
+ --install-dir)
38
+ [ "$#" -ge 2 ] || die "--install-dir requires a value"
39
+ install_dir=$2
40
+ shift 2
41
+ ;;
42
+ --no-path-update)
43
+ update_path=0
44
+ shift
45
+ ;;
46
+ -h|--help)
47
+ usage
48
+ exit 0
49
+ ;;
50
+ *)
51
+ die "unknown option: $1"
52
+ ;;
53
+ esac
54
+ done
55
+
56
+ if printf '%s' "$install_dir" | LC_ALL=C grep '[[:cntrl:]]' >/dev/null 2>&1; then
57
+ die "the installation directory cannot contain control characters"
58
+ fi
59
+
60
+ fetch_stdout() {
61
+ url=$1
62
+ if command -v curl >/dev/null 2>&1; then
63
+ curl -fsSL -A etherscan-cli-installer "$url"
64
+ elif command -v wget >/dev/null 2>&1; then
65
+ wget -qO- --user-agent=etherscan-cli-installer "$url"
66
+ else
67
+ die "curl or wget is required"
68
+ fi
69
+ }
70
+
71
+ fetch_file() {
72
+ base=$1
73
+ name=$2
74
+ destination=$3
75
+
76
+ if [ -d "$base" ]; then
77
+ cp "$base/$name" "$destination"
78
+ return
79
+ fi
80
+
81
+ case "$base" in
82
+ file://*)
83
+ cp "${base#file://}/$name" "$destination"
84
+ ;;
85
+ https://*)
86
+ if command -v curl >/dev/null 2>&1; then
87
+ curl -fsSL -A etherscan-cli-installer "$base/$name" -o "$destination"
88
+ elif command -v wget >/dev/null 2>&1; then
89
+ wget -q --user-agent=etherscan-cli-installer "$base/$name" -O "$destination"
90
+ else
91
+ die "curl or wget is required"
92
+ fi
93
+ ;;
94
+ *)
95
+ die "invalid download base URL or directory: $base"
96
+ ;;
97
+ esac
98
+ }
99
+
100
+ if [ -z "$version" ] || [ "$version" = latest ]; then
101
+ [ -z "$download_base" ] || die "a version is required with the installer test download source"
102
+ release_json=$(fetch_stdout "https://api.github.com/repos/$repository/releases/latest")
103
+ version=$(printf '%s\n' "$release_json" | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | sed -n '1p')
104
+ [ -n "$version" ] || die "could not resolve the latest Etherscan CLI version"
105
+ fi
106
+
107
+ case "$version" in
108
+ v*) tag=$version; release_version=${version#v} ;;
109
+ *) tag="v$version"; release_version=$version ;;
110
+ esac
111
+
112
+ printf '%s\n' "$tag" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z.-]+)?$' || die "invalid release version: $tag"
113
+
114
+ system_name=${ETHERSCAN_INSTALL_TEST_OS:-$(uname -s)}
115
+ case "$system_name" in
116
+ Linux|linux) os=linux ;;
117
+ Darwin|darwin) os=darwin ;;
118
+ *) die "unsupported operating system: $system_name" ;;
119
+ esac
120
+
121
+ machine_arch=${ETHERSCAN_INSTALL_TEST_ARCH:-$(uname -m)}
122
+ case "$machine_arch" in
123
+ x86_64|amd64) arch=amd64 ;;
124
+ arm64|aarch64) arch=arm64 ;;
125
+ *) die "unsupported architecture: $machine_arch. Etherscan CLI supports amd64 and arm64." ;;
126
+ esac
127
+
128
+ archive_name="etherscan_${release_version}_${os}_${arch}.tar.gz"
129
+ if [ -z "$download_base" ]; then
130
+ download_base="https://github.com/$repository/releases/download/$tag"
131
+ fi
132
+
133
+ temp_dir=$(mktemp -d 2>/dev/null || mktemp -d -t etherscan-install)
134
+ trap 'rm -rf "$temp_dir"' EXIT HUP INT TERM
135
+ archive_path="$temp_dir/$archive_name"
136
+ checksum_path="$temp_dir/checksums.txt"
137
+ source_executable="$temp_dir/etherscan"
138
+
139
+ printf 'Downloading Etherscan CLI %s for %s/%s...\n' "$release_version" "$os" "$arch"
140
+ fetch_file "$download_base" "$archive_name" "$archive_path"
141
+ fetch_file "$download_base" checksums.txt "$checksum_path"
142
+
143
+ expected_hash=$(awk -v name="$archive_name" '$2 == name || $2 == ("*" name) { print tolower($1); exit }' "$checksum_path")
144
+ [ -n "$expected_hash" ] || die "no checksum was published for $archive_name"
145
+ printf '%s\n' "$expected_hash" | grep -Eq '^[0-9a-f]{64}$' || die "invalid checksum published for $archive_name"
146
+
147
+ if command -v sha256sum >/dev/null 2>&1; then
148
+ actual_hash=$(sha256sum "$archive_path" | awk '{ print tolower($1) }')
149
+ elif command -v shasum >/dev/null 2>&1; then
150
+ actual_hash=$(shasum -a 256 "$archive_path" | awk '{ print tolower($1) }')
151
+ else
152
+ die "sha256sum or shasum is required to verify the download"
153
+ fi
154
+
155
+ [ "$actual_hash" = "$expected_hash" ] || die "checksum verification failed for $archive_name"
156
+
157
+ entry_count=$(tar -tzf "$archive_path" | awk '$0 == "etherscan" { count++ } END { print count + 0 }')
158
+ [ "$entry_count" -eq 1 ] || die "$archive_name must contain exactly one root-level etherscan"
159
+ tar -xOzf "$archive_path" etherscan >"$source_executable"
160
+ [ -s "$source_executable" ] || die "$archive_name contains an empty etherscan executable"
161
+
162
+ mkdir -p "$install_dir"
163
+ staged_executable="$install_dir/.etherscan.new.$$"
164
+ cp "$source_executable" "$staged_executable"
165
+ chmod 0755 "$staged_executable"
166
+ mv -f "$staged_executable" "$install_dir/etherscan"
167
+
168
+ path_updated=0
169
+ if [ "$update_path" -eq 1 ]; then
170
+ case ":$PATH:" in
171
+ *:"$install_dir":*) ;;
172
+ *)
173
+ shell_name=${SHELL:-sh}
174
+ shell_name=${shell_name##*/}
175
+ escaped_install_dir=$(printf '%s' "$install_dir" | sed 's/[\\"$`]/\\&/g')
176
+ if [ "$shell_name" = fish ]; then
177
+ profile="$HOME/.config/fish/config.fish"
178
+ mkdir -p "$(dirname "$profile")"
179
+ path_line="fish_add_path \"$escaped_install_dir\""
180
+ else
181
+ case "$shell_name" in
182
+ zsh) profile="$HOME/.zshrc" ;;
183
+ bash) profile="$HOME/.bashrc" ;;
184
+ *) profile="$HOME/.profile" ;;
185
+ esac
186
+ path_line="export PATH=\"$escaped_install_dir:\$PATH\""
187
+ fi
188
+
189
+ # Match the exact line we would write (whole-line, fixed-string) so an
190
+ # unrelated profile line that merely contains the path does not suppress
191
+ # the update, and a genuine duplicate is not appended.
192
+ if ! [ -f "$profile" ] || ! grep -Fx -e "$path_line" "$profile" >/dev/null 2>&1; then
193
+ {
194
+ printf '\n# Etherscan CLI\n'
195
+ printf '%s\n' "$path_line"
196
+ } >>"$profile"
197
+ path_updated=1
198
+ fi
199
+ ;;
200
+ esac
201
+ fi
202
+
203
+ printf '\nEtherscan CLI %s installed successfully.\n' "$release_version"
204
+ printf 'Installed to: %s\n' "$install_dir/etherscan"
205
+ if [ "$update_path" -eq 0 ]; then
206
+ printf 'Add %s to PATH to run etherscan from any directory.\n' "$install_dir"
207
+ elif [ "$path_updated" -eq 1 ]; then
208
+ printf 'Open a new terminal, then run: etherscan version\n'
209
+ else
210
+ printf 'Run: etherscan version\n'
211
+ fi