How to Send Money Using Python: A Web3.py Tutorial
Trigger warning: The following tutorial contains elements of explicit cryptography, peer-to-peer financial services, and other transgressive behaviors. The examples are meant only to illustrate the power and ease of blockchain for Python.
Hey all yall Pythoners out there!
I鈥檓 really into the Python community. Like so many, Python was my first programming language. The Python hackathon and meet-ups are awesome. And, as a bonus point,聽I love Monty Python!
I thought I鈥檇 join all these loves together with my current job: showing developers how powerful blockchain programming can be, and how easy the skills are to pick up.
This will be a tutorial walking Python developers through the basics of聽Web3.py, a blockchain (Ethereum) library. We鈥檒l do a lot of this from the Python interpreter. (In the next tutorial, we鈥檒l set up a proper directory but we鈥檙e keeping things easy for now!)
Note: For security reasons, we鈥檒l be sending our money over a test network. All of these same techniques can be used on the main Ethereum network.
Table of Contents
- Installation
- Setting up Connection
- Initialization
- Create an Account
- ENS accounts
- Send Money
Installation
We鈥檙e gonna use聽pip to install聽web3.py聽from our command line:
Code language: Python (python)$ pip3 install web3
For people with both Python 2 and 3 installed, you should check to see which version聽pip
聽command invokes. Some default to 2.7:
$ pip -V
pip 10.0.1 from /Library/Python/2.7/site-packages/pip-10.0.1-py2.7.egg/pip (python 2.7)
$ pip3 -V
pip 20.0.2 from /usr/local/lib/python3.7/site-packages/pip (python 3.7)
Code language: JavaScript (javascript)
Also, if you鈥檙e using virtualenv,聽here鈥檚 some documentation聽about setting up a clean environment for Web3.py
Great! We鈥檙e on our way.
Setting Up Our Connection
Blockchain systems are decentralized networks made up of people running individual dedicated P2P software or 鈥渘odes鈥. It鈥檚 similar to a torrent network: To interact with the network, you either have to host a node or use a service that hosts one for you.
Since this is a basic tutorial, we鈥檒l use a service. The most popular one is聽Infura.聽You can setup your own free account (instructions here) or you can use the Product ID below. It鈥檚 crucial you get a Project ID and API endpoint 鈥 it will be our API endpoint to the blockchain and the analytics dashboard is helpful.
Copy the Endpoint and聽be sure to prepend https:// to the address.
Once you have that, you鈥檙e ready to connect to the blockchain using Python!
Initialization
Let鈥檚 fire up our Python interpreter. This can vary depending on your Python installation, but is usually accomplished with running whatever keyword you typically put before a python file. For me on my Mac iTerm with both Pyton 2 and 3 installed, it鈥檚:
$ python3
Python 3.8.2
[Clang 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
Code language: JavaScript (javascript)
To check that all our setup is correct, please run your python interpreter and the following commands:
>>> from web3 import Web3, HTTPProvider
>>> import json
Code language: JavaScript (javascript)
The above command imports some of the main methods from聽web3.py
聽we鈥檙e going to use to connect to the blockchain as well as the ever-faithful, native聽json
聽library.
Next, we鈥檒l create an object,聽w3
, which we initialize with our Infura API endpoint ( prepended with https:// ). It will become the main way聽web3.py
聽works with the blockchain throughout the rest of the tutorial.
>>> w3 = Web3(Web3.HTTPProvider("https://rinkeby.infura.io/v3/8e4cd4b220fa42d3ac2acca966fd07fa"))
Code language: JavaScript (javascript)
Note: You need to add聽HTTPS://聽in front of your Infura API address, otherwise you鈥檒l get an error!
We also need to add in some middleware to help us work with Infura and the Rinkeby testnet:
>>> from web3.middleware import geth_poa_middleware
>>> w3.middleware_onion.inject(geth_poa_middleware, layer=0)
Code language: JavaScript (javascript)
Now, to see if all鈥檚 gone well, we run:
>>> w3.isConnected()
Code language: CSS (css)
If you get聽True
, congratulations! You鈥檙e connected to the blockchain!
If you get聽False
, a few things you can check:
- If you鈥檝e had to restart the interpreter, you have to re-import the libraries and re-initialize the variables
- Have you copied the Infura API key correctly?
- Have you installed聽
web3.py
聽and installed and imported Web3 and HTTPProvider library? - Have you prepended the API key with https:// ?
Create an Account
If we鈥檇 like to send money on the blockchain, we鈥檒l need an Ethereum account. Ethereum accounts are the main unit of identity on the Ethereum blockchain 鈥 an account鈥檚 address is how the user is identified on the network. Underpinning the account system is a decentralized identity protocol based on聽public key cryptography.聽Essentially, identity on a blockchain network is confirmed by authentication of digital signatures from a single private key (held in secret by a single user) by its public address counterpart (held by the entire network). While it has significant user experience hurdles, it does provide a fast, peer-to-peer authentication protocol.
Generating an account to use on the Ethereum network is super easy with聽web3.py.
Note: In the next few steps, I鈥檓 going to break a few rules of cryptography and security. 1) I鈥檓 going to generate a private key with inadequate entropy (randomness) and 2) I鈥檓 going to post a private key online. I鈥檓 not going to use this key beyond this tutorial鈥攊t鈥檚 just for educational purposes. You should always use proper private key management, like聽Geth聽or聽MetaMask,聽and never share your private key publicly.
>>> my_account = w3.eth.account.create('Nobody expects the Spanish Inquisition!')
>>> my_account._address
'0x5b580eB23Fca4f0936127335a92f722905286738'
>>> my_account._private_key
HexBytes('0x265434629c3d2e652550d62225adcb2813d3ac32c6e07c8c39b5cc1efbca18b3')
Code language: JavaScript (javascript)
The above command uses the string input to generate the聽my_account
聽object, which contains a private key (my_account._private_key
) and its associated Ethereum address (my_account._address
) . However, since this has been posted publicly, someone could generate and use the same private key. (Luckily, I鈥檓 just using it for this tutorial and only on a test blockchain network.)
For this reason, users typically delegate private key creation and management to software called clients (like聽Geth) or wallets (like聽MetaMask). These projects provide an incredibly secure way to generate and handle private keys for blockchain interactions.
ENS Accounts
Ethereum addresses are long hexadecimal numbers. They鈥檙e nearly impossible to type or remember, so the Ethereum community created the聽Ethereum Name System (ENS).聽It serves the same benefit of the Domain Name System, which replaces website server numbers (216.58.194.46) to human-readable names (google.com). Rather than a聽.com
聽domain, most ENS names use a聽.eth
聽domain.
For example, I have an Ethererum account at聽0x4d3dd8471a289E820Aa9E2Dc5f437C1b2E22F598
聽but I鈥檝e used ENS to map the more readable name聽coogan.eth
聽to the address. If you type those into apps or projects that support ENS (such as聽web3.py
!), it will substitute in the Ethereum hexadecimal address. Unfortunately, we won鈥檛 be able to use it for this tutorial as聽.eth
聽domain names only work on mainnet鈥ut maybe in the next tutorial!
Send Money
For this last section, we鈥檙e going to send some money from the account we just created to another Ethereum account. All from the python interpreter!
A reasonable response to sending money is, 鈥淚sn鈥檛 this just internet funny money that鈥檚 always fluctuating with a downward trajectory?鈥 And, yes, cryptocurrency USD prices are extremely volatile. This has caused hesitation from businesses 鈥 why would you accept a currency with an uncertain price?
However, there鈥檚 a novel workaround built within the Ethereum ecosystem. Ethereum is called 鈥渢he World Computer鈥 because it鈥檚 a distributed system that allows developers to upload and execute their own code. Code uploaded to Ethereum in this way is called a聽smart contract.聽Once it鈥檚 uploaded to the network, it becomes a standalone entity, with its own address, memory storage and network access. Smart contracts have given rise to a rich Ethereum developer community that is both creative and tireless in addressing challenges, like the inherent price volatility of blockchains.
In an attempt to create a stable source of value for Ethereum, a group of developers wrote and uploaded code to Ethereum called聽Dai.聽It鈥檚 a digital token that is always worth about $1 USD. (The technical details to achieve this stability are fascinating but beyond the scope of this tutorial 鈥 if you鈥檇 like to read more about the mechanics聽you can find more here). Once we hold the Dai token, we can exchange it with other users for the same rate as US dollars. We鈥檒l do that now!
(Note: to get Dai to an account different from the one in the tutorial, you can mint your own on the testnet using聽this contract address,聽Metamask and your own account.聽Follow the steps in the tutorial here)
Contract Instantiation
First, to interface with code that鈥檚 been uploaded by a developer to Ethereum, we need to know what methods the uploaded code exposes.聽Web3.py
聽natively knows how to interface with the core Ethereum software, but needs guidance to interact with third-party code. We provide that guidance by providing Web3.py an聽Application Binary Interface (ABI). Similar to an聽Application Programming Interface (API),聽the ABI allows our machine to know what functions are available to us and what parameters those functions expect. ABIs are not available on the blockchain and are supplied by the developer on sites like Github or Etherscan.
Here鈥檚 the testnet Dai ABI we鈥檒l be using, please click and copy the entire code snippet:
Note: this code is聽really聽long, take special care to copy it fully!
abi = '[{"constant":true,"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"mint","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"}]'
Code language: JavaScript (javascript)
We need to parse it using聽json
:
>>> abi = json.loads(abi)
We also need to tell聽web3.py
聽where to find this code on the Ethereum network. We do so with the following code:
>>> address = '0xc3dbf84Abb494ce5199D5d4D815b10EC29529ff8'
Code language: JavaScript (javascript)
We then use the ABI and the address to instantiate a聽smart contract object.聽This will give us access to the functions exposed by the code:
>>> dai = w3.eth.contract(address=address, abi=abi)
To test we鈥檝e properly instantiated the contract, we鈥檒l call a function that tells us how much Dai is held by the contract:
>>> dai.functions.totalSupply().call()
10100000000000101001376883458034812485564519
Code language: CSS (css)
(The balance may be different by the time you run this)
Building the Transaction
To transfer the Dai from our Spanish Inquisition account (my_account
), we鈥檒l use the聽transfer
聽function from the Dai smart contract, shown below:
We can see we need to pass in two parameters to the contract:聽to
, which will be a hexadecimal Ethereum聽address
聽and聽value
, which is聽uint256
. Dealing with unsigned integers of 256 bits (uint256
) can be challenging for even seasoned developers. It鈥檚 a testament to unusual programming that has to be done at the smart contract level and frequently trips me up.聽Web3.py
聽has a method we can use to cast values from integer to the format required for this smart contract,聽toHex
. Let鈥檚 send 10 Dai, and since the amount we鈥檙e sending is below 16, we鈥檒l just put a聽0x
聽on the front of it. For聽address
, put the address to whom you鈥檇 like to send the Dai.
So our transaction currently looks like this:
transaction = dai.functions.transfer(TO_ADDRESS, 0x10)
These parameters are good for the Dai contract (we won鈥檛 get an error there), but we need more parameters for our transaction to run on the Ethereum network. Those values are聽chainId
,聽gas
聽and聽nonce
.
.buildTransaction({'chainId': 4,
'gas':70000,
'nonce': w3.eth.getTransactionCount(my_account._address)})
Code language: JavaScript (javascript)
- ChainId聽helps聽
web3.py
聽know to which network the transaction is being sent. Different networks have different quirks (as we saw when we installed the聽middleware
聽at the beginning for Rinkeby) and this helps聽web3.py
聽bundle the transaction correctly. Rinkeby鈥檚 network ID is聽4
,聽here鈥檚 a complete list of network IDs.. - Gas聽is the small payment you make to miners on the network to run your transaction. Many people are surprised by this, but the amount is small (our transaction will cost 0.00007000 ETH, for example,聽but is demarcated in a particular denomination called Gwei).聽
Gas
聽helps run the network in a decentralized and secure way. - Nonce聽is a global variable particular to each Ethereum account. It serves the same purpose as the number on the bottom of the check: it allows for proper ordering of payments from different accounts. It increments up one after each transaction sent.聽
Web3.py
聽has a method for finding the current聽nonce
聽of the address:聽w3.eth.getTransactionCount(ETHEREUM_ADDRESS)
.
We鈥檒l use the聽web3.py
聽method聽.buildTransaction
聽to incorporate these three variables into our transaction. I鈥檓 also adding in my friend鈥檚 Ethereum address and will send him聽10
聽Dai:
>>> transaction = dai.functions.transfer('0xafC2F2bBD4173311BE60A7f5d4103b098D2703e8', 0x10).buildTransaction({'chainId': 4, 'gas':70000, 'nonce': w3.eth.getTransactionCount('0x5b580eB23Fca4f0936127335a92f722905286738')})
Code language: JavaScript (javascript)
Signing and Sending Transaction
Now that we have our聽transaction
, we need to sign it with our private key. This is how the peer-to-peer protocol of Ethereum will know that it is this account that wants to send the money. To sign, we put the聽transaction
聽object and our聽my_account._private_key
聽into the following function:
>>> signed_txn = w3.eth.account.signTransaction(transaction, '0x265434629c3d2e652550d62225adcb2813d3ac32c6e07c8c39b5cc1efbca18b3')
Code language: JavaScript (javascript)
Note: You should never post your real private key online! This is being done for educational purposes only. Double Note: Until this account is drained of test ether or test dai, the above command will be valid for the Rinkeby network
With our signed transaction, all we need to do now is send it to the network through our Infura API endpoint. We do this through our聽w3
聽object with the following command:
>>> txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction)
If that goes through, congratulations!聽You鈥檝e just sent money using Python!
To find your transaction, you can print聽txn_hash
聽and take the string value to聽Etherscan for Rinkeby.聽Here鈥檚 the hash I have (yours will be different!):
>>> txn_hash
HexBytes('0xc5f98cbe6f1eaef16916b148e6c4ae926b11ab9dde750e188362745da39d560e')
Code language: JavaScript (javascript)
You can see it on the Ethereum testnet here.
As you can see, using聽web3.py
聽opens up all kinds of possibilities with your applications. In the next tutorial, I鈥檓 hoping to do something more built out with proper files and directories. For now, I just wanted to show you some of the incredible options blockchain can provide. I hope you found something interesting! The community is really eager to engage with new folks, be sure to reach out.
Thank you to Daniel Ellison for his feedback and comments, sometimes copied verbatim!