Web3.py Python Library¶
Introduction¶
Web3.py is a set of libraries that allow developers to interact with Ethereum nodes using HTTP, IPC, or WebSocket protocols with Python. Tanssi EVM appchains have an Ethereum-like API available that is fully compatible with Ethereum-style JSON RPC invocations. Therefore, developers can leverage this compatibility and use the Ethers.js library to interact with a Tanssi EVM appchain node as if they were doing so on Ethereum. For more information on Web3.py, check their documentation site.
In this guide, you'll learn how to use setup the Web3.py library for your Tanssi EVM appchain. Next, to showcase the library in action, you'll use Web3.py to send a transaction and deploy a contract on a Tanssi EVM appchain running in Tanssi's Dancebox TestNet. This guide can be adapted for your own Tanssi EVM appchain by simply changing the endpoint.
Note
The examples in this guide are based on a MacOS or Ubuntu 20.04 environment. If you're using Windows, you'll need to adapt them accordingly.
Furthermore, please ensure that you have Python3 and a package manager like pip installed.
Checking Prerequisites¶
For the examples in this guide, you will need to have the following:
- An account with funds in the Tanssi EVM appchain you are testing with
Installing Web3.py¶
For this guide, you'll need to install the Web3.py library and the Solidity compiler. To install both packages, you can run the following command:
pip3 install web3 py-solc-x
Setting up the Web3.py Provider¶
Throughout this guide, you'll be creating a bunch of scripts that provide different functionality such as sending a transaction, deploying a contract, and interacting with a deployed contract. In most of these scripts you'll need to create an Web3.py provider to interact with the network.
To create a provider, you can take the following steps:
- Import the
web3
library - Create the
web3
provider suing using theWeb3(Web3.HTTPProvider()))
method and providing the Tanssi EVM appchain URL
# 1. Import web3.py
from web3 import Web3
# 2. Create web3.py provider
# Insert your RPC URL here
web3 = Web3(Web3.HTTPProvider('https://fraa-dancebox-3001-rpc.a.dancebox.tanssi.network'))
Save this code snippet as you'll need it for the scripts that are used in the following sections.
Send a Transaction¶
During this section, you'll be creating a couple of scripts. The first one will be to check the balances of your accounts before trying to send a transaction. The second script will actually send the transaction.
You can also use the balance script to check the account balances after the transaction has been sent.
Check Balances Script¶
You'll only need one file to check the balances of both addresses before and after the transaction is sent. To get started, you can create a balances.py
file by running:
touch balances.py
Next, you will create the script for this file and complete the following steps:
- Set up the Web3 provider
- Define the
address_from
andaddress_to
variables - Get the balance for the accounts using the
web3.eth.get_balance
function and format the results using theweb3.from_wei
# 1. Import web3.py
from web3 import Web3
# 2. Create web3.py provider
provider_rpc = {
# Insert your RPC URL here
"evm_appchain": "https://fraa-dancebox-3001-rpc.a.dancebox.tanssi.network",
}
web3 = Web3(Web3.HTTPProvider(provider_rpc["evm_appchain"]))
# 2. Create address variables
address_from = "INSERT_ADDRESS_FROM"
address_to = "INSERT_ADDRESS_TO"
# 4. Fetch balance data
balance_from = web3.from_wei(
web3.eth.get_balance(Web3.to_checksum_address(address_from)), "ether"
)
balance_to = web3.from_wei(
web3.eth.get_balance(Web3.to_checksum_address(address_to)), "ether"
)
print(f"The balance of { address_from } is: { balance_from } TANGO")
print(f"The balance of { address_to } is: { balance_to } TANGO")
To run the script and fetch the account balances, you can run the following command:
python3 balances.py
If successful, the balances for the origin and receiving address will be displayed in your terminal in TANGO.
Send Transaction Script¶
You'll only need one file for executing a transaction between accounts. For this example, you'll be transferring 1 DEV token from an origin address (from which you hold the private key) to another address. To get started, you can create a transaction.py
file by running:
touch transaction.py
Next, you will create the script for this file and complete the following steps:
- Add imports, including Web3.py and the
rpc_gas_price_strategy
, which will be used in the following steps to get the gas price used for the transaction - Set up the Web3 provider
- Define the
account_from
, including theprivate_key
, and theaddress_to
variables. The private key is required to sign the transaction. Note: This is for example purposes only. Never store your private keys in a Python file - Use the Web3.py Gas Price API to set a gas price strategy. For this example, you'll use the imported
rpc_gas_price_strategy
- Create and sign the transaction using the
web3.eth.account.sign_transaction
function. Pass in thenonce
gas
,gasPrice
,to
, andvalue
for the transaction along with the sender'sprivate_key
. To get thenonce
you can use theweb3.eth.get_transaction_count
function and pass in the sender's address. To predetermine thegasPrice
you'll use theweb3.eth.generate_gas_price
function. For thevalue
, you can format the amount to send from an easily readable format to Wei using theweb3.to_wei
function - Using the signed transaction, you can then send it using the
web3.eth.send_raw_transaction
function and wait for the transaction receipt by using theweb3.eth.wait_for_transaction_receipt
function
# 1. Add imports
from web3 import Web3
from web3.gas_strategies.rpc import rpc_gas_price_strategy
# 2. Create web3.py provider
provider_rpc = {
# Insert your RPC URL here
"evm_appchain": "https://fraa-dancebox-3001-rpc.a.dancebox.tanssi.network",
}
web3 = Web3(Web3.HTTPProvider(provider_rpc["evm_appchain"]))
# 3. Create address variables
account_from = {
"private_key": "INSERT_ YOUR_PRIVATE_KEY",
"address": "INSERT_ PUBLIC_ADDRESS_OF_PK",
}
address_to = "INSERT_ ADDRESS_TO"
print(
f'Attempting to send transaction from { account_from["address"] } to { address_to }'
)
# 4. Set the gas price strategy
web3.eth.set_gas_price_strategy(rpc_gas_price_strategy)
# 5. Sign tx with PK
tx_create = web3.eth.account.sign_transaction(
{
"nonce": web3.eth.get_transaction_count(
Web3.to_checksum_address(account_from["address"])
),
"gasPrice": web3.eth.generate_gas_price(),
"gas": 21000,
"to": Web3.to_checksum_address(address_to),
"value": web3.to_wei("1", "ether"),
},
account_from["private_key"],
)
# 6. Send tx and wait for receipt
tx_hash = web3.eth.send_raw_transaction(tx_create.rawTransaction)
tx_receipt = web3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Transaction successful with hash: { tx_receipt.transactionHash.hex() }")
To run the script, you can run the following command in your terminal:
python3 transaction.py
If the transaction was succesful, in your terminal you'll see the transaction hash has been printed out.
You can also use the balances.py
script to check that the balances for the origin and receiving accounts have changed. The entire workflow would look like this:
Deploy a Contract¶
The contract you'll be compiling and deploying in the next couple of sections is a simple incrementer contract, arbitrarily named Incrementer.sol
. You can get started by creating a file for the contract:
touch Incrementer.sol
Next, you can add the Solidity code to the file:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Incrementer {
uint256 public number;
constructor(uint256 _initialNumber) {
number = _initialNumber;
}
function increment(uint256 _value) public {
number = number + _value;
}
function reset() public {
number = 0;
}
}
The constructor
function, which runs when the contract is deployed, sets the initial value of the number variable stored on-chain (default is 0). The increment
function adds the _value
provided to the current number, but a transaction needs to be sent, which modifies the stored data. Lastly, the reset
function resets the stored value to zero.
Note
This contract is a simple example for illustration purposes only.
Compile Contract Script¶
In this section, you'll create a script that uses the Solidity compiler to output the bytecode and interface (ABI) for the Incrementer.sol
contract. To get started, you can create a compile.py
file by running:
touch compile.py
Next, you will create the script for this file and complete the following steps:
- Import the
solcx
package - Optional - If you haven't already installed the Solidity compiler, you can do so with by using the
solcx.install_solc
function - Compile the
Incrementer.sol
function using thesolcx.compile_files
function - Export the contract's ABI and bytecode
# 1. Import solcx
import solcx
# 2. If you haven't already installed the Solidity compiler, uncomment the following line
# solcx.install_solc()
# 3. Compile contract
temp_file = solcx.compile_files(
"Incrementer.sol",
output_values=["abi", "bin"],
# solc_version='0.8.19'
)
# 4. Export contract data
abi = temp_file["Incrementer.sol:Incrementer"]["abi"]
bytecode = temp_file["Incrementer.sol:Incrementer"]["bin"]
Note
If you see an error stating that Solc is not installed
, uncomment the step 2 described in the code snippet.
Deploy Contract Script¶
With the script for compiling the Incrementer.sol
contract in place, you can then use the results to send a signed transaction that deploys it. To do so, you can create a file for the deployment script called deploy.py
:
touch deploy.py
Next, you will create the script for this file and complete the following steps:
- Add imports, including Web3.py and the ABI and bytecode of the
Incrementer.sol
contract - Set up the Web3 provider
- Define the
account_from
, including theprivate_key
. The private key is required to sign the transaction. Note: This is for example purposes only. Never store your private keys in a Python file - Create a contract instance using the
web3.eth.contract
function and passing in the ABI and bytecode of the contract - Build a constructor transaction using the contract instance and passing in the value to increment by. For this example, you can use
5
. You'll then use thebuild_transaction
function to pass in the transaction information including thefrom
address and thenonce
for the sender. To get thenonce
you can use theweb3.eth.get_transaction_count
function - Sign the transaction using the
web3.eth.account.sign_transaction
function and pass in the constructor transaction and theprivate_key
of the sender - Using the signed transaction, you can then send it using the
web3.eth.send_raw_transaction
function and wait for the transaction receipt by using theweb3.eth.wait_for_transaction_receipt
function
# 1. Add imports
from compile import abi, bytecode
from web3 import Web3
# 2. Create web3.py provider
provider_rpc = {
# Insert your RPC URL here
"evm_appchain": "https://fraa-dancebox-3001-rpc.a.dancebox.tanssi.network",
}
web3 = Web3(Web3.HTTPProvider(provider_rpc["evm_appchain"]))
# 3. Create address variable
account_from = {
"private_key": "INSERT_YOUR_PRIVATE_KEY",
"address": "INSERT_PUBLIC_ADDRESS_OF_PK",
}
print(f'Attempting to deploy from account: { account_from["address"] }')
# 4. Create contract instance
Incrementer = web3.eth.contract(abi=abi, bytecode=bytecode)
# 5. Build constructor tx
construct_txn = Incrementer.constructor(5).build_transaction(
{
"from": Web3.to_checksum_address(account_from["address"]),
"nonce": web3.eth.get_transaction_count(
Web3.to_checksum_address(account_from["address"])
),
}
)
# 6. Sign tx with PK
tx_create = web3.eth.account.sign_transaction(
construct_txn, account_from["private_key"]
)
# 7. Send tx and wait for receipt
tx_hash = web3.eth.send_raw_transaction(tx_create.rawTransaction)
tx_receipt = web3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Contract deployed at address: { tx_receipt.contractAddress }")
To run the script, you can enter the following command into your terminal:
python3 deploy.py
If successful, the contract's address will be displayed in the terminal.
Read Contract Data (Call Methods)¶
Call methods are the type of interaction that don't modify the contract's storage (change variables), meaning no transaction needs to be sent. They simply read various storage variables of the deployed contract.
To get started, you can create a file and name it get.py
:
touch get.py
Then you can take the following steps to create the script:
- Add imports, including Web3.py and the ABI of the
Incrementer.sol
contract - Set up the Web3 provider
- Define the
contract_address
of the deployed contract - Create a contract instance using the
web3.eth.contract
function and passing in the ABI and address of the deployed contract - Using the contract instance, you can then call the
number
function
# 1. Import the ABI
from compile import abi
from web3 import Web3
# 2. Create web3.py provider
provider_rpc = {
# Insert your RPC URL here
"evm_appchain": "https://fraa-dancebox-3001-rpc.a.dancebox.tanssi.network",
}
web3 = Web3(Web3.HTTPProvider(provider_rpc["evm_appchain"]))
# 3. Create address variable
contract_address = "INSERT_CONTRACT_ADDRESS"
print(f"Making a call to contract at address: { contract_address }")
# 4. Create contract instance
Incrementer = web3.eth.contract(address=contract_address, abi=abi)
# 5. Call Contract
number = Incrementer.functions.number().call()
print(f"The current number stored is: { number } ")
To run the script, you can enter the following command in your terminal:
python3 get.py
If successful, the value will be displayed in the terminal.
Interact with Contract (Send Methods)¶
Send methods are the type of interaction that modify the contract's storage (change variables), meaning a transaction needs to be signed and sent. In this section, you'll create two scripts: one to increment and one to reset the incrementer. To get started, you can create a file for each script and name them increment.py
and reset.py
:
touch increment.py reset.py
Open the increment.py
file and take the following steps to create the script:
- Add imports, including Web3.py and the ABI of the
Incrementer.sol
contract - Set up the Web3 provider
- Define the
account_from
, including theprivate_key
, thecontract_address
of the deployed contract, and thevalue
to increment by. The private key is required to sign the transaction. Note: This is for example purposes only. Never store your private keys in a Python file - Create a contract instance using the
web3.eth.contract
function and passing in the ABI and address of the deployed contract - Build the increment transaction using the contract instance and passing in the value to increment by. You'll then use the
build_transaction
function to pass in the transaction information including thefrom
address and thenonce
for the sender. To get thenonce
you can use theweb3.eth.get_transaction_count
function - Sign the transaction using the
web3.eth.account.sign_transaction
function and pass in the increment transaction and theprivate_key
of the sender - Using the signed transaction, you can then send it using the
web3.eth.send_raw_transaction
function and wait for the transaction receipt by using theweb3.eth.wait_for_transaction_receipt
function
# 1. Add imports
from compile import abi
from web3 import Web3
# 2. Create web3.py provider
provider_rpc = {
# Insert your RPC URL here
"evm_appchain": "https://fraa-dancebox-3001-rpc.a.dancebox.tanssi.network",
}
web3 = Web3(Web3.HTTPProvider(provider_rpc["evm_appchain"]))
# 3. Create variables
account_from = {
"private_key": "INSERT_YOUR_PRIVATE_KEY",
"address": "INSERT_PUBLIC_ADDRESS_OF_PK",
}
contract_address = "INSERT_CONTRACT_ADDRESS"
value = 3
print(
f"Calling the increment by { value } function in contract at address: { contract_address }"
)
# 4. Create contract instance
Incrementer = web3.eth.contract(address=contract_address, abi=abi)
# 5. Build increment tx
increment_tx = Incrementer.functions.increment(value).build_transaction(
{
"from": Web3.to_checksum_address(account_from["address"]),
"nonce": web3.eth.get_transaction_count(
Web3.to_checksum_address(account_from["address"])
),
}
)
# 6. Sign tx with PK
tx_create = web3.eth.account.sign_transaction(increment_tx, account_from["private_key"])
# 7. Send tx and wait for receipt
tx_hash = web3.eth.send_raw_transaction(tx_create.rawTransaction)
tx_receipt = web3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Tx successful with hash: { tx_receipt.transactionHash.hex() }")
To run the script, you can enter the following command in your terminal:
python3 increment.py
If successful, the transaction hash will be displayed in the terminal. You can use the get.py
script alongside the increment.py
script to make sure that value is changing as expected:
Next you can open the reset.py
file and take the following steps to create the script:
- Add imports, including Web3.py and the ABI of the
Incrementer.sol
contract - Set up the Web3 provider
- Define the
account_from
, including theprivate_key
, and thecontract_address
of the deployed contract. The private key is required to sign the transaction. Note: This is for example purposes only. Never store your private keys in a Python file - Create a contract instance using the
web3.eth.contract
function and passing in the ABI and address of the deployed contract - Build the reset transaction using the contract instance. You'll then use the
build_transaction
function to pass in the transaction information including thefrom
address and thenonce
for the sender. To get thenonce
you can use theweb3.eth.get_transaction_count
function - Sign the transaction using the
web3.eth.account.sign_transaction
function and pass in the reset transaction and theprivate_key
of the sender - Using the signed transaction, you can then send it using the
web3.eth.send_raw_transaction
function and wait for the transaction receipt by using theweb3.eth.wait_for_transaction_receipt
function
# 1. Add imports
from compile import abi
from web3 import Web3
# 2. Create web3.py provider
provider_rpc = {
# Insert your RPC URL here
"evm_appchain": "https://fraa-dancebox-3001-rpc.a.dancebox.tanssi.network",
}
web3 = Web3(Web3.HTTPProvider(provider_rpc["evm_appchain"]))
# 3. Create variables
account_from = {
"private_key": "INSERT_YOUR_PRIVATE_KEY",
"address": "INSERT_PUBLIC_ADDRESS_OF_PK",
}
contract_address = "INSERT_CONTRACT_ADDRESS"
print(f"Calling the reset function in contract at address: { contract_address }")
# 4. Create contract instance
Incrementer = web3.eth.contract(address=contract_address, abi=abi)
# 5. Build reset tx
reset_tx = Incrementer.functions.reset().build_transaction(
{
"from": Web3.to_checksum_address(account_from["address"]),
"nonce": web3.eth.get_transaction_count(
Web3.to_checksum_address(account_from["address"])
),
}
)
# 6. Sign tx with PK
tx_create = web3.eth.account.sign_transaction(reset_tx, account_from["private_key"])
# 7. Send tx and wait for receipt
tx_hash = web3.eth.send_raw_transaction(tx_create.rawTransaction)
tx_receipt = web3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Tx successful with hash: { tx_receipt.transactionHash.hex() }")
To run the script, you can enter the following command in your terminal:
python3 reset.py
If successful, the transaction hash will be displayed in the terminal. You can use the get.py
script alongside the reset.py
script to make sure that value is changing as expected:
| Created: September 1, 2023