Interacting with Python

Using web3.py

Installation

We will use the web3.py package to programatically interact with the Ethereum testnet.

$ pip install web3

Once installed, we can connect to the RPC using

from web3 import Web3

w3 = Web3(Web3.HTTPProvider('http://127.0.0.1:8545'))

In order to interact with contracts specifically, we need to be able to provide the ABI (Application Binary Interface) of the contract. An ABI decribes the protocol for interacting with a system, in this case, for interacting with the contract. Effectively, an ABI tells web3.py exactly how to interact with the remote contract.

Luckily, we can use Python to generate the ABI from the source code itself. To do so, we need to install py-solc-x:

$ pip install py-solc-x

py-solc-x handles versions of solc, the Solidity compiler. Different versions could generate different ABIs, so keep an eye on it, but generally it won't matter too much. Before we can use it to generate a contract's ABI, we have to install the version of solc that we want:

from solcx import install_solc

# install the latest version
install_solc("0.8.29")

Interacting with Contracts in Python

Now we can use the source code of the Solidity contract to generate an ABI for it. After that, everything is pretty self-explanatory!

from web3 import Web3
from solcx import set_solc_version, compile_source

set_solc_version('0.8.29')

with open('src/Counter.sol') as f:
    compiled_sol = compile_source(f.read(), output_values=['abi'])

contract_id, contract_interface = compiled_sol.popitem()
abi = contract_interface['abi']

w3 = Web3(Web3.HTTPProvider('http://127.0.0.1:8545'))
counter = w3.eth.contract(
    address='0x5FbDB2315678afecb367f032d93F642f64180aa3',
    abi=abi
)

# read state
print(counter.functions.getCounter().call())

# update state
print(counter.functions.increment().transact())

Running this code multiple times shows the counter incrementing!

Accounts on Local Nodes

Typically transactions - such as the last line, with .transact() - need to be signed using a private key. Here, we have not specified the private key, yet it still works. Why is that?

Our anvil testnet spins up a node locally, with several unlocked accounts. Running the program locally, w3 will take the first unlocked account and use it. We can print the unlocked accounts out:

print(w3.eth.accounts)
# ['0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', '0x90F79bf6EB2c4f870365E785982E1f101E93b906', '0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65', '0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc', '0x976EA74026E726554dB657fA54763abd0C3a0aa9', '0x14dC79964da2C08b23698B3D3cc7Ca32193d9955', '0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f', '0xa0Ee7A142d267C1f36714E4a8F75612F20a79720']

Comparing this to the output of anvil, we see that they are the same addresses in the same order. To override this functionality, we can set the variable w3.eth.default_account, which defaults to None (and when it's None, the default account used is w3.eth.accounts[0]).

w3.eth.default_account = '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC'

Alternatively, if it's a one-off, you can set the from field in the transact method:

counter.functions.increment().transact({
    'from': '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC'
})

Last updated

Was this helpful?