Skip to main content

eth-contract

EVM contract abstraction and ABI utilities for Python.

The core design principle: decouple contract definitions from Web3 instances. Build calldata as pure functions, compose contract calls freely, and only bind to a provider at the moment of execution. This makes it easy to work with multiple chains/providers and to compose calls with multicall or other routers.

Installation

pip install eth-contract

Patterns

1. Pythonic ABI Definitions

Stop writing unreadable JSON ABI files or relying on the Solidity compiler to get an ABI. Define your contract interface directly in Python.

Human-Readable ABIs

Pass Solidity-style signature strings to Contract.from_abi. The library parses them into a full JSON ABI at runtime:

from eth_contract import Contract

ERC20 = Contract.from_abi([
    "function transfer(address to, uint256 amount) returns (bool)",
    "function balanceOf(address owner) view returns (uint256)",
    "function approve(address spender, uint256 amount) returns (bool)",
    "event Transfer(address indexed from, address indexed to, uint256 amount)",
    "event Approval(address indexed owner, address indexed spender, uint256 amount)",
])

Structs are supported too — define them inline and reference them from functions and events:

Router = Contract.from_abi([
    """struct ExactInputSingleParams {
      address tokenIn;
      address tokenOut;
      uint24 fee;
      address recipient;
      uint256 amountIn;
      uint256 amountOutMinimum;
      uint160 sqrtPriceLimitX96;
    }""",
    "function exactInputSingle(ExactInputSingleParams params) payable returns (uint256 amountOut)",
])

Type-Annotated ABI Structs

For richer Python integration, define structs as typed Python classes using ABIStruct. The class behaves like a NamedTuple and provides encode() / decode() / human_readable_abi() for free.

Supported annotation forms:

Python annotation Solidity ABI type
Annotated[T, 'solidity_type'] explicit type (always works)
bool bool
int uint256
str string
bytes bytes
list[bool|int|str|bytes] bool[] / uint256[] / …
SomeStruct (ABIStruct subclass) tuple (nested struct)
list[SomeStruct] tuple[] (dynamic array of structs)
Annotated[list[SomeStruct], 'SomeStruct[N]'] tuple[N] (fixed-size array of structs)
from typing import Annotated
from eth_contract import ABIStruct, Contract

class SwapParams(ABIStruct):
    token_in:  Annotated[str, 'address']
    token_out: Annotated[str, 'address']
    fee:       Annotated[int, 'uint24']   # explicit when default doesn't fit
    recipient: Annotated[str, 'address']
    amount_in: int                         # default: int → uint256
    amount_out_minimum: int

# Generate the human-readable ABI fragment automatically
print(SwapParams.human_readable_abi())
# ['struct SwapParams { address token_in; address token_out; uint24 fee; ... }']

# Build the contract using the generated struct definition
Router = Contract.from_abi(
    SwapParams.human_readable_abi() + [
        "function exactInputSingle(SwapParams params) payable returns (uint256 amountOut)",
    ]
)

ABIStruct supports nesting — use another ABIStruct subclass directly as a field type, or wrap it in list[...] for arrays of structs:

class Inner(ABIStruct):
    x: bool               # default mapping: bool → bool
    y: Annotated[bytes, 'bytes32']

class Outer(ABIStruct):
    value: int            # default mapping: int → uint256
    inner: Inner          # single nested struct
    inners: list[Inner]   # dynamic array of structs  → tuple[]
    static_inners: Annotated[list[Inner], 'Inner[3]']  # fixed-size → tuple[3]

tx = Outer(
    value=42,
    inner=Inner(x=True, y=b'\x01' * 32),
    inners=(Inner(x=False, y=b'\x02' * 32),),
    static_inners=(Inner(x=True, y=b'\x03' * 32),) * 3,
)
decoded = Outer.decode(tx.encode())
assert decoded == tx

print(Outer.human_readable_abi())
# ['struct Inner { bool x; bytes32 y; }',
#  'struct Outer { uint256 value; Inner inner; Inner[] inners; Inner[3] static_inners; }']

2. Web3-Agnostic Calldata Building

Building calldata is a pure function — no Web3 instance required. Bind an address or transaction parameters to a contract with contract(to=..., ...), then call functions to produce encoded calldata. The actual Web3 provider is only provided at the point of execution (.call() or .transact()).

from eth_contract import Contract

ERC20 = Contract.from_abi([
    "function transfer(address to, uint256 amount) returns (bool)",
    "function balanceOf(address owner) view returns (uint256)",
])

token = ERC20(to="0xTokenAddress...")

# Build calldata without any Web3 instance
calldata = token.fns.transfer("0xRecipient...", 10**18).data
# HexBytes('0xa9059cbb...')

# Execute only when you have a provider
balance = await token.fns.balanceOf("0xUser...").call(w3)
receipt = await token.fns.transfer("0xRecipient...", 10**18).transact(w3, account)

Because calldata building is decoupled from execution, the same ContractFunction object can be passed to multicall or any other batching mechanism:

from eth_contract.multicall3 import multicall
from eth_contract import ERC20

USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
users = ["0xUser1...", "0xUser2..."]

# Build all calls without a provider
calls = [(USDC, ERC20.fns.balanceOf(user)) for user in users]
calls += [(WETH, ERC20.fns.balanceOf(user)) for user in users]

# Execute all calls in a single RPC round-trip
results = await multicall(w3, calls)

Contracts can be rebound to different addresses or parameters on the fly:

# Base contract definition (no address bound)
ERC20 = Contract.from_abi(["function balanceOf(address) view returns (uint256)"])

# Bind to a specific token address
usdc = ERC20(to="0xUSDC...")
weth = ERC20(to="0xWETH...")

# Both share the same ABI, different addresses
usdc_balance = await usdc.fns.balanceOf(user).call(w3)
weth_balance = await weth.fns.balanceOf(user).call(w3)

3. Built-In Utility ABIs

No need to copy-paste ABIs for common contracts. eth-contract ships ready-to-use instances:

from eth_contract import ERC20
from eth_contract.multicall3 import MULTICALL3, multicall
from eth_contract.weth import WETH
from eth_contract.entrypoint import ENTRYPOINT07, ENTRYPOINT08

ERC20

from eth_contract import ERC20

token = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"  # USDC

balance = await ERC20.fns.balanceOf(user).call(w3, to=token)
receipt = await ERC20.fns.transfer(recipient, amount).transact(w3, account, to=token)

Multicall3

Batch many read calls into one RPC request:

from eth_contract import ERC20
from eth_contract.multicall3 import multicall

tokens = ["0xUSDC...", "0xWETH...", "0xDAI..."]
calls = [(token, ERC20.fns.balanceOf(user)) for token in tokens]
balances = await multicall(w3, calls)
# [usdc_balance, weth_balance, dai_balance]

WETH

from eth_contract.weth import WETH

weth_address = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
receipt = await WETH.fns.deposit().transact(w3, account, to=weth_address, value=10**18)

Deterministic Deployment (CREATE2 / CREATE3)

from eth_contract.create2 import create2_deploy, create2_address
from eth_contract.create3 import create3_deploy, create3_address
from eth_contract.utils import get_initcode

initcode = get_initcode(artifact)              # from a compiled artifact dict
salt = 0

address = create2_address(initcode, salt=salt) # compute address before deploying
address = await create2_deploy(w3, account, initcode, salt=salt)

CLI tools for deployment:

python -m eth_contract.create2 artifact.json [ctor_args...] --salt 0 --rpc-url http://...
python -m eth_contract.create3 artifact.json [ctor_args...] --salt 0 --rpc-url http://...
python -m eth_contract.contract abi.json      # list all signatures in an ABI file

Utility Helpers

from eth_contract.utils import send_transaction, send_transactions, transfer, balance_of

# Single transaction
receipt = await send_transaction(w3, account, to=addr, data=calldata)

# Batch with automatic nonce management
receipts = await send_transactions(w3, [tx1, tx2, tx3], account=account)

# ERC20 or native transfer (pass ZERO_ADDRESS for native ETH)
await transfer(w3, token_address, sender, receiver, amount)
await transfer(w3, ZERO_ADDRESS, sender, receiver, amount)  # native ETH

# Balance query
bal = await balance_of(w3, token_address, address)
bal = await balance_of(w3, ZERO_ADDRESS, address)  # native ETH

Built-In Contracts Summary

Contract Import
ERC20 from eth_contract import ERC20
Multicall3 from eth_contract.multicall3 import MULTICALL3, multicall
WETH from eth_contract.weth import WETH
ERC-4337 EntryPoint v0.7 from eth_contract.entrypoint import ENTRYPOINT07
ERC-4337 EntryPoint v0.8 from eth_contract.entrypoint import ENTRYPOINT08
CREATE2 factory from eth_contract.create2 import create2_deploy
CreateX (CREATE3) from eth_contract.create3 import create3_deploy

Please open an issue if you want to see more ABIs included.


Project Setup

  1. Install nix
  2. Install direnv and nix-direnv
  3. uv sync --frozen to install all dependencies. (Without --frozen, uv version after v0.6.15 modifies the uv.lock)
  4. pytest to run all tests

If you are able to run all tests, you are ready to go!

Metadata

Release files for eth-contract 0.4.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for eth-contract 0.4.1
File Size Uploaded Provenance
eth_contract-0.4.1.tar.gz 129.0 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for eth-contract 0.4.1
File Interpreter ABI Platform
eth_contract-0.4.1-py3-none-any.whl Python 3 none any Details

Total release size: 237.5 kB

Release files / eth_contract-0.4.1.tar.gz

Download URL eth_contract-0.4.1.tar.gz
Size 129.0 kB
Tags Source
SHA-256 checksum
How to use checksums
157522506f254bbfd1f9546187ce9c7852378f84beb0fbffee086a7b21f5f03f
BLAKE2b-256 checksum
How to use checksums
f6d43d3988b0cb9eee80c6bc205f4b56a4e6e6306b9685f67a3312a352d58926
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.6 {"installer":{"name":"uv","version":"0.10.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / eth_contract-0.4.1-py3-none-any.whl

Download URL eth_contract-0.4.1-py3-none-any.whl
Size 108.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
58fa898285f4bd12520b50e1cbacbee43efa1b1ad94702f481ad0481ee8146a6
BLAKE2b-256 checksum
How to use checksums
6c01811d91abed4d75822399890e87284fbb54d19d7b8c7086e609f813544af8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.6 {"installer":{"name":"uv","version":"0.10.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for eth-contract 0.4.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page