💡 I’ve been dying to write this series. As a Pythonista, this is just…chefkiss
If you don't want to read, but watch a video CLICK HERE
OK. You’re a Pythonista. You’re in web3 or looking at web3. You want to use them Python skills. Lets get started.
In this article we’re going to install and setup Vyper (smart contract language), Ape (development framework and Web3.py(interact with smart contract). This is the full pythonic web3 stack.
Click here to fetch the Github repo.
Setup your virtual environment
As pythonista’s we like to keep a clean pc. Let’s spin up a virtual environment.
python3 -m venv ./myv
Activate the virtual environment by running:
source ./myv/bin/activate
Install Dependencies
Cool we got three things to install: Vyper, Ape and web3.py
pip install vyper eth-ape web3
awesome.
One more thing. Ape works with plugins
. So its very bare-bone. Depending on your smart contract language, and the network you can want to connect, you can install plugins. We’re going to keep it simple for now. We’re using vyper, so install pip install ape-vyper
. More info, click here.
Build an Ape project
So next up inside any folder you want, create an ape
project. First lets create and navigate to a new folder
mkdir hello_world
cd hello_world
Then use the following command to create a new ape project
ape init
You will be prompted for a name for your project. I named mine hello
Write you Vyper contract
So Vyper is pythonic
, which means it’s very similar to python, with a few differences in syntax and the way the language operates. Rest assured, these are small adjustments. Let’s start nice and easy with a hello world contract. It has a variable with hello world and one function to set the value of the variable when we deploy the contract.
# Define the version
# @version ^0.3.0
# Create a string variable that can store maximum 100 characters
greet: public(String[100])
#Define the visibility with a decorator
@external
#This is the constructor that sets the value of the variable greet
#upon deploying the smart contract
def __init__():
self.greet = "Hello World"
Before we compile and deploy, let’s take a closer look.
Variable
First is our variable. The structure of defining a variable is as follows:
:()
So in our case we have a string named greet
. Anyone can see this variable once we deploy our contract on chain, because it has the visibility public.
Strings
Now…strings are special in Vyper. The number 100 defined inside our string refers to the amount of characters we can store in there. This only applies to strings. Here’s an example when you define a number or a boolean.
fav_num: public(uint256)
flag:public(bool)
The decorator
So what is this @external
that we’re using. Well above every function we can use a decorator (just like you’re used too in Python). In Vyper there 5 pre-defined decorator for functions:
public
functions can be called internally and via transactions,
external
functions can only be called via transactions and not internally,
internal
functions can only be called within the contract or its derivatives and not via transactions,
view
functions are read-only and do not modify the state of the blockchain,
and pure
functions are a special case where they promise not to modify or read the contract's state.
In our case we’re using the @external
for our constructor, but what is a constructor. Next chapter.
Constructors in Vyper
If you’re coming from Solidity, you’re familiar with constructors, just the syntax is different.
For newcomers, the constructor is a special function that only runs when a contract is being deployed. That’s it. After that no one can call it.
Constructors are typically handy for setting the owner of the contract, or pre-defining a token supply and a bunch of other stuff.
A constructor is always typed like this:
def __init__():
...
The ‘self’ keyword
You’ve seen this before in Python. Self refers to the instance of the class. Over here it refers to the contract. So when setting the value of a global or state variable, we write it out like this:
self.greet = "Hello World"
Cool now lets compile and deploy.
Create an account
When we deploy, we need to pass our private keys
. There are a couple of ways to do it:
Pass your private keys through the terminal - Only do this with dummy accounts
Use a
.env
file - Only use this for dev environmentsEncrypt your key through the terminal - Best practise
Now, the risk with a .env
file is that you might commit it to Github and then your private key
becomes public. We don’t want that.
Best practise is to pass it through the terminal and encrypt it. Ape allows this natively, so we’re going to use this method. Run the following command to get a list of accounts
ape accounts list
run the following command to create a new account
ape accounts import <NAME>
So as an example
ape accounts import scrollsepolia
You will be prompted to pass your private key and then a password. Don’t forget this password (duhh). Run ape accounts -list
to double check your account got added.
Compile and Deploy
First we need to compile our contract. Make sure you have the ape-vyper plugin installed. Run:
ape compile
This generate a file in the .build necessary to deploy our contract
Deploy script
Now we can deploy directly from the CLI, but it’s better to do it with a script. We have more control and it’s best practise.
from ape import accounts, project
def main():
# Initialize deployer account and print balance
dev_account = accounts.load("scrollsep")
print(f'The account balance is: {dev_account.balance / 1e18} ETH')
# Deploy the smart contract and print a message
kw = {
'type': 0
}
dev_account.deploy(project.hello, **kw)
print("Contract deployed!")
The only thing I want to highlight, is this part
kw = {
'type': 0
}
We’re passing a keyword argument with type:0. Now…there is a reason. For some chains running it like this will work:
dev_account.deploy(project.hello)
So without the keyword argument. The reason we’re passing the kw
, is due to the fact that some chains do not support EIP 1559 transactions. There are different type of transactions. If you’re curious, click here. If you believe me, go ahead and continue.
Run the following command to deploy your smart contract
ape run scripts/deploy.py --network <RPC URL>
Here’s my example deploying it to Scroll Sepolia test-net
ape run scripts/deploy.py --network https://sepolia-rpc.scroll.io/
Now we could further optimise, by preventing to pass an rpc-url directly. We’re going to do this in the next article.
You will be asked if you want to sign the transaction, say yes. Then pass your password to unlock the account and you will be asked if you want to leave your account unlocked. I always say no.
Done!
Interact with your smart contract
We deployed our smart contract. Great. Now we can use web3.py to interact with our smart contract. Go ahead and create a file called hello_
contract.py
. This is the full code of our contract:
# Importing necessary libraries
import json # Importing the json library for handling JSON data
from web3 import Web3 # Importing Web3 from the web3.py library
# Specify the URL of the blockchain node you want to connect to
rpc_url = "https://sepolia-rpc.scroll.io/"
# Creating a Web3 instance and connecting to the Ethereum blockchain using the specified node
web3 = Web3(Web3.HTTPProvider(rpc_url))
# ABI (Application Binary Interface) for the smart contract
# This is a JSON string that describes how to interact with the contract
abi = json.loads('[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"greet","outputs":[{"name":"","type":"string"}],"stateMutability":"view","type":"function"}]')
# The Ethereum address of the deployed smart contract
address = "0x268d4A9c3Ba20C938B9f3Db634786aB93628A7Cb"
# Creating a contract object using the Web3 instance, contract address, and ABI
# This object will be used to interact with the smart contract
contract = web3.eth.contract(address=address, abi=abi)
# Calling the 'greet' function of the contract
# Since 'greet' is a public variable, it can be accessed as a function
# This call does not make any changes to the blockchain, it just reads data
greet_value = contract.functions.greet().call()
# Printing the value obtained from the smart contract
print("Greet value:", greet_value)
In your directory .build
inside a .json
file you can find the abi. My contract is named hello
so the file is named hello.json
.
This json has one huge object (everything between squarely brackets), you need only the abi part. This is what mine looks like:
abi = json.loads('[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"greet","outputs":[{"name":"","type":"string"}],"stateMutability":"view","type":"function"}]')
Check out the Github, if you want to know what exact part I took from my hello.json
.
In your terminal, you can find your smart contract address. You will need to pass that here
# The Ethereum address of the deployed smart contract
address = "0x268d4A9c3Ba20C938B9f3Db634786aB93628A7Cb"
Our variable greet
has a visibility of public
, so it’s call-able
, like this:
greet_value = contract.functions.greet().call()
To run this script, in your terminal run
python hello_contract.py
The output should look something like this
Greet value: Hello World
Bonus
Now…if you’re like me and your terminal is sometimes just a bit of a mess and you just want your contract address written somewhere. Then I got some good news. You can adjust your deploy script to look like this
from ape import accounts, project
from datetime import datetime
def main():
contract_name = "hello"
# Initialize deployer account and print balance
dev_account = accounts.load("scrollsep")
print(f'The account balance is: {dev_account.balance / 1e18} ETH')
# Deploy the smart contract and print a message
kw = {
'type': 0
}
contract_class = getattr(project, contract_name)
deployed_contract = dev_account.deploy(contract_class, **kw)
print("Contract deployed!")
contract_address = deployed_contract.address
# Get the current timestamp
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# Write the contract name, address, and timestamp to a file
with open("deployed_contracts.txt", "a") as file:
file.write(f"{current_time}: {contract_name} - Address: {contract_address}\n")
print(f"{contract_name} address {contract_address} recorded with timestamp.")
We define the contract name first at the top of the deploy script
contract_name = "hello"
Then you need to use the getattr
function to dynamically access an attribute (in this case, the contract) based on a string variable.
contract_class = getattr(project, contract_name)
Now we can deploy the contract using the following line
deployed_contract = dev_account.deploy(contract_class, **kw)
Here’s where we write the name, date and contract address to a text file
# Write the contract name, address, and timestamp to a file
with open("deployed_contracts.txt", "a") as file:
file.write(f"{current_time}: {contract_name} - Address: {contract_address}\n")
Done!
Dang…you’ve created a virtual environment, installed & configured Ape, wrote a smart contract in Vyper and interacted with it using web3.py. Mission accomplished!
Now that we have this fundamental basis, we can use this to build more awesome stuff. Stay tuned for the next one!