Add your description here
Project description
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
- Install nix
- Install direnv and nix-direnv
uv sync --frozento install all dependencies. (Without--frozen,uvversion after v0.6.15 modifies theuv.lock)pytestto run all tests
If you are able to run all tests, you are ready to go!
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file eth_contract-0.4.0.tar.gz.
File metadata
- Download URL: eth_contract-0.4.0.tar.gz
- Upload date:
- Size: 122.2 kB
- Tags: Source
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
96313b19c20b59d0089b794f97e6abc9bb61d064f5f8e6debe4a9a3b18ba86d2
|
|
| MD5 |
e8630f45953e4702e504141a0cd25aff
|
|
| BLAKE2b-256 |
5fd961cb0d8d62c98e82319eac0e133022f02c85dda3bebc46b3a47ef7fb6068
|
File details
Details for the file eth_contract-0.4.0-py3-none-any.whl.
File metadata
- Download URL: eth_contract-0.4.0-py3-none-any.whl
- Upload date:
- Size: 106.3 kB
- Tags: Python 3
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
98cc1feffd9592c53e01b784acc39afbef3d5ed55ce9a438ffa7bef0fa9092f3
|
|
| MD5 |
a46b51143e84a0a2d148622e83e9b9cd
|
|
| BLAKE2b-256 |
1bc287f0c2a6c0a17a542d789257efb4a2a02235f56e27a79d651deca7725087
|