This article will give you a quick overview of a Python library, Web3.py. By the end of this article, we will install it on our local computer and understand how to use the basic functionality, such as sending Ether, deploying a Smart Contract, and interacting with it in Python.
What is Web3.py?
Web3.py is a Python library for interacting with Ethereum. Simply put, Ethereum is a collection of computers (or nodes) running specific software (or Ethereum client) communicating with each other over the Internet. Therefore, if we want to interact with Ethereum from our Python programs outside the Ethereum network, we need to connect to one of the Ethereum nodes first. Web3.py is a library to make it easy to connect to an Ethereum node and interact with the Ethereum network.
There are several ways to connect to an Ethereum node using Web3.py, such as IPC (if an Ethereum node is running on the same machine), HTTP, or WebSocket. However, in this article, we will use a local Ethereum network provided by eth-tester. It is basically a test stub designed for local development and testing purposes. It is integrated with Web3.py, so it is a good option for us to see how Web3.py works.
We also use Jupyter Notebook in this article. It is probably not the best way to develop Web3 applications, but again it is a good option to explore Web3.py and demonstrate how it works.
How to Install web3.py
Firstly create a virtual environment and install the following packages using pip.
jupyterlab
(Jupyter Notebook)web3
(Web3.py and tester)py-solc-x
(Python wrapper for the solc Solidity compiler)
Open a terminal and execute the following commands:
$ mkdir web3py $ cd web3py $ python3 -m venv venv $ source ./venv/bin/activate (venv) $
Then, install the packages using pip as follows:
(venv) $ pip install -U pip (venv) $ pip install jupyterlab (venv) $ pip install web3 (venv) $ pip install "web3[tester]" (venv) $ pip install py-solc-x
Note that the version of Web3.py is downgraded when I install web3[tester] for some reason, so double-check the version:
(venv) $ pip list ... web3 5.9.0 ...
If the version is not the latest (5.24 at the time of writing), try upgrading the library. In my environment, simply upgrading it seems to work fine.
(venv) $ pip install -U web3 (venv) $ pip list ... web3 5.24.0 ...
How to Connect to a Local Ethereum Test Network
Start Jupyter Notebook by running the command below.
(venv) $ jupyter notebook
The default web browser will automatically open the Jupyter notebook home page. Create a new notebook using the Python 3 kernel.
As mentioned above, we will use a test Ethereum network running in the local environment for demonstration purposes. To connect to the local Ethereum network, we can use Web3.EthereumTesterProvider()
as shown below.
from web3 import Web3 w3 = Web3(Web3.EthereumTesterProvider()) print(w3.isConnected())
If the connection is successful, we will get the following output:
True
EthereumTesterProvider
creates 10 accounts, which we can use to interact with the network.
w3.eth.accounts
Output:
['0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf', '0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF', '0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69', '0x1efF47bc3a10a45D4B230B5d10E37751FE6AA718', '0xe1AB8145F7E55DC933d51a18c793F901A3A0b276', '0xE57bFE9F44b819898F47BF37E5AF72a0783e1141', '0xd41c057fd1c78805AAC12B0A94a405c0461A6FBb', '0xF1F6619B38A98d6De0800F1DefC0a6399eB6d30C', '0xF7Edc8FA1eCc32967F827C9043FcAe6ba73afA5c', '0x4CCeBa2d7D2B4fdcE4304d3e09a1fea9fbEb1528']
We can check the Ether balance of each account by running get_balance()
. The unit is wei, but we can convert it to ether by using the utility method fromWei()
if necessary, as shown below:
for account in w3.eth.accounts: balance = w3.eth.get_balance(account) print(f'Account: {account}, balance: {balance} wei = {w3.fromWei(balance, "ether")} ether')
Output:
Account: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, balance: 1000000000000000000000000 wei = 1000000 ether Account: 0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF, balance: 1000000000000000000000000 wei = 1000000 ether Account: 0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69, balance: 1000000000000000000000000 wei = 1000000 ether Account: 0x1efF47bc3a10a45D4B230B5d10E37751FE6AA718, balance: 1000000000000000000000000 wei = 1000000 ether Account: 0xe1AB8145F7E55DC933d51a18c793F901A3A0b276, balance: 1000000000000000000000000 wei = 1000000 ether Account: 0xE57bFE9F44b819898F47BF37E5AF72a0783e1141, balance: 1000000000000000000000000 wei = 1000000 ether Account: 0xd41c057fd1c78805AAC12B0A94a405c0461A6FBb, balance: 1000000000000000000000000 wei = 1000000 ether Account: 0xF1F6619B38A98d6De0800F1DefC0a6399eB6d30C, balance: 1000000000000000000000000 wei = 1000000 ether Account: 0xF7Edc8FA1eCc32967F827C9043FcAe6ba73afA5c, balance: 1000000000000000000000000 wei = 1000000 ether Account: 0x4CCeBa2d7D2B4fdcE4304d3e09a1fea9fbEb1528, balance: 1000000000000000000000000 wei = 1000000 ether
As you can see, each test account has 1 million ether. These are not real Ether, so they are not worth anything in the real world (just in case you are wondering).
How to send Ether
We can send Ether from one account to another by using the method send_transaction()
. For example, the following code sends five ether from the first account (accounts[0]
) to the second account (accounts[1]
):
w3.eth.send_transaction({ 'from': w3.eth.accounts[0], 'to': w3.eth.accounts[1], 'value': w3.toWei(5, 'ether') })
Output:
HexBytes('0x09e35f432cfd9cf4b4ba06a7c2d617e41f05eac2b1df5db550a9c63fe6d902c0')
The output is the transaction hash. We will use it to get more details about the transaction later, but first, we can confirm that the balances have been changed. The first account has five ether less than before and the second account has five ether more.
for account in w3.eth.accounts[0:2]: print(f'Account: {account}, balance: {w3.fromWei(w3.eth.get_balance(account), "ether")}')
Output:
Account: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, balance: 999994.999999999999979 Account: 0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF, balance: 1000005
The first account has slightly less than 999,995 ether (21,000 wei less, to be precise) because it paid the transaction fee (gas) when making the transaction. You can check the details of the transaction by the method get_transaction_receipt()
with the transaction hash returned by the method send_transaction()
earlier in this section.
w3.eth.get_transaction_receipt('0x09e35f432cfd9cf4b4ba06a7c2d617e41f05eac2b1df5db550a9c63fe6d902c0')
Output:
AttributeDict({'transactionHash': HexBytes('0x09e35f432cfd9cf4b4ba06a7c2d617e41f05eac2b1df5db550a9c63fe6d902c0'), 'transactionIndex': 0, 'blockNumber': 1, 'blockHash': HexBytes('0xf9577944f63953f51e080917c07437adfa90a923518f323ca204db5c15b99c41'), 'cumulativeGasUsed': 21000, 'gasUsed': 21000, 'contractAddress': None, 'logs': [], 'status': 1})
You can see that the gasUsed
value is indeed 21000, which is the base fee for a transaction in Ethereum. In the test network, the gas price is set to 1 wei as shown below, so the total transaction fee was 21,000 wei.
w3.eth.gas_price
Output:
1
You can find more about Ethereum’s gas in the following Finxter article:
How to Deploy a Smart Contract
Now let’s see how the Smart Contract deployment works using Web3.py. First, we need a Smart Contract. In this article, we will use a simple Smart Contract below, which I have taken from the Solidity documentation:
// storage.sol // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.4.16 <0.9.0; contract SimpleStorage { uint storedData; function set(uint x) public { storedData = x; } function get() public view returns (uint) { return storedData; } }
On Jupyter Notebook, create a new text file with the content above and save it as storage.sol
in the same directory as the notebook.
Then, go back to the notebook. Before deploying the Smart Contract, we need some preparations, such as setting up the compiler and compiling the Smart Contract. Let’s look at the steps one by one.
Import the Solidity compiler solcx
and install it:
import solcx solcx.install_solc()
Output:
Version('0.8.10')
Compile the Smart Contract storage.sol
:
compiled_sol = solcx.compile_files( ['storage.sol'], output_values=["abi", "bin"], solc_version='0.8.10' ) contract_id, contract_interface = compiled_sol.popitem() print(f'contract_id = {contract_id}') print(f'contract_interface = {contract_interface}')
Output:
contract_id = storage.sol:SimpleStorage contract_interface = {'abi': [{'inputs': [], 'name': 'get', 'outputs': [{'internalType': 'uint256', 'name': '', 'type': 'uint256'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'internalType': 'uint256', 'name': 'x', 'type': 'uint256'}], 'name': 'set', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function'}], 'bin': '608060405234801561001057600080fd5b50610150806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806360fe47b11461003b5780636d4ce63c14610057575b600080fd5b610055600480360381019061005091906100c3565b610075565b005b61005f61007f565b60405161006c91906100ff565b60405180910390f35b8060008190555050565b60008054905090565b600080fd5b6000819050919050565b6100a08161008d565b81146100ab57600080fd5b50565b6000813590506100bd81610097565b92915050565b6000602082840312156100d9576100d8610088565b5b60006100e7848285016100ae565b91505092915050565b6100f98161008d565b82525050565b600060208201905061011460008301846100f0565b9291505056fea2646970667358221220c742cec841ceedc32c9418a24f498eedbdaa8f7350de2a9f008b8f762a34fd4f64736f6c634300080a0033'}
You can see the abi (Application Binary Interface) and bin (byte code) of the compiled code, which are used in the next step.
Finally, we can deploy the Smart Contract by the method contract()
. When this method is called without specifying address, it will create a new Smart Contract.
tx_hash = w3.eth.contract( abi=contract_interface['abi'], bytecode=contract_interface['bin'] ).constructor().transact()
You can find the address of the deployed Smart Contract by looking up the transaction using the transaction hash returned by the contract()
method:
address = w3.eth.get_transaction_receipt(tx_hash)['contractAddress'] print(f'address = {address}')
Output:
address = 0xF2E246BB76DF876Cef8b38ae84130F4F55De395b
How to use a Smart Contract
Now that we have deployed the Smart Contract and got the address, we can create a contract object using the method contract()
with the address and abi as shown below.
contract = w3.eth.contract(address=address, abi=contract_interface["abi"])
The Smart Contract has two functions, get()
and set()
. Let’s use call()
to execute the function get()
and find the current value.
contract.functions.get().call()
Output:
0
We can see that the current value is 0.
Now let’s try the function set()
to update the storage value from 0 to 100. As this function changes the state of the blockchain, we use the function transact()
, which will send a new transaction.
tx_hash = contract.functions.set(100).transact() receipt = w3.eth.wait_for_transaction_receipt(tx_hash) print(receipt)
Output:
AttributeDict({'transactionHash': HexBytes('0xeeb66d2757938585cf52c4cd215d4f4f3c6bb7cc72bde6a3f495e68f68811288'), 'transactionIndex': 0, 'blockNumber': 2, 'blockHash': HexBytes('0x07afe70cd6221e58be98eedbd28702fc108f6725f2855687f1870a70c08b8a6a'), 'cumulativeGasUsed': 41862, 'gasUsed': 41862, 'contractAddress': None, 'logs': [], 'status': 1})
The function finished successfully (status = 1). We can check the stored value by running the function get()
again.
contract.functions.get().call()
Output:
100
We can confirm that the value has been updated to 100.
Summary
In this article, we looked at the basic functionality of Web3.py, a Python library for interacting with Ethereum.
Firstly, we installed web3.py
and the related packages and set up the environment. Then, we connected to the local test Ethereum network, explored the accounts and balances, and sent some Ether from one account to another.
Lastly, we compiled a Smart Contract, deployed it to the local test Ethereum network, and executed the functions in the Smart Contract.
I hope this article has been a helpful introduction to Web3.py. You can find more about Web3.py
in the Web3.py documentation.