ethcli 0.21.2

Comprehensive Ethereum CLI for logs, transactions, accounts, and contracts
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
# ethcli Implementation Plan

> Expanding eth-log-fetcher into a comprehensive Ethereum CLI tool

## Overview

Rename and expand `eth-log-fetcher` to `ethcli` - a unified CLI for Ethereum data fetching, transaction analysis, and blockchain exploration. Integrates `foundry-block-explorers` for Etherscan API access while retaining custom RPC pool and log fetching capabilities.

## Current State

```
eth-log-fetcher v0.3.2
├── Log fetching (parallel RPC, checkpoints, streaming)
├── Transaction analysis (tx command)
├── RPC endpoint management
├── Signature cache + 4byte.directory (internal only)
├── Multi-chain support (7 chains)
└── Output: JSON, CSV, SQLite
```

## Target State

```
ethcli v0.4.0
├── logs      - Fetch historical logs (existing)
├── tx        - Analyze transactions (existing)
├── account   - Balance, transactions, token transfers (NEW)
├── contract  - ABI, source, creation, verify (NEW)
├── token     - Info, holders, balances (NEW)
├── gas       - Oracle, estimates (NEW)
├── sig       - Signature lookup (expose existing internal code)
├── endpoints - RPC management (existing)
└── config    - Configuration (existing)
```

---

## Phase 1: Project Rename & Restructure

### 1.1 Package Rename

**Cargo.toml changes:**
```toml
[package]
name = "ethcli"
version = "0.4.0"
description = "Comprehensive Ethereum CLI for logs, transactions, accounts, and contracts"
keywords = ["ethereum", "cli", "etherscan", "blockchain", "web3"]

[[bin]]
name = "ethcli"
path = "src/main.rs"

[lib]
name = "ethcli"
path = "src/lib.rs"
```

**New dependency:**
```toml
foundry-block-explorers = "0.9"
```

### 1.2 Directory Restructure

```
src/
├── main.rs                    # CLI entry point
├── lib.rs                     # Library exports
│
├── cli/                       # NEW: CLI command modules
│   ├── mod.rs
│   ├── logs.rs               # logs subcommand (extract from main.rs)
│   ├── tx.rs                 # tx subcommand (extract from main.rs)
│   ├── account.rs            # NEW
│   ├── contract.rs           # NEW
│   ├── token.rs              # NEW
│   ├── gas.rs                # NEW
│   ├── sig.rs                # NEW (exposes existing functionality)
│   ├── endpoints.rs          # extract from main.rs
│   └── config.rs             # extract from main.rs
│
├── etherscan/                 # NEW: Etherscan client wrapper
│   ├── mod.rs
│   ├── client.rs             # Extended client wrapping foundry-block-explorers
│   └── cache.rs              # Move from src/cache.rs
│
├── rpc/                       # Keep existing
├── abi/                       # Keep existing (simplified, delegate to etherscan/)
├── tx/                        # Keep existing
├── output/                    # Keep existing
├── config/                    # Keep existing
├── fetcher.rs                 # Keep existing
├── checkpoint.rs              # Keep existing
├── proxy.rs                   # Keep existing
└── error.rs                   # Extend with new error types
```

---

## Phase 2: Integrate foundry-block-explorers

### 2.1 Create Extended Client

**src/etherscan/client.rs:**
```rust
use foundry_block_explorers::Client as EtherscanClient;
use crate::etherscan::cache::SignatureCache;
use std::sync::Arc;

/// Extended Etherscan client with signature caching and 4byte lookups
pub struct Client {
    inner: EtherscanClient,
    sig_cache: Arc<SignatureCache>,
    http: reqwest::Client,
    chain: Chain,
}

impl Client {
    pub fn new(chain: Chain, api_key: Option<&str>) -> Result<Self> {
        let inner = EtherscanClient::builder()
            .chain(chain.into())
            .with_api_key(api_key.unwrap_or_default())
            .build()?;

        Ok(Self {
            inner,
            sig_cache: Arc::new(SignatureCache::new()),
            http: reqwest::Client::new(),
            chain,
        })
    }

    // ========== Delegated methods (from foundry-block-explorers) ==========

    // Contract
    pub async fn contract_abi(&self, addr: Address) -> Result<JsonAbi>;
    pub async fn contract_source_code(&self, addr: Address) -> Result<ContractMetadata>;
    pub async fn contract_creation_data(&self, addr: Address) -> Result<ContractCreationData>;

    // Account
    pub async fn get_ether_balance_single(&self, addr: Address) -> Result<U256>;
    pub async fn get_ether_balance_multi(&self, addrs: &[Address]) -> Result<Vec<AccountBalance>>;
    pub async fn get_transactions(&self, addr: Address, params: TxListParams) -> Result<Vec<NormalTransaction>>;
    pub async fn get_internal_transactions(&self, params: InternalTxParams) -> Result<Vec<InternalTransaction>>;
    pub async fn get_erc20_token_transfer_events(&self, params: TokenQueryOption) -> Result<Vec<Erc20TokenTransfer>>;
    pub async fn get_erc721_token_transfer_events(&self, params: TokenQueryOption) -> Result<Vec<Erc721TokenTransfer>>;
    pub async fn get_erc1155_token_transfer_events(&self, params: TokenQueryOption) -> Result<Vec<Erc1155TokenTransfer>>;
    pub async fn get_mined_blocks(&self, addr: Address, page: u64, offset: u64) -> Result<Vec<MinedBlock>>;

    // Gas
    pub async fn gas_oracle(&self) -> Result<GasOracle>;
    pub async fn gas_estimate(&self, gas_price: u64) -> Result<u64>;

    // ========== Our extensions (unique value-add) ==========

    /// Lookup function selector - checks cache, then 4byte.directory
    pub async fn lookup_selector(&self, selector: &str) -> Option<String> {
        let normalized = normalize_selector(selector);

        if let Some(sig) = self.sig_cache.get_function(&normalized) {
            return Some(sig);
        }

        let sig = self.fetch_4byte_function(&normalized).await?;
        self.sig_cache.set_function(&normalized, &sig);
        Some(sig)
    }

    /// Lookup event topic - checks cache, then 4byte.directory
    pub async fn lookup_event(&self, topic0: &str) -> Option<String> {
        let normalized = normalize_topic(topic0);

        if let Some(sig) = self.sig_cache.get_event(&normalized) {
            return Some(sig);
        }

        let sig = self.fetch_4byte_event(&normalized).await?;
        self.sig_cache.set_event(&normalized, &sig);
        Some(sig)
    }

    /// Get token metadata via eth_call (name, symbol, decimals)
    pub async fn get_token_metadata(&self, addr: &str) -> Result<TokenMetadata>;

    /// Get cache statistics
    pub fn cache_stats(&self) -> CacheStats {
        self.sig_cache.stats()
    }
}

impl std::ops::Deref for Client {
    type Target = EtherscanClient;
    fn deref(&self) -> &Self::Target { &self.inner }
}
```

### 2.2 Chain Compatibility

**src/config/chain.rs (add conversion):**
```rust
impl From<Chain> for alloy_chains::Chain {
    fn from(chain: Chain) -> Self {
        alloy_chains::Chain::from_id(chain.chain_id())
    }
}
```

---

## Phase 3: CLI Commands

### 3.1 Command Structure

**src/main.rs:**
```rust
#[derive(Parser)]
#[command(name = "ethcli")]
#[command(version, about = "Comprehensive Ethereum CLI")]
struct Cli {
    #[command(subcommand)]
    command: Commands,

    #[arg(long, default_value = "ethereum", global = true)]
    chain: String,

    #[arg(long, env = "ETHERSCAN_API_KEY", global = true)]
    etherscan_key: Option<String>,

    #[arg(short, long, action = clap::ArgAction::Count, global = true)]
    verbose: u8,

    #[arg(short, long, global = true)]
    quiet: bool,
}

#[derive(Subcommand)]
enum Commands {
    /// Fetch historical logs from contracts
    Logs { /* existing args */ },

    /// Analyze transaction(s)
    Tx { /* existing args */ },

    /// Account operations
    Account {
        #[command(subcommand)]
        action: AccountCommands,
    },

    /// Contract operations
    Contract {
        #[command(subcommand)]
        action: ContractCommands,
    },

    /// Token operations
    Token {
        #[command(subcommand)]
        action: TokenCommands,
    },

    /// Gas oracle and estimates
    Gas {
        #[command(subcommand)]
        action: GasCommands,
    },

    /// Signature lookup
    Sig {
        #[command(subcommand)]
        action: SigCommands,
    },

    /// RPC endpoint management
    Endpoints { /* existing */ },

    /// Configuration
    Config { /* existing */ },
}
```

### 3.2 Account Commands

```bash
ethcli account balance 0x123...                    # Single address
ethcli account balance 0x123... 0x456... 0x789...  # Multiple addresses
ethcli account txs 0x123... --page 1 --limit 50   # Transaction history
ethcli account internal-txs 0x123...               # Internal transactions
ethcli account erc20 0x123... --token 0xUSDC       # ERC20 transfers
ethcli account erc721 0x123...                     # NFT transfers
ethcli account erc1155 0x123...                    # ERC1155 transfers
ethcli account mined-blocks 0x123...               # Blocks validated
ethcli account funded-by 0x123...                  # First funder
```

### 3.3 Contract Commands

```bash
ethcli contract abi 0x123...                # Get ABI (JSON)
ethcli contract abi 0x123... -o abi.json    # Save to file
ethcli contract source 0x123...             # Get verified source
ethcli contract source 0x123... -o src/     # Save to directory
ethcli contract creation 0x123...           # Creator + tx hash
```

### 3.4 Token Commands

```bash
ethcli token info 0xUSDC           # Name, symbol, decimals, supply
ethcli token holders 0xUSDC        # Top 100 holders
ethcli token holders 0xUSDC --limit 500
ethcli token balance 0xUSDC --holder 0x123...
ethcli token supply 0xUSDC         # Current supply
ethcli token supply 0xUSDC --block 18000000  # Historical
```

### 3.5 Gas Commands

```bash
ethcli gas oracle
# Output:
# Gas Prices (Ethereum)
# ─────────────────────
# Safe:      15 gwei  (~5 min)
# Standard:  18 gwei  (~3 min)
# Fast:      25 gwei  (~30 sec)
# Base Fee:  14.2 gwei
# Priority:  1-3 gwei

ethcli gas estimate 20
# Estimated confirmation: ~45 seconds
```

### 3.6 Signature Commands

```bash
ethcli sig fn 0xa9059cbb
# transfer(address,uint256)

ethcli sig event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
# Transfer(address,address,uint256)

ethcli sig cache-stats
# Signature Cache
# ───────────────
# Functions: 1,234 cached (1,180 valid)
# Events:    567 cached (542 valid)
# Path:      ~/.cache/ethcli/signatures.json

ethcli sig cache-clear
# Cache cleared
```

---

## Phase 4: Output Formatting

All commands support consistent output options:

```bash
--output pretty   # Human-readable (default)
--output json     # JSON object
--output ndjson   # Newline-delimited JSON (streaming)
--output csv      # CSV (where applicable)
```

Example:
```bash
ethcli account balance 0x123... 0x456... -o json
```
```json
[
  {"address": "0x123...", "balance": "1234567890000000000", "balance_eth": "1.23456789"},
  {"address": "0x456...", "balance": "9876543210000000000", "balance_eth": "9.87654321"}
]
```

---

## Phase 5: Implementation Order

### Sprint 1: Foundation
- [ ] Rename package to `ethcli` in Cargo.toml
- [ ] Add `foundry-block-explorers` dependency
- [ ] Create `src/etherscan/mod.rs` and `client.rs`
- [ ] Move `cache.rs` to `src/etherscan/cache.rs`
- [ ] Create `src/cli/` directory structure
- [ ] Extract existing commands to cli modules
- [ ] Verify all existing functionality works

### Sprint 2: Account & Contract Commands
- [ ] Implement `ethcli account balance`
- [ ] Implement `ethcli account txs`
- [ ] Implement `ethcli account erc20/721/1155`
- [ ] Implement `ethcli contract abi`
- [ ] Implement `ethcli contract source`
- [ ] Implement `ethcli contract creation`

### Sprint 3: Token & Gas Commands
- [ ] Implement `ethcli token info`
- [ ] Implement `ethcli token holders`
- [ ] Implement `ethcli token balance/supply`
- [ ] Implement `ethcli gas oracle`
- [ ] Implement `ethcli gas estimate`

### Sprint 4: Sig & Polish
- [ ] Implement `ethcli sig fn`
- [ ] Implement `ethcli sig event`
- [ ] Implement `ethcli sig cache-stats/cache-clear`
- [ ] Add pretty output formatting
- [ ] Update README and docs
- [ ] Add integration tests

---

## Migration & Compatibility

### Backward Compatibility

Option 1: Shell alias
```bash
alias eth-log-fetch='ethcli logs'
```

Option 2: Wrapper binary (src/bin/eth-log-fetch.rs)
```rust
fn main() {
    let args: Vec<String> = std::env::args().collect();
    let mut new_args = vec!["ethcli".to_string(), "logs".to_string()];
    new_args.extend(args.into_iter().skip(1));
    // exec ethcli with new args
}
```

### Config Migration

On first run, auto-migrate:
- `~/.config/eth-log-fetcher/``~/.config/ethcli/`
- `~/.cache/eth-log-fetch/``~/.cache/ethcli/`

---

## Why foundry-block-explorers?

| Aspect | Roll Our Own | Use foundry-block-explorers |
|--------|--------------|----------------------------|
| Maintenance | We maintain all Etherscan API bindings | Foundry team maintains |
| API changes | We fix | They fix |
| Type safety | Build from scratch | Already done |
| Testing | Need comprehensive tests | Already tested |
| Ecosystem | Standalone | Part of alloy/foundry ecosystem |
| Our value-add | Duplicated effort | Focus on sig cache + 4byte |

**Conclusion:** Use foundry-block-explorers for Etherscan API, keep our unique additions (signature cache, 4byte.directory, RPC pool, log fetching).

---

## File Sizes (Estimated)

| File | Lines | Notes |
|------|-------|-------|
| src/cli/account.rs | ~250 | 8 subcommands |
| src/cli/contract.rs | ~150 | 3 subcommands |
| src/cli/token.rs | ~200 | 4 subcommands |
| src/cli/gas.rs | ~80 | 2 subcommands |
| src/cli/sig.rs | ~100 | 4 subcommands |
| src/etherscan/client.rs | ~300 | Wrapper + extensions |
| **Total new code** | ~1,100 | Plus refactored existing |

---

## Success Criteria

- [ ] All existing `eth-log-fetch` functionality preserved
- [ ] `ethcli logs` works identically to old `eth-log-fetch`
- [ ] All new commands have `--help` documentation
- [ ] JSON output works for all commands
- [ ] Tests pass on Linux, macOS, Windows
- [ ] Binary size increase < 3MB
- [ ] No performance regression in log fetching