Codegen python interfaces for web3.py contracts.
Project description
Type-safe Python bindings for Ethereum smart contracts!
Used by
Features
Static Python bindings for ethereum smart contracts.
- Parses JSON ABIs to create typesafe web3.py contract instances
- Functions have typesafe function parameters and return values
- Smart Contract internal types are exposed as dataclasses
- Contract event interfaces are exposed as typesafe classes
This project is a work-in-progress. All code is provided as is and without guarantee.
Install
pip install --upgrade pypechain
For development install instructions, see toplevel INSTALL.md
Packages 📦
Usage
Pypechain is primarily to be used via the CLI:
❯❯ pypechain -h
usage: pypechain [-h] [--output-dir OUTPUT_DIR] [--line-length LINE_LENGTH] abi_file_path
Generates class files for a given abi.
positional arguments:
abi_file_path Path to the abi JSON file or directory containing multiple JSON files.
options:
-h, --help show this help message and exit
--output-dir OUTPUT_DIR
Path to the directory where files will be generated. Defaults to pypechain_types.
--line-length LINE_LENGTH
Optional argument for the output file's maximum line length. Defaults to 80.
However, you can also run the main
script directly from Python:
from pypechain import pypechain
abi_dir = "some/abi/dir"
output_dir = "some/output/dir"
pypechain(abi_dir, output_dir)
Examples
Pypechain generates a Python module from compiled Solidity code in ABI format. This enables access to typesafe contract, struct, and event objects, which greatly improves the developer experience.
Accessing contract balances
Using web3:
from web3 import Web3
web3 = Web3()
base_token_address = "0xSomeAddress"
user_address = "0xUserAddress"
# Contract construction takes an ABI filepath string
base_token_contract = web3.eth.contract(
abi=base_contract_abi, address=web3.to_checksum_address(base_token_address)
)
# Arbitrary function arguments and names forces one to examine the ABI JSON to know the values & types
# Additionally, the types are not specified as Python types in the ABI
fn_args = [user_address]
fn_kwargs = {}
# HARD TO DISCOVER ALL FUNCTIONS AND THEIR ARGUMENTS
contract_function = base_token_contract.get_function_by_name("balanceOf")(*fn_args, **fn_kwargs)
# The function call also takes arbitrary args and kwargs
call_args = []
call_kwargs = {}
# UNTYPED RETURN VALUE!!
return_values: dict[str, Any] = contract_function.call(*call_args, **call_kwargs)
Using Pypechain generated objects:
from web3 import Web3
from pypechain_types import ERC20MintableContract
web3 = Web3()
base_token_address = "0xSomeAddress"
user_address = "0xUserAddress"
# Contracts include a factory function to initialize with your given web3 provider
base_token_contract: ERC20MintableContract = ERC20MintableContract.deploy(w3=web3, signer=user_address)
# balanceOf is a class function, enabling IDE tab-completion, intuitive inspection, typed inputs and typed outputs
user_base_balance: int = base_token_contract.functions.balanceOf(user_address).call()
Understanding contracts
Solidity files can be difficult to read for native Python programmers that have little exposure to smart contract code.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ReturnTypes {
struct SimpleStruct {
uint intVal;
string strVal;
}
struct InnerStruct {
bool boolVal;
}
struct NestedStruct {
uint intVal;
string strVal;
InnerStruct innerStruct;
}
function mixStructsAndPrimitives() public pure returns (SimpleStruct memory simpleStruct, NestedStruct memory, uint, string memory name, bool YesOrNo) {
simpleStruct = SimpleStruct({
intVal: 1,
strVal: "You are number 1"
});
NestedStruct memory nestedStruct = NestedStruct({
intVal: 2,
strVal: "You are number 2",
innerStruct: InnerStruct({boolVal: true})
});
return (simpleStruct, nestedStruct, 1, "ReturnTypesContract", false);
}
}
Running pypechain on the compiled ABI from this contract produces code that is more intuitive for Python programmers.
... # imports
@dataclass
class SimpleStruct:
"""SimpleStruct struct."""
intVal: int
strVal: str
@dataclass
class InnerStruct:
"""InnerStruct struct."""
boolVal: bool
@dataclass
class NestedStruct:
"""NestedStruct struct."""
intVal: int
strVal: str
innerStruct: InnerStruct
class ReturnTypesMixStructsAndPrimitivesContractFunction(ContractFunction):
"""ContractFunction for the mixStructsAndPrimitives method."""
class ReturnValues(NamedTuple):
"""The return named tuple for MixStructsAndPrimitives."""
simpleStruct: SimpleStruct
arg2: NestedStruct
arg3: int
name: str
YesOrNo: bool
def __call__(self) -> ReturnTypesMixStructsAndPrimitivesContractFunction:
clone = super().__call__()
self.kwargs = clone.kwargs
self.args = clone.args
return self
def call(
self,
transaction: TxParams | None = None,
block_identifier: BlockIdentifier = "latest",
state_override: CallOverride | None = None,
ccip_read_enabled: bool | None = None,
) -> ReturnValues:
"""returns ReturnValues."""
# Define the expected return types from the smart contract call
return_types = [SimpleStruct, NestedStruct, int, str, bool]
# Call the function
raw_values = super().call(transaction, block_identifier, state_override, ccip_read_enabled)
return self.ReturnValues(*rename_returned_types(return_types, raw_values))
class ReturnTypesContractFunctions(ContractFunctions):
"""ContractFunctions for the ReturnTypes contract."""
mixStructsAndPrimitives: ReturnTypesMixStructsAndPrimitivesContractFunction
def __init__(
self,
abi: ABI,
w3: "Web3",
address: ChecksumAddress | None = None,
decode_tuples: bool | None = False,
) -> None:
super().__init__(abi, w3, address, decode_tuples)
self.mixStructsAndPrimitives = ReturnTypesMixStructsAndPrimitivesContractFunction.factory(
"mixStructsAndPrimitives",
w3=w3,
contract_abi=abi,
address=address,
decode_tuples=decode_tuples,
function_identifier="mixStructsAndPrimitives",
)
class ReturnTypesContract(Contract):
"""A web3.py Contract class for the ReturnTypes contract."""
abi: ABI = returntypes_abi
bytecode: bytes = HexBytes(returntypes_bytecode)
def __init__(self, address: ChecksumAddress | None = None) -> None:
try:
# Initialize parent Contract class
super().__init__(address=address)
self.functions = ReturnTypesContractFunctions(returntypes_abi, self.w3, address)
except FallbackNotFound:
print("Fallback function not found. Continuing...")
functions: ReturnTypesContractFunctions
@classmethod
def deploy(cls, w3: Web3, signer: ChecksumAddress) -> Self:
"""Deploys and instance of the contract.
Parameters
----------
w3 : Web3
A web3 instance.
signer : ChecksumAddress
The address to deploy the contract from.
Returns
-------
Self
A deployed instance of the contract.
"""
deployer = cls.factory(w3=w3)
tx_hash = deployer.constructor().transact({"from": signer})
tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
deployed_contract = deployer(address=tx_receipt.contractAddress) # type: ignore
return deployed_contract
@classmethod
def factory(cls, w3: Web3, class_name: str | None = None, **kwargs: Any) -> Type[Self]:
contract = super().factory(w3, class_name, **kwargs)
contract.functions = ReturnTypesContractFunctions(returntypes_abi, w3, None)
return contract
Project details
Release history Release notifications | RSS feed
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
Hashes for pypechain-0.0.29-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 8932bd0c38336c857fb77d8d8c1f4e2821ae702c07901bf22740af5f54d4c8bc |
|
MD5 | 4718324aa4ccd6e50d99631e65180d2b |
|
BLAKE2b-256 | 349aa1d7d15334cbee70349b06ecf6e9c5c9be0cb64f00e7919ed82f8ecc14b3 |