Deploy and Interact with a Smart Contract using Web3.py
Web3.py is a Python library that allows developers to interact with Ethereum-based blockchains with Python. Rootstock has an Ethereum-like API available that is fully compatible with Ethereum-style JSON-RPC invocations. Therefore, developers can leverage this compatibility and use the Web3.py
library to interact with Rootstock similar to how developers interact with smart contracts on Ethereum.
In this guide, you'll learn how to use the Web3.py library to deploy and interact with smart contracts on Rootstock.
See tutorial on how to interact with Rootstock using Rust
Prerequisites
- A testnet account with tRBTC funds.
- An API KEY from the Rootstock RPC Service.
- Set up the project
- A Solidity Compiler installed -> see solidity compiler installation instructions
Set up the project and install dependencies:
# create a directory for the project
mkdir web3-python-guide && cd web3-python-guide
# install python 3.10
brew install python@3.10
# set up the development virtual environment
python3.10 -m venv env
source env/bin/activate
# install dependencies
pip install Web3 py-solc-x
Solidity compiler installation instructions for MacOs:
brew install solc-select
solc-select use 0.8.25 --always-install
solc --version
# Version: 0.8.25+commit.7dd6d404.Darwin.appleclang
Set Up Secrets for the Project
We will be using sensitive data that doesn’t have to be stored in the code, and instead we will store them in a .env
file.
For that, first lets install the package to read data from the .env
file:
pip install python-dotenv
Then, we will create a .env
file and add the secrets:
touch .env
add the following variables to the file:
Replace YOUR_APIKEY
with the API key from your dashboard.
# get this YOUR_APIKEY from the Rootstock RPC Service.
RPC_PROVIDER_APIKEY = '{YOUR_APIKEY}'
# this is the private key of the account from which you will deploy the contract
ACCOUNT_PRIVATE_KEY = '{YOUR_PRIVATE_KEY}'
Deploy a smart contract
Write the smart contract
The contract to be compiled and deployed in the next section is a simple contract that stores a message, and will allow for setting different messages by sending a transaction.
You can get started by creating a file for the contract:
touch Greeter.sol
Next, add the Solidity code to the file:
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0;
contract Greeter {
string public greeting;
constructor() public {
greeting = 'Hello';
}
function setGreeting(string memory _greeting) public {
greeting = _greeting;
}
function greet() view public returns (string memory) {
return greeting;
}
}
The constructor function, which runs when the contract is deployed, sets the initial value of the string variable stored on-chain to “Hello”. The setGreeting function adds the _greeting
provided to the greeting, but a transaction needs to be sent, which modifies the stored data. Lastly, the greet
function retrieves the stored value.
Compile the smart contract
We will create a script that uses the Solidity compiler to output the bytecode and interface (ABI) for the Greeter.sol
contract. To get started, we will create a compile.py
file by running:
touch compile.py
Next, we will create the script for this file and complete the following steps:
Import the solcx
package, which will compile the source code
Compile the Greeter.sol
contract using the solcx.compile_files
function
Export the contract's ABI and bytecode
Code and paste the code below into compile.py
;
import solcx
solcx.install_solc('0.8.25')
# Compile contract
temp_file = solcx.compile_files(
'Greeter.sol',
output_values=['abi', 'bin'],
solc_version='0.8.25'
)
# Export contract data
abi = temp_file['Greeter.sol:Greeter']['abi']
bytecode = temp_file['Greeter.sol:Greeter']['bin']
You can now run the script to compile the contract:
python compile.py
Deploy the smart contract
With the script for compiling the Greeter.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 theGreeter.sol
contract - Set up the Web3 provider
In order to set up the Web3 Provider, we have to read the environment variables that we previously added to the .env file.
# Add the Web3 Provider
RPC_PROVIDER_APIKEY = os.getenv('RPC_PROVIDER_APIKEY')
RPC_PROVIDER_URL = 'https://rpc.testnet.rootstock.io/' + RPC_PROVIDER_APIKEY
web3 = Web3(Web3.HTTPProvider(RPC_PROVIDER_URL))
- Define the
account_from
. The private key is required to sign the transaction. Note: This is for example purposes only. Never store your private keys in your code
# Set the default account
PRIVATE_KEY = os.getenv('ACCOUNT_PRIVATE_KEY')
account_from = {
'private_key': PRIVATE_KEY,
'address': web3.eth.account.from_key(PRIVATE_KEY).address
}
- Create a contract instance using the
web3.eth.contract
function and passing in the ABI and bytecode of the contract - Set the gas price strategy using the
web3.eth.set_gas_price_strategy
function, which will allow us to fetch the gasPrice from the RPC Provider. This is important because otherwise the Web3 library will attempt to useeth_maxPriorityFeePerGas
andeth_feeHistory
RPC methods, which are only supported by post-London Ethereum nodes. - Build a constructor transaction using the contract instance. You will 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 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
Code and paste the code below into deploy.py
;
from compile import abi, bytecode
from web3 import Web3
from web3.gas_strategies.rpc import rpc_gas_price_strategy
from dotenv import load_dotenv
import os
load_dotenv()
# Add the Web3 Provider
RPC_PROVIDER_APIKEY = os.getenv('RPC_PROVIDER_APIKEY')
RPC_PROVIDER_URL = 'https://rpc.testnet.rootstock.io/' + RPC_PROVIDER_APIKEY
web3 = Web3(Web3.HTTPProvider(RPC_PROVIDER_URL))
# Set the default account
PRIVATE_KEY = os.getenv('ACCOUNT_PRIVATE_KEY')
account_from = {
'private_key': PRIVATE_KEY,
'address': web3.eth.account.from_key(PRIVATE_KEY).address
}
print("Attempting to deploy from account: ", account_from['address'])
# Create contract instance
Greeter = web3.eth.contract(abi=abi, bytecode=bytecode)
# Set the gas price strategy
web3.eth.set_gas_price_strategy(rpc_gas_price_strategy)
# Build the transaction
construct_txn = Greeter.constructor().build_transaction({
'from': account_from['address'],
'nonce': web3.eth.get_transaction_count(account_from['address']),
'gasPrice': web3.eth.generate_gas_price()
})
# Sign the transaction that deploys the contract
signed_txn = web3.eth.account.sign_transaction(construct_txn, account_from['private_key'])
# Send the transaction that deploys the contract
txn_hash = web3.eth.send_raw_transaction(signed_txn.rawTransaction)
# Wait for the transaction to be mined, and get the transaction receipt
txn_receipt = web3.eth.wait_for_transaction_receipt(txn_hash)
print(f"Transaction successful with hash: { txn_receipt.transactionHash.hex() }")
print(f"Contract deployed at address: { txn_receipt.contractAddress }")
Now you can run the script and get the result.
python deploy.py
>> Attempting to deploy from account: 0x3b32a6463Bd0837fBF428bbC2A4c8B4c022e5077
>> Transaction successful with hash: 0x98a256c106bdb65e4de6a267e94000acdfe0d6f23c3dc1444f14dccf00713a69
>> Contract deployed at address: 0xba39f329255d55a0276c695111b2edc9250C2341
Note: Save the contract address, as we will use it later in the guide.