Metadata-Version: 2.4
Name: evmsrc
Version: 0.3.1
Summary: Fetch verified source code for EVM smart contracts
Project-URL: Homepage, https://github.com/4234288/evmsrc
Project-URL: Repository, https://github.com/4234288/evmsrc
Project-URL: Issues, https://github.com/4234288/evmsrc/issues
Author: 4234288
License-Expression: MIT
License-File: LICENSE
Keywords: blockchain,ethereum,etherscan,evm,smart-contracts,solidity,sourcify
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.11
Requires-Dist: click>=8.1
Requires-Dist: httpx>=0.27
Requires-Dist: py-solc-x>=2.0
Requires-Dist: pydantic>=2.0
Requires-Dist: rich>=13.0
Requires-Dist: tenacity>=8.2
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pre-commit>=3.7; extra == 'dev'
Requires-Dist: pytest-mock>=3.14; extra == 'dev'
Requires-Dist: pytest-vcr>=1.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Requires-Dist: vcrpy>=6.0; extra == 'dev'
Description-Content-Type: text/markdown

# evmsrc

Fetch verified source code for EVM smart contracts.

Ships with 7 EVM chains (Ethereum, BSC, Polygon, Arbitrum, Optimism,
Base, Avalanche) via Etherscan v2, plus Sourcify as a fallback provider.

## Installation

Requires Python 3.11+.

```bash
pip install evmsrc
```

Or, for an isolated install of just the CLI:

```bash
pipx install evmsrc
```

## Setup

For best coverage, get a free Etherscan API key at <https://etherscan.io/apis>
and export it:

```bash
export ETHERSCAN_API_KEY=your_key_here
```

Without a key, evmsrc falls back to Sourcify alone. Sourcify covers most
verified contracts but does not flag proxies (so the result is always
`standalone` and `--resolve-proxy` becomes a no-op).

## Usage

Fetch a simple ERC-20 (DAI):

```bash
evmsrc fetch \
  --chain ethereum \
  --address 0x6B175474E89094C44Da98b954EedeAC495271d0F \
  --output /tmp/dai
```

Fetch a proxy contract (USDC). By default only the proxy is fetched. Pass
`--resolve-proxy` to also pull the implementation contract:

```bash
# Proxy only (default): status=partial, warning about unresolved implementation
evmsrc fetch \
  --chain ethereum \
  --address 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 \
  --output /tmp/usdc

# Proxy and implementation: status=complete, two contract entries in manifest
evmsrc fetch \
  --chain ethereum \
  --address 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 \
  --output /tmp/usdc \
  --resolve-proxy
```

Verify that the fetched source actually compiles to the deployed bytecode
(requires `ETHERSCAN_API_KEY`; downloads the matching `solc` version on first
use):

```bash
evmsrc fetch \
  --chain ethereum \
  --address 0xC18360217D8F7Ab5e7c516566761Ea12Ce7F9D72 \
  --output /tmp/ens \
  --verify-bytecode
```

Output adds a `Match:` line per contract: `full` (byte-equal), `partial`
(equal except trailing metadata hash — the common case), or `mismatch`
(something differs — worth investigating).

Fetch a token on another chain (BUSD on BSC):

```bash
evmsrc fetch \
  --chain bsc \
  --address 0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56 \
  --output /tmp/busd
```

Attempt to fetch an unverified address; exits with status 1:

```bash
evmsrc fetch \
  --chain ethereum \
  --address 0x000000000000000000000000000000000000dEaD \
  --output /tmp/dead
```

Fetch many contracts at once from a file (one address per line, `#` comments
allowed). Default concurrency is 5 to stay within Etherscan's free-tier rate
limit:

```bash
cat > /tmp/tokens.txt <<EOF
# Top stablecoins on Ethereum
0x6B175474E89094C44Da98b954EedeAC495271d0F
0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
EOF

evmsrc fetch-batch \
  --chain ethereum \
  --addresses-file /tmp/tokens.txt \
  --output /tmp/batch \
  --concurrency 5
```

`fetch-batch` accepts the same `--resolve-proxy` and `--verify-bytecode`
flags as `fetch`; they apply to every address.

List supported chains:

```bash
evmsrc chains
# ethereum        chain_id=1
# bsc             chain_id=56
# polygon         chain_id=137
# arbitrum        chain_id=42161
# optimism        chain_id=10
# base            chain_id=8453
# avalanche       chain_id=43114
```

## Exit codes

| Status     | Exit code |
|------------|-----------|
| complete   | 0         |
| partial    | 0         |
| unverified | 1         |
| failed     | 1         |

## Output structure

Each successful `fetch` produces:

```
output_dir/
├── manifest.json
└── source/
    └── 0x6b1754.../
        └── Dai.sol
```

With `--resolve-proxy`, the implementation contract gets its own address
directory alongside the proxy:

```
output_dir/
├── manifest.json
└── source/
    ├── 0xa0b869.../       # proxy
    │   └── FiatTokenProxy.sol
    └── 0x435068.../       # implementation
        └── FiatToken.sol
```

`fetch-batch` writes per-address subdirs plus a top-level
`batch_manifest.json` with status counts:

```
output_dir/
├── batch_manifest.json
├── 0x6b1754.../
│   ├── manifest.json
│   └── source/
│       └── 0x6b1754.../
│           └── Dai.sol
└── 0xa0b869.../
    ├── manifest.json
    └── source/...
```

`manifest.json` records the chain, address, fetch status, per-contract role
(standalone / proxy / implementation), compiler info, source file hashes,
and any warnings or errors. Source files are written under
`source/<address>/<rel_path>`, preserving the provider's path exactly.

## Development

To work on evmsrc from source:

```bash
git clone https://github.com/4234288/evmsrc
cd evmsrc
python -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/pre-commit install
```

> The pre-commit hooks reference `.venv/bin/python` directly. If you use a different
> virtualenv path (e.g. `venv/`, `env/`, conda, poetry), adjust the `entry:` lines
> in `.pre-commit-config.yaml` accordingly.

Run checks manually:

```bash
pre-commit run --all-files           # ruff + mypy + pytest unit
pytest tests/integration/            # vcr integration (auto-skipped without cassettes)
```

Hooks run on every `git commit`. Bypass with `--no-verify` (not recommended).

Integration tests use [`pytest-vcr`](https://pytest-vcr.readthedocs.io/) to
replay recorded Etherscan responses. They are auto-skipped when neither a
recorded cassette nor `ETHERSCAN_API_KEY` is available.

To record fresh cassettes:

```bash
export ETHERSCAN_API_KEY=your_real_key
rm -f tests/fixtures/cassettes/*.yaml   # clear any stale cassettes
pytest tests/integration/                # records on first run
```

The YAML files land under `tests/fixtures/cassettes/` and should be committed.
Subsequent runs replay from the cassettes (no key required).

## Known limitations

- **Proxy pattern detection is name-based.** The pattern field reads `Unknown`
  for proxies whose contract name doesn't match the `transparent` / `fiattoken` /
  `uups` / `beacon` heuristic — including `ERC1967Proxy`, whose name is
  ambiguous between Transparent and UUPS variants. Unmatched proxies are still
  flagged as proxies with implementation address recorded; only the pattern
  label is missing.
- **Proxy recursion is single-level.** `--resolve-proxy` fetches the
  implementation contract one level down. If the implementation is itself a
  proxy (rare), its own implementation is not chased.
- **Sourcify does not detect proxies.** Contracts fetched via the Sourcify
  fallback are recorded as `standalone` even if they're actually proxies.
  Proxy detection runs only when Etherscan returns the contract.
- **Bytecode verification covers Standard JSON Input only.** Contracts
  verified on Etherscan as single-file Solidity or multi-file JSON skip
  verification (a `VERIFY_UNSUPPORTED_FORMAT` warning is recorded). Most
  modern contracts use Standard JSON Input.
- **Etherscan's SJI reconstruction can be lossy.** When Etherscan rebuilds
  the Standard JSON Input it returns, the output isn't always byte-equal
  to what the original deployer compiled. This produces a `mismatch` even
  for genuinely-verified contracts — useful audit signal, not an evmsrc bug.
