# interevm

Async EVM/Web3 library for Python — connection pooling, request batching, ABI encoding/decoding, and Multicall3 support.

## Installation

```bash
pip install interevm
```

**Requirements:** Python 3.11+, no web3.py dependency.

---

## Quick start

### RPCClient

```python
import asyncio
from interevm import RPCClient

async def main():
    async with RPCClient("https://eth.llamarpc.com") as client:
        block  = await client.get_block_number()
        bal    = await client.get_balance("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")
        fees   = await client.get_eip1559_fees()
        print(block, bal, fees)

asyncio.run(main())
```

Pass a full `RPCConfig` to tune connection pool size, timeouts, retries, and TTL cache:

```python
from interevm import RPCClient, RPCConfig

config = RPCConfig(
    rpc_url="https://eth.llamarpc.com",
    max_connections=50,
    max_retries=3,
    cache_ttl=60,
)
client = RPCClient(config)
```

---

### Contract — encode / decode

```python
from interevm import Contract

ERC20_ABI = [...]  # standard ERC-20 ABI list

token = Contract("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", ERC20_ABI)

# Build calldata manually
calldata = token.encode_function_data("balanceOf", "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")

# Decode a raw eth_call result
result = token.decode_function_result("balanceOf", "0x00000000000000000000000000000000000000000000000000000002540be400")
print(result)  # 10000000000

# Decode an event log
decoded = token.decode_log(receipt["logs"][0]["topics"], receipt["logs"][0]["data"])
print(decoded)  # {'from': '0x...', 'to': '0x...', 'value': 1000, '_event': 'Transfer'}
```

---

### Multicall

```python
import asyncio
from interevm import RPCClient, Contract, MulticallHelper

async def main():
    async with RPCClient("https://eth.llamarpc.com") as client:
        helper = MulticallHelper(client)

        usdc  = Contract("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", ERC20_ABI)
        weth  = Contract("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", ERC20_ABI)
        wallet = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"

        results = await helper.execute_multicall([
            (usdc.address, usdc, "balanceOf", (wallet,), "usdc_balance"),
            (weth.address, weth, "balanceOf", (wallet,), "weth_balance"),
        ])

        print(results["usdc_balance"])
        print(results["weth_balance"])

asyncio.run(main())
```

---

## Public API

### `interevm.RPCClient`

| Method | Description |
|--------|-------------|
| `get_chain_id()` | Chain ID (cached per instance) |
| `get_block_number()` | Latest block number |
| `get_balance(address, block)` | Balance in wei |
| `get_nonce(address, block)` | Transaction count |
| `get_bytecode(address, block)` | Contract bytecode |
| `get_gas_price()` | Gas price in wei |
| `call(to, data, ...)` | Raw eth_call |
| `estimate_gas(tx)` | Gas estimate |
| `send_raw_transaction(signed_tx)` | Broadcast signed tx |
| `get_block_by_number(n, full_txs)` | Block data |
| `batch_get_blocks(numbers)` | Batch block fetch |
| `get_transaction(hash)` | Transaction by hash |
| `get_transaction_receipt(hash)` | Receipt by hash |
| `batch_get_transactions(hashes)` | Batch tx fetch |
| `batch_get_receipts(hashes)` | Batch receipt fetch |
| `get_eip1559_fees()` | base_fee / max_priority_fee / max_fee |
| `get_params_for_eip1559_transaction(addr)` | nonce + fees in one call |
| `multicall(multicall_contract, calls)` | Multicall3.tryAggregate |
| `call_function(contract, name, *args)` | Typed contract call |
| `debug_trace_transaction(hash, ...)` | debug_traceTransaction |
| `close()` | Release all connections |

### `interevm.RPCConfig`

Dataclass with connection, timeout, retry, and cache settings.

### `interevm.RPCError`

Exception raised on JSON-RPC error responses. Carries `.code`, `.message`, `.data`.

### `interevm.CallRequest`

Dataclass representing one call in a batch (`method`, `params`, `id`).

### `interevm.Contract`

| Method | Description |
|--------|-------------|
| `encode_function_data(name, *args)` | Build hex calldata |
| `decode_function_result(name, data)` | Decode eth_call result |
| `decode_log(topics, data)` | Decode event log |
| `get_function(name)` → `ABIFunction` | ABI function descriptor |
| `get_event(name)` → `ABIEvent` | ABI event descriptor |
| `get_event_by_topic(topic0)` | Lookup event by topic0 |
| `has_function(name)` / `has_event(name)` | Membership check |
| `list_functions()` / `list_events()` | List names |

### `interevm.ABIFunction`

Frozen dataclass: `name`, `inputs`, `outputs`, `state_mutability`, `selector` (computed).
Methods: `encode_input(*args)`, `decode_output(data)`.

### `interevm.ABIEvent`

Frozen dataclass: `name`, `inputs`, `anonymous`, `topic0` (computed).
Methods: `decode_log(topics, data)`.

### `interevm.MulticallHelper`

`execute_multicall(calls, batch_size=100)` — batch eth_calls via Multicall3.

### Helper functions

| Function | Description |
|----------|-------------|
| `mkdata(sig, types, params)` | Build calldata from signature + params |
| `compute_function_selector(sig)` | 4-byte selector (LRU cached) |
| `compute_event_topic(sig)` | 32-byte topic0 hash (LRU cached) |
| `topic_from_address(addr)` | Address → 32-byte topic |
| `topic_from_uint(value)` | uint → 32-byte topic |
| `ensure_hex_prefix(value)` | Ensure `0x` prefix |
| `format_tx_dict(tx)` | Convert int fields to hex for JSON-RPC |
| `rpc_retry(coro, *args, retries, base_delay)` | Standalone retry wrapper |
