evmify 0.2.0

EVM-ify any chain: a generic JSON-RPC client for EVM chains, plus a modular adapter -> normalizer -> transformer pipeline that serves non-EVM chains (Solana, Bitcoin, ...) as EVM blocks, transactions, receipts and logs.
Documentation
# Contributing

Thanks for helping! Issues and PRs are welcome.

## Before you open a PR

```sh
cargo fmt --all
cargo clippy --all-targets -- -D warnings
cargo clippy --all-targets --no-default-features -- -D warnings
cargo test
```

CI runs the same commands.

## Adding a non-EVM chain

Use `src/adapters/solana.rs` or `src/adapters/bitcoin.rs` as a template. A new chain is one file with four parts:

1. **Raw types** — `#[derive(Deserialize)]` structs for the node's responses. Model only the fields you use; serde ignores the rest, which keeps you robust to API additions.
2. **Adapter** — implement `pipeline::Adapter`: `chain()`, `head()`, `block(height)`, `transaction(native_id)`, and optionally `finalized_head()`. Return `Ok(None)` (not an error) for heights/ids that do not exist; map the node's "not found" error codes to `None`.
3. **Normalizer** — implement `pipeline::Normalizer`: raw → `NormalizedBlock` / `NormalizedTx`. Keep it pure (no I/O). Rules of thumb:
   - amounts in the chain's base unit, as `u128`
   - identifiers stay native strings; the transformer maps them
   - use `NormalizedEvent::transfer` / `balance_change` so the output looks like ERC-20 logs
   - put anything else useful in `extra` (it ends up in `_native.extra`)
4. **`pipeline()` helper** — wire adapter + normalizer + `EvmTransformer` with the right `IdMapper` (`DefaultMapper::new(true)` if native ids are 32-byte hex, otherwise wrap in `IndexedMapper`).

Then add the chain to `KNOWN_CHAINS` in `src/chains.rs` (with its public RPC if it has one), a `connect()` helper next to `pipeline()`, register the module in `src/adapters/mod.rs`, and write tests in `tests/<chain>.rs` using `MockTransport` with realistic JSON fixtures (copy real responses and trim them). Document the field mapping in the module docs and the README table.

## Adding an EVM chain

Nothing to do for it to *work*: `EvmClient` talks to any EVM endpoint by URL. To give a chain a shorthand key, a name and a default RPC, add one line to `KNOWN_CHAINS` in `src/chains.rs`:

```rust
//  chain id  key      display name  symbol  public RPC (or None)
evm(2020,    "ronin", "Ronin",      "RON",  Some("https://api.roninchain.com/rpc")),
```

- **Chain id.** The chain's **EIP-155 chain ID**: a global identifier, not something local to this crate. It is the value the node returns for `eth_chainId`, which is how `EvmClient` recognizes the chain and checks that a key points at the right network. Look it up on [chainlist.org]https://chainlist.org (backed by [ethereum-lists/chains]https://github.com/ethereum-lists/chains) and double-check it against the chain's own docs.
- **Key.** Short and lowercase; test networks get a suffix (`-sepolia`, `-testnet`, or the testnet's name, like `ronin-saigon`). Users can override its RPC with `EVMIFY_RPC_<KEY>`.
- **Public RPC.** Only add an endpoint **the chain's own documentation publishes**, and link that page in your PR description. Third-party lists go stale quickly (Polygon's long-time `polygon-rpc.com`, for example, is no longer its official endpoint). If you can't find an official one, use `None`: the chain still works with a URL or `EVMIFY_RPC_<KEY>`.

`cargo test` checks that keys and chain ids are unique and URLs are `https://`.