From Zero to Hero: Learning Web3 with Infura and Python

From Zero to Hero: Learning Web3 with Infura and Python

·

10 min read

Web3, blockchain technology, and cryptocurrency are all fascinating topics. The technology, applications, ecosystem, and the impact on society are all moving at incredible speeds. In this article, we will talk about learning web3 development from the point of view of a seasoned developer who is a total web3 newbie. We will look at the prerequisites for web3 development, use Python to access the blockchain via Infura, web3's top API service, and go over a simple project for managing a wallet.

How to get started

Even though I've been coding since the late 1990's, I am truly a complete beginner in the web3 world. I'm no expert, so I won't try to explain the fundamentals. There are a lot of great content guides and tutorials out there. I suggest starting with the Infura documentation, which is very comprehensive as well as comprehensible.

There is also a community discord if you prefer a more interactive style of learning.

Anyway, let's start with some basics. We need an Infura account, a wallet to store our cryptocurrency, and we need some money to play with.

Opening an account with Infura

Infura is a provider of blockchain APIs and developer tools. This means that if you want to access the blockchain, you don't need to run a node yourself. Instead, you just access a friendly API, and Infura does all the heavy lifting for you. Infura is free and totally secure, as it doesn't store your private keys and doesn't have the ability to modify your transactions or replay them multiple times.

You can open an account here for free, no credit card required.

Creating an Infura project

A project is where things become interesting. Each project has an API key, which identifies it and allows you to use Infura. Follow the instructions here.

Setting up a crypto wallet

The next piece of the puzzle is a crypto wallet. This is where the rubber meets the road. In the blockchain environment, crypto wallets hold balances that are fully controlled by a set of digital keys. There is no such thing as personal ownership of an account. Each account has a public key - which is visible in the blockchain - and a private key that controls the account. Whoever holds a private key has total control of an account. You may also manage multiple accounts as a set of private keys. Wallets give you a secure way to manage your accounts/private keys as well as other benefits such as convenience, portability, and compatibility.

Infura recommends MetaMask. You can install MetaMask as a browser extension.

Great, we have a crypto wallet. Now let's talk money!

Getting some money

The blockchain is not free. Crypto economics are way above my pay grade, but in simple terms, each transaction costs money. If you want to play on the blockchain, you need funds. Luckily for developers, there are test networks that let you get test money for free. You can't exchange it for real money, but you can use it for developing and testing web3 applications.

Speaking of which, there are different types of networks. Here, we will focus on the Ethereum blockchain.

In this project, I used the testnet Sepolia. You can get test ETH from Sepolia by going to a faucet site. (ETH is Ethereum's native cryptocurrency. You use it to pay for transactions on the Ethereum network. Test ETH is a necessity for Ethereum development.)

A faucet site can transfer a small amount of testnet ETH to your wallet. Some faucets will require you to do some mining to earn your money, and some will gift you some money periodically. I had success with the ConsenSys Sepolia faucet which gives out .5 Sepolia ETH per day to an address.

OK. We covered all the basics. Let's check out the Infura API.

Accessing the Infura API

Infura provides a JSON-RPC API over HTTPS (REST) and WebSockets. It has several categories and you can read all about them here.

Additionally, the Infura API supports multiple different networks. Each network has its own https endpoint that you use as a base URL when accessing the API.

Here are the endpoints for Ethereum:

Mainnet

Goerli

Sepolia

Just to test that we can access the API, let's get our wallet balance using curl.

I stored the Infura API key and API key secret in environment variables called simply:

INFURA_API_KEY and INFURA_API_KEY_SECRET. I also stored the public key of the MetaMask wallet in an environment variable called SEPOLIA_ACCOUNT.

The curl command is:

$ curl --user ${INFURA_API_KEY}:${INFURA_API_KEY_SECRET} \
       -X POST \
       -H "Content-Type: application/json" \
       --data '{"jsonrpc":"2.0","method":"eth_getBalance","params":["'"${SEPOLIA_ACCOUNT}"'","latest"],"id":1}' \
  https://sepolia.infura.io/v3/${INFURA_API_KEY}

{"jsonrpc":"2.0","id":1,"result":"0x1d7e6e62f1523600"}

As you can see, I have a HUGE balance of 0x1d7e6e62f1523600!!!! But, no need to get too excited; the balance units are Wei. One ETH is equal to 10^18 Wei. If we crunch the numbers, we can see that I have a little more than 2 ETH in my account. Of course, this is all testnet money.

Note that I didn't have to use my account's private key to check my balance. Anyone can check the balance of any account in the blockchain. The balance of any account is not sensitive information. However, the identity of the account and the person that holds the private key is sensitive and confidential.

All right… we had our fun with hitting the Infura API directly. Let's do some coding.

Web3 development with Python

The web3 ecosystem supports many programming languages. Infura APIs can be accessed from popular libraries in JavaScript (web3.js and ethers.js), Golang (github.com/ethereum/go-ethereum) and Python (web3.py).

Choose your weapon —web3.py

Although most of coding these days is in JavaScript/Node.js and Ruby, Python is great when learning a new topic. The web3.py library seems powerful, mature, and well-documented. So I decided to go with it.

Choose your target — wallet manager

The web3 world can be overwhelming: transactions, smart contracts, IPFS, DAO (decentralized autonomous organizations), defi (decentralized finance), NFTs. I decided to pick a simple concept of a wallet manager for this web3 test project. The wallet manager is kind of a "hello web3 world" project because all it does is get your balance and send some money to a destination account. Since I got my money from a Sepolia faucet, I decided to give back by sending it some of the funds. Let's check out the code.

The web3-test dapp (decentralized app)

The code is available on Github here. (Special thanks to the-gigi!)

I used Poetry to scaffold the application. The README provides step-by-step instructions to set up everything. Before we dive into the code, let's run the program and see what happens:

$ poetry run python main.py

balance before transaction: 2.1252574454
send 20,000 gwei to 0xea4d57b2dd421c5bfc893d126ec15bc42b3d0bcd (Sepolia faucet account)
balance after transaction: 2.125184945399832

As you can see, my balance was initially a little over 2 testnet ETH. Then, I sent 20,000 Gwei (which is 20 billion Wei) to the Sepolia faucet account that I got the money from in the first place. As you can see, it barely made a dent in my balance. This just shows what a tiny unit the Wei is.

The code is pretty simple. There is just a single file called main.py. The file contains a main() function and a WalletManager class. Let's start with the main() function, which is the entry point to the program.

The main() function takes no command-line arguments or configuration files. Everything is hard-coded for simplicity. First, the function instantiates the WalletManager class, then it defines the public key of the Sepolia faucet account. Now, we get to action. The function obtains the balance of the wallet by invoking the get_balance() method of WalletManager, then it passes the requested unit (ether), and displays it on screen. Next, the function invokes the send_eth() method to send 20,000 Gwei to the target account. Finally, it gets and displays the balance again after the money has been sent.

def main():
    wm = WalletManager()

    sepolia_faucet_account = wm.w3.toChecksumAddress('0xea4d57b2dd421c5bfc893d126ec15bc42b3d0bcd')

    balance = str(wm.get_balance('ether'))
    print(f'balance before transaction: {balance}')

    print(f'send 20,000 gwei to {sepolia_faucet_account} (Sepolia faucet account)')
    wm.send_eth(sepolia_faucet_account, 20000, 'gwei')

    balance = str(wm.get_balance('ether'))
    print(f'balance after transaction: {balance}')

if __name__ == '__main__':
    main()

Let's look at the WalletManager class. It has four methods:

  1. __init__()

  2. __create_web3_instance()

  3. get_balance()

  4. sent_eth()

Method 1: __init__()

Let's look at them one by one. The __init__() method, which is the constructor, first calls the __create_web3_instance() method and stores the result in a variable called w3. Then __init__() extracts a couple of environment variables and stores them. It continues to compute a couple of gas fees (which is the fuel the blockchain is running on) and the rewards that are given to the people that validate transactions. If you want to know more about gas and fees, read this.

It also stores the chain ID, which identifies the Sepolia testnet (in this case). We will need this ID later when sending transactions to the Sepolia testnet.

import base64
import os
import web3

class WalletManager:
    def __init__(self):
        self.w3 = self.__create_web3_instance()
        self.account = os.environ['SEPOLIA_ACCOUNT']
        self.account_private_key = os.environ['METAMASK_PRIVATE_KEY']
        self.max_fee_per_gas = self.w3.toWei('250', 'gwei')
        self.max_priority_fee_per_gas = self.w3.eth.max_priority_fee
        self.chain_id = self.w3.eth.chain_id

Method 2: __create_web3_instance()

Let's see what happens inside the __create_web3_instance() method. __create_web3_instance() is a static method because it doesn't need any information from the WalletManager class. It gets the Infura API key and API key secret from the environment, and then it encodes them into a basic authentication token. It prepares the proper endpoint for our project on the Sepolia testnet, and then it instantiates a web3 object from the web3 library with all the information. This object will allow us to invoke the Infura API via a convenient Python interface (instead of constructing JSON-RPC requests and parsing the results).

@staticmethod
def __create_web3_instance():
    infura_api_key = os.environ['INFURA_API_KEY']
    infura_api_key_secret = os.environ['INFURA_API_KEY_SECRET']
    data = f'{infura_api_key}:{infura_api_key_secret}'.encode('ascii')
    basic_auth_token = base64.b64encode(data).strip().decode('utf-8')

    infura_sepolia_endpoint = f'https://sepolia.infura.io/v3/{infura_api_key}'

    headers = dict(Authorization=f'Basic {basic_auth_token}')
    return web3.Web3(web3.HTTPProvider(infura_sepolia_endpoint, request_kwargs=dict(headers=headers)))

Method 3: get_balance()

Alright, next is the get_balance() method.

It is an extremely simple method. It just invokes the w3.eth.get_balance() method of the web3 object and passes our account. The eth.get_balance() always returns the result in Wei, which is often too small. Our method provides us with the option to convert the result to another denomination like Gwei or Ether. It does it by invoking the w3.fromWei() method provided again by our web3 instance. Note that we didn't have to use our private key to check the balance.

balance = self.w3.eth.get_balance(selpytf.account)
if unit != 'wei':
    return self.w3.fromWei(balance, unit)

Method 4: send_eth()

Last but not least is the send_eth() method. There is a lot going on here, so let's break it into multiple blocks.

First, send_eth() converts the amount to send to Wei (if necessary), and then it gets the transaction count of the account and stores it as nonce. The nonce allows us to overwrite pending transactions if needed.

def send_eth(self, target_account, amount, unit='wei'):
    if unit != 'wei':
        amount = self.w3.toWei(amount, unit)

    nonce = self.w3.eth.get_transaction_count(self.account)

Next, it constructs a transaction object. The most important fields are the from (the wallet's account), the to (the transaction's recipient) and the value (how much to send). Then, there are the fields that determine how much gas to pay. (The more gas, the more likely that validators will include the transaction,) The chainId identifies the network to run this transaction against and a couple of administrative fields (empty data and type).

tx = {'nonce': nonce,
      'maxFeePerGas': self.max_fee_per_gas,
      'maxPriorityFeePerGas': self.max_priority_fee_per_gas,
      'from': self.account,
      'to': target_account,
      'value': amount,
      'data': b'',
      'type': 2,
      'chainId': self.chain_id}
tx['gas'] = self.w3.eth.estimate_gas(tx)

OK. We have a transaction, so can we send it? Not so fast. First, we need to sign it with our private key. This is what prevents other people from transferring money out of our account. Signing the transaction with the private key allows validators to confirm that the private key corresponds to the public key of the account.

signed_tx = self.w3.eth.account.sign_transaction(tx, self.account_private_key)

Now we can send the transaction as a raw transaction. This means that Infura never sees our private key, and it can't change our transaction or direct our transfer to a different account. This is the magic of the blockchain in action. After sending the transaction, we get back a hash code and wait for the transaction to complete. If the status of the result is 1, then all is well. If not, the code raises an exception.

tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction)

result = self.w3.eth.wait_for_transaction_receipt(tx_hash)
if result['status'] != 1:
    raise RuntimeError('transaction failed: {tx_hash}')

That's all it takes to interact in a very basic, yet secure way with the blockchain.

Conclusion: start your web3 journey with Infura

Diving headfirst into the world of web3 can be intimidating, even for an experienced programmer. We've learned a lot; mostly, we've learned that we have a lot more to learn! Infura made it easy by providing a solid API, great guidance, and strong integration with other components of the ecosystem, like MetaMask and the web3.py library. If you are in a similar position and want to learn web3 development - or even want to start a web3 career - I highly recommend starting with Infura. See where the journey takes you!