Latest news about Bitcoin and all cryptocurrencies. Your daily crypto news habit.
Itâs one thing to get a basic smart contract up on Ethereumâââjust google âERC20 Token Tutorialâ youâll find plenty of information on how to do it. Interacting with a contract programmatically is another thing entirely, and if youâre a Python coder, then tutorials are scarce.
One, by my count, and itâs soooo 2017.
Fortunately for us, Version 4 of Web3.py has just been released, which means itâs now easier than ever to run a python script and watch as magical things happen on the blockchain. Spooky.
A big shout out Piper Merriam, Jason Carver and all the others whoâve worked so on hard on Web3.py to make life easy for the rest of usâââat Sempo weâre using Ethereum to make Disaster Response more transparent, and itâs only really possible thanks to Web3.py.
Getting Set Up
First we get set up, making sure we have the relevant python libraries installed and so-forth.
Python libraries everywhere, but what are they for?
There are plenty of python libraries related to Ethereum out there, but there are two that come up a lot when people talk about Ethereum: Web3.py and Pyethereum. At first glance itâs not obvious which one you should use for what.
Pyethereum
A Python implementation of the Ethereum Virtual Machine (EVM). The EVM, in turn is the part of the Ethereum protocol that actually runs the code in Smart Contracts and determines their outputs. So if you wanted to run an Ethereum node in Python, Pyethereum is a good place to start.
Even if youâre perfectly happy running your Smart Contracts without running your own node, Pyethereum is still a good library to have aroundâ it contains a bunch of functions that do useful things like calculate a userâs address from their private key and so on.
Web3.py
A library for actually interacting with the Ethereum Blockchain. Weâre talking about things like transferring Ether between accounts, publishing Smart Contracts and triggering functions attached existing Smart Contracts. Itâs inspired by the popular javascript library Web3.js, and itâll be the main library we use in this tutorial.
Ok, less talk, more do!
At first I tried working with a version Python 3.5, but at runtime I ran into issues, apparently caused by Pythonâs type hinting. Creating a Virtual Environment based off Python 3.6 solved this, so Iâd recommend doing the same.
Go ahead and pip-install web3 (make sure you get version 4)Â .
Unless you enjoy spending real money for the sake of it, youâll need a wallet on an Ethereum TestNet such as Ropsten with plenty of Ether to play with. An easy way to do this is download the Metamask extension for Chrome, and create a new account from there.
Make sure you also select the âRopsten Test Netâ on the left
Even if you have an existing wallet with real Ether in it, Iâd highly recommend creating a new one for development purposes. Weâre going to be doing some relatively reckless things with private keys, so it canât be a problem if they accidentally become public (public-private keys?)
Getting test Ether for your newly created wallet is easy: simply go to faucet.metamask.io and click on ârequest 1 ether from faucetâ. This should be plenty for what weâll be doing.
Finally, because weâre going to be working with the Ropsten TestNet without hosting our own node, we need a provider that we can connect the Blockchain through. Infura.io works well for this, so go and create a free account over there. Note down your provider url for the Ropsten TestNet (looks something like https://ropsten.infura.io/FE2Gfedcm3tfed3).
Deploying a Smart Contract
Using Python to deploy a Smart Contract without running your own node is pretty difficult, so weâre going to cheat on this step. For many Smart Contract use-cases, you only need to do it once anyway.
As I mentioned earlier, thereâs a million guides on how to deploy an ERC20 contract, so weâre going to deploy a little different (and conveniently shorter).
Q: Who loves sharing their opinions on the internet?
Everyone?
Good answer. The following Smart Contract, which Iâve named âSoap Boxâ allows anyone to broadcast any opinion they want to the blockchain, where itâll be viewable for the rest of eternity (give or take).
Thereâs a catch though: Only addresses that have paid the requisite 0.02 Ether fee will be able to broadcast their opinion. Doesnât sound very fair, but there you have it.
Remix, Ethereumâs online code editor is excellent, so create a new file over there and paste in the following code. Itâs written in Solidity (the programming language of Smart Contracts). It doesnât matter if the code doesnât make too much senseâââweâll be going through the relevant parts in more detail later, but at the end of the day this is a Python tutorial.
pragma solidity ^0.4.0;contract SoapBox {
// Our 'dict' of addresses that are approved to share opinions mapping (address => bool) approvedSoapboxer; string opinion; // Our event to announce an opinion on the blockchain event OpinionBroadcast(address _soapboxer, string _opinion);
// This is a constructor function, so its name has to match the contract function SoapBox() public { } // Because this function is 'payable' it will be called when ether is sent to the contract address. function() public payable{ // msg is a special variable that contains information about the transaction if (msg.value > 20000000000000000) { //if the value sent greater than 0.02 ether (in Wei) // then add the sender's address to approvedSoapboxer approvedSoapboxer[msg.sender] = true; } } // Our read-only function that checks whether the specified address is approved to post opinions. function isApproved(address _soapboxer) public view returns (bool approved) { return approvedSoapboxer[_soapboxer]; } // Read-only function that returns the current opinion function getCurrentOpinion() public view returns(string) { return opinion; }
//Our function that modifies the state on the blockchain function broadcastOpinion(string _opinion) public returns (bool success) {
// Looking up the address of the sender will return false if the sender isn't approved if (approvedSoapboxer[msg.sender]) { opinion = _opinion; emit OpinionBroadcast(msg.sender, opinion); return true; } else { return false; } }
}
Hereâs where Metamask becomes really useful: If you click on the ârunâ tab on the top right of the remix window and select âInjected Web3â under the âEnvironmentâ dropdown, the âAccountâ dropdown should populate with address of the account you created earlier in MetaMask. If not, just refresh the browser.
Next click on âCreateâ. Metamask should bring up a popup asking you to confirm the transaction. If not, just open the Metamask extension and do it there:
Youâll receive a message at the bottom of the Remix console letting you know that the creation of the contract is pending. Click on the link to view its status on Etherscan. If you refresh and the âToâ field is populated with the Contract address, the contract has successfully deployed.
Once youâve noted down the contract address, itâs time for us to start interacting with the contract via Web3.py
In my mind are four (and a half) ways you can interact with Ethereum Smart Contracts. The last two (and a half) often get lumped together, but the differences are important. Weâve already seen the first oneâââdeploying a Smart Contract on the Blockchain. Now weâll cover the rest on python:
- Sending Ether to a Contract: Self explanatory reallyâââsending ether from a wallet to the address of the smart contract. Hopefully in exchange for something useful
- Calling a function: Executing a read-only function of a Smart Contract to get some information (such as an addressâs balance)
- Transacting with a function: Executing a function of a Smart Contract that makes changes to the state of the blockchain.
- Viewing Events: Viewing information published onto the blockchain because of previous transactions with functions.
Send Ether to a Contract
Some (but not all) Smart Contracts include a âpayableâ function. These functions are triggered if you send Ether to the contractâs address. A classic use case for this is an ICO: send ether to a contract and return youâll be issued with tokens.
First weâll start off with our imports and create a new web3 object thatâs connected to the Ropsten TestNet via Infura.io.
import timefrom web3 import Web3, HTTPProvidercontract_address = [YOUR CONTRACT ADDRESS]wallet_private_key = [YOUR TEST WALLET PRIVATE KEY]wallet_address = [YOUR WALLET ADDRESS]w3 = Web3(HTTPProvider([YOUR INFURA URL]))w3.eth.enable_unaudited_features()
You can find your wallet private key from the menu next to your Account Name in Metamask. Because weâre using some features of Web3.py that havenât been fully audited for security, we need to call w3.eth.enable_unaudited_features() to acknowledge that weâre aware that bad things might happen. I told you we were doing some risky things with private keys!
Now weâll write a function that sends ether from our wallet to the contract:
def send_ether_to_contract(amount_in_ether): amount_in_wei = amount_in_ether * 1000000000000000000; nonce = w3.eth.getTransactionCount(wallet_address) txn_dict = {'to': contract_address,'value': amount_in_wei,'gas': 2000000,'gasPrice': w3.toWei('40', 'gwei'),'nonce': nonce,'chainId': 3 } signed_txn = w3.eth.account.signTransaction(txn_dict, wallet_private_key) txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction) txn_receipt = Nonecount = 0while txn_receipt is None and (count < 30): txn_receipt = w3.eth.getTransactionReceipt(txn_hash) print(txn_receipt) time.sleep(10)if txn_receipt is None:return {'status': 'failed', 'error': 'timeout'}return {'status': 'added', 'txn_receipt': txn_receipt}
First letâs go over the transaction dictionary txn_dictâ it contains most of the information required to define the transaction we send to the Smart Contraction.
- To: Where weâre sending the ether to (in this case the smart contract)
- Value: How much weâre sending in Wei
- Gas: Gas is a measure of the computational effort that goes into executing a transaction on Ethereum. In this case weâre specifying an upper limit on how much gas weâre willing to go through to execute this transaction.*
- Gas Price: How much weâre willing to pay (in Wei) per unit of Gas.
- Nonce: This is an address nonce rather than the more commonly referred to Proof of Work. Itâs simply a count of how many previous transactions the sending address has made, and is used to prevent double spending.
- Chain ID: Each Ethereum Network has its own chain IDâââthe main net has an ID of 1, while Ropstenâs is 3. You can find a longer list here.
*A quick note on the gas limit: Thereâs functions that allow you to estimate how much gas a transaction will use. However, I find the best way to choose the limit is to work out how much your willing to pay in ether before youâd just rather have the transaction fail, and go with that.
Once weâve defined the important parts of our transaction, weâll sign it using our walletâs Private Key. Then itâs ready to be sent to the network, which weâll do with the sendRawTransaction method.
Our transaction wonât actually be completed until a miner decides to include it in a block. In general, how much you offer to pay for each unit Gas (remember our gas price parameter) determines how quickly a node will decide to include your transaction in a block (if at all).
https://ethgasstation.info/ is good place to work out how long youâll be waiting for your transaction to be included in a block.
This time delay means that transactions are asynchronous. When we call the sendRawTransaction, weâre immediately given transactionâs unique hash. You can use this hash at any time to query whether your transaction has been included in a block or not. Weâll know that the transaction has be added to the blockchain if and only if weâre able to get a transaction receipt (because all good purchases come with receipts right?). Thatâs why we create the loop to regularly check whether weâve got a receipt:
txn_receipt = Nonecount = 0
while txn_receipt is None and (count < 30): txn_receipt = w3.eth.getTransactionReceipt(txn_hash) print(txn_receipt) time.sleep(10)
Itâs worth noting that a transaction can be added to the blockchain and still fail for any number of reasons, such as not having enough Gas.
So thatâs the Python code for sending ether to the contract. Letâs quickly review the payable function that we wrote in Solidity:
function() public payable{ if (msg.value >= 20000000000000000) { approvedSoapboxer[msg.sender] = true; } }
Msg is a special variable in Smart Contracts that contains information about the transaction that was sent to the Smart Contract. In this case, weâre using msg.value, which gives the amount of Ether that was sent in the transaction (in Wei rather than raw Ether). Likewise, msg.sender gives the address of the wallet that made the transactionâââif enough Ether has been sent, weâll add this to the dictionary of approved accounts.
Go ahead an run the send_ether_to_contract function. Hopefully youâll get a receipt back. You can also check whether the transaction went through by looking up your wallet address on the Ropsten Network section of Etherscan. Weâll be able to get a bit more information in Python in the next section.
Calling a function
Weâve just sent some amount of Ether to our Smart Contract, so it makes sense that we want to check whether the our wallet address has now been approved to share opinions. For this weâve defined the following function in our Smart Contract:
function isApproved(address _soapboxer) public view returns (bool approved) { return approvedSoapboxer[_soapboxer]; }
Compared to python, thereâs a lot of extra stuff plastered around this function such as declaring type (address and bool). At its core though, this function simply takes an address (the _soapboxer parameter), looks up the corresponding approval boolean in what is effectively (but not quite) a hash table/python dict and returns that value.
When you call a smart contract function, the Ethereum node will calculate the result, and return it to you. Hereâs where things get a little bit complex: calls are read-only, meaning they donât make any changes to the blockchain. If the above function contained a line of code to record number time an address had been checked for approval:
approvedCheckedCount[_soapboxer] = approvedCheckedCount[_soapboxer] + 1
Then when the function was called, the node would calculate the new value of approvedCheckedCount, but discard it once a result had been returned.
In exchange for being read-only, function calls donât cost you any ether to run, so you can happily check whether an account as been approved without worrying about costs.
Letâs jump back up to the top of our python file and add some more set-up code.
import contract_abi
contract = w3.eth.contract(address = contract_address, abi = contract_abi.abi)
Youâll need to create another python file named contract_abi. This will contain a big JSON string of information that Python needs to interact with the functions we defined in our Smart Contract, called the Application Binary Interface (ABI). You can find the ABIâs JSON string for your smart contract in Remix:
- Click on the âCompileâ tab
- Click on âDetailsââââa modal should appear with a lot of information
- Scroll Down to the ABI section and click on the âCopy to clipboardâ Icon.
Paste the copied string into your âcontract_abi.pyâ file, which should look something like this:
abi = """[ { A BIG LIST OF ABI INFO SPREAD ACROSS MULTIPLE DICTS }]"""
The other line we added to our main python file now takes this ABI JSON string and uses it to set up a contract object. If you explore contract, youâll notice that it contains a functions attribute that includes the three functions we created in our Smart Contract.
Now weâll create a python function that calls the Smart Contractâs isApproved function to check whether a specified address is approved to share opinions.
def check_whether_address_is_approved(address):
return contract.functions.isApproved(address).call()
Well that was short.
You can now use this to check wether your wallet address is approved. If you ran the send_ether_to_contract function earlier and sent a sufficient amount of Ether, hopefully youâll get back âtrueâ.
Transacting with a function
Weâre up to the final major interaction with our Smart Contractâââbroadcasting an opinion. Once again, letâs review our Solidity Code:
function broadcastOpinion(string _opinion) public returns (bool success) { if (approvedSoapboxer[msg.sender]) { opinion = _opinion; emit OpinionBroadcast(msg.sender, opinion); return true;
} else { return false; } }
Nothing too new here: we take the incoming _opinion parameter and use it to set global variable opinion. (Which can intern by queried by a getter function, if you so desire). Thereâs one line thatâs a bit different:
emit OpinionBroadcast(msg.sender, opinion)
Weâll cover that shortly.When you interact with a Smart Contractâs function via a transaction, any changes the function makes to the state of Smart Contract are published on the blockchain. In exchange for this privilege, youâll have to pay the miners some (hopefully small) amount of Ether. Python time:
def broadcast_an_opinion(covfefe): nonce = w3.eth.getTransactionCount(wallet_address) txn_dict = contract.functions.broadcastOpinion(covfefe).buildTransaction({'chainId': 3,'gas': 140000,'gasPrice': w3.toWei('40', 'gwei'),'nonce': nonce, }) signed_txn = w3.eth.account.signTransaction(txn_dict, private_key=wallet_private_key) result = w3.eth.sendRawTransaction(signed_txn.rawTransaction) tx_receipt = w3.eth.getTransactionReceipt(result) count = 0while tx_receipt is None and (count < 30): time.sleep(10) tx_receipt = w3.eth.getTransactionReceipt(result) print(tx_receipt)if tx_receipt is None:return {'status': 'failed', 'error': 'timeout'} processed_receipt = contract.events.OpinionBroadcast().processReceipt(tx_receipt) print(processed_receipt) output = "Address {} broadcasted the opinion: {}"\ .format(processed_receipt[0].args._soapboxer, processed_receipt[0].args._opinion) print(output)return {'status': 'added', 'processed_receipt': processed_receipt}
This is effectively the same process as the one used when sending Ether to the smart contract. Weâll create and sign a transaction and then send it to the network. Once again, the transaction is asynchronous, which means that regardless of what the function is told to return in the Solidity code, what youâll actually get back is always the transactionâs hash.
Given that transactions donât return any useful information in their own right, we need something else. This leads us to our last (half) way of interacting with Smart Contracts.
Events
I call Events a âhalfâ way of interacting with Smart Contracts because technically theyâre emitted by a transaction. Events are a Smart Contractâs way of recording things on a blockchain in an easy to read formâââtheyâre basically just a set of values that can be looked up using the receipt of a particular transaction. We defined one at the very top our smart contract:
event OpinionBroadcast(address _soapboxer, string _opinion);
Then, when we use the broadcastOpinion function, we use it to emit information to the blockchain.
Once the transaction has been added to a block, you can then use the transaction hash to query the blockchain for the specific values emitted by the OpinionBroadcast event. Thatâs the last bit of our python code in the function broadcast_an_opinion. Youâll notice that the information we asked to be emitted by the event is stored in the âargsâ attribute.
This event is very much public. In fact, anyone can easily use Etherscan or similar to view a log of all the events emitted by your Smart Contract.
Etherscan automatically detects âTransferâ events a lists them all. Nifty
If youâve made it this far, youâve earned the right to broadcast an opinion. Go ahead and run broadcast_an_opinion with your opinion of choice.
If everything has run smoothly, you should soon get back a processed receipt, and a printout from the OpinionBroadcast event that has been put onto the blockchain.
Nice.
Hereâs the complete python code:
import timefrom web3 import Web3, HTTPProvidercontract_address = [YOUR CONTRACT ADDRESS]wallet_private_key = [YOUR TEST WALLET PRIVATE KEY]wallet_address = [YOUR WALLET ADDRESS]w3 = Web3(HTTPProvider([YOUR INFURA URL]))w3.eth.enable_unaudited_features()contract = w3.eth.contract(address = contract_address, abi = contract_abi.abi)def send_ether_to_contract(amount_in_ether): amount_in_wei = amount_in_ether * 1000000000000000000; nonce = w3.eth.getTransactionCount(wallet_address) txn_dict = {'to': contract_address,'value': amount_in_wei,'gas': 2000000,'gasPrice': w3.toWei('40', 'gwei'),'nonce': nonce,'chainId': 3 } signed_txn = w3.eth.account.signTransaction(txn_dict, wallet_private_key) txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction) txn_receipt = Nonecount = 0while txn_receipt is None and (count < 30): txn_receipt = w3.eth.getTransactionReceipt(txn_hash) print(txn_receipt) time.sleep(10)if txn_receipt is None:return {'status': 'failed', 'error': 'timeout'}return {'status': 'added', 'txn_receipt': txn_receipt}def check_whether_address_is_approved(address):return contract.functions.isApproved(address).call()def broadcast_an_opinion(covfefe): nonce = w3.eth.getTransactionCount(wallet_address) txn_dict = contract.functions.broadcastOpinion(covfefe).buildTransaction({'chainId': 3,'gas': 140000,'gasPrice': w3.toWei('40', 'gwei'),'nonce': nonce, }) signed_txn = w3.eth.account.signTransaction(txn_dict, private_key=wallet_private_key) result = w3.eth.sendRawTransaction(signed_txn.rawTransaction) tx_receipt = w3.eth.getTransactionReceipt(result) count = 0while tx_receipt is None and (count < 30): time.sleep(10) tx_receipt = w3.eth.getTransactionReceipt(result) print(tx_receipt)if tx_receipt is None:return {'status': 'failed', 'error': 'timeout'} processed_receipt = contract.events.OpinionBroadcast().processReceipt(tx_receipt) print(processed_receipt) output = "Address {} broadcasted the opinion: {}"\ .format(processed_receipt[0].args._soapboxer, processed_receipt[0].args._opinion) print(output)return {'status': 'added', 'processed_receipt': processed_receipt}if __name__ == "__main__": send_ether_to_contract(0.03) is_approved = check_whether_address_is_approved(wallet_address) print(is_approved) broadcast_an_opinion('Despite the Constant Negative Press')
Wrapping up
So that about covers it. As I mentioned, weâre not quite at the point yet where itâs easy to actually deploy Smart Contracts using python, but everything else is there. At Sempo, weâre using all of the technology that Iâve covered above to make Disaster Response more transparent.
If you have any thoughts or suggestions, please leave them in the comments and Iâll get back to you ASAP!
Ethereum Smart Contracts in Python: a comprehensive(ish) guide was originally published in Hacker Noon on Medium, where people are continuing the conversation by highlighting and responding to this story.
Disclaimer
The views and opinions expressed in this article are solely those of the authors and do not reflect the views of Bitcoin Insider. Every investment and trading move involves risk - this is especially true for cryptocurrencies given their volatility. We strongly advise our readers to conduct their own research when making a decision.