Metadata-Version: 2.4
Name: evmsrc
Version: 0.4.0
Summary: Fetch verified source code for EVM smart contracts.
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: 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

```
███████╗██╗   ██╗███╗   ███╗███████╗██████╗  ██████╗
██╔════╝██║   ██║████╗ ████║██╔════╝██╔══██╗██╔════╝
█████╗  ██║   ██║██╔████╔██║███████╗██████╔╝██║
██╔══╝  ╚██╗ ██╔╝██║╚██╔╝██║╚════██║██╔══██╗██║
███████╗ ╚████╔╝ ██║ ╚═╝ ██║███████║██║  ██║╚██████╗
╚══════╝  ╚═══╝  ╚═╝     ╚═╝╚══════╝╚═╝  ╚═╝ ╚═════╝
```

Fetch verified source code for EVM smart contracts.

Ships with 7 EVM chains via Etherscan v2: Ethereum, BSC, Polygon, Arbitrum,
Optimism, Base, and Avalanche. Sourcify acts as a keyless fallback provider.

## Installation

Requires Python 3.11+.

```bash
pip install evmsrc
```

Or, for an isolated install of just the CLI:

```bash
pipx install evmsrc
```

## API Key

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 token like DAI:

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

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

```bash
# Proxy only by 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.
This requires `ETHERSCAN_API_KEY` and 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` means byte-equal, `partial`
means equal except the trailing metadata hash, which is the common case, and
`mismatch` means something differs and is worth investigating.

Fetch a token on another chain. This example uses BUSD on BSC:

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

Attempt to fetch an unverified address. This exits with status 1:

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

Fetch many contracts at once from a file with one address per line. Blank
lines and `#` comments are ignored. 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, the per-contract
role of standalone, proxy, or 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 such as `venv/`, `env/`, conda, or 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`. **Never** bypass with `--no-verify` unless explicitly approved by the author.

Integration tests use `pytest-vcr` at <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 with no key required.

## Known Limitations

- **Proxy pattern detection is name-based.** The pattern field reads `Unknown`
  for proxies whose contract name does not match the `transparent` / `fiattoken` /
  `uups` / `beacon` heuristic. That includes `ERC1967Proxy`, whose name is
  ambiguous between Transparent and UUPS variants. Unmatched proxies are still
  flagged as proxies with the 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, which is 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, and 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. That is a useful audit signal, not an
  `evmsrc` bug.
