Coming from a Python background, I was initially a bit discouraged to discover a lack of tutorials for interacting with the Ethereum blockchain using Python. However, after reading (excellent) and taking a good look at the rich feature set of Ethereum’s own , I was determined to write something of my own. I wanted a clean way to compile, deploy and interact with a contract from Python. I ended up writing a convenience interface that collects and abstracts a number of web3.py methods so it can be easily imported into different Python modules. Nick Williams guide web3.py Let me preface this by saying a working knowledge of Python, Solidity, and the Ethereum blockchain are recommended. I also recommend you get your feet wet reading Nick’s aforementioned guide and having a poke around web3.py. Another great resource is this of the Python / Ethereum ecosystem summarizing the amazing work by , and others into this space. overview Piper Merriam Jason Carver Before we get stuck in, there are a few requirements you’ll need to set up on your system first: node/npm python2/pip (needed to build ganache-cli below) python3/pip3 , the solidity compiler ( solc-js from npm) solc not web3-py , our development blockchain ganache-cli py-solc-x Please follow the links above for installation instructions. Also note that py-solc-x is a fork of the official repo updated for Solc v0.5.x. As with any software you put on your machine, don’t just take my word for it’s safety and efficacy! I encourage you to do your own due diligence as I don’t maintain the above packages. I’d also like to preface this by saying I wrote everything on Ubuntu, if you run into issues on Windows or OS X I will do everything I can to help. H_owever,_ I do also have a lovely if you’d like to spin up a fresh Ubuntu VM in or . provisioning script Vagrant WSL Setup Let’s get stuck in. Go ahead and open up a terminal in a fresh directory and the . git clone project The contract I’m using is the ubiquitous ‘Greeter’ contract with an inherited access control contract. However, the interface was designed to be contract agnostic, so feel free to follow along with your own project if you’re working on one. We’re going to use the python-wrapped solc compiler for compiling our contract, and web3.py for interacting with the blockchain. I developed this using ganache-cli — it should work in theory with any client, but I haven’t tested them out. In a separate tab or terminal window, fire up ganache using the command. You should see an output beginning similarly to what’s below: ganache-cli Ganache CLI v6.1.8 (ganache-core: 2.2.1) Available Accounts==================(0) 0xf52cef744ccdd52e66856057d820d2b6677af63c (~100 ETH)(1) 0x6be3a04dcce9d82bf36b71b20831553e6d9e154e (~100 ETH)(2) 0xd9b6e69094f7ed37eaa724b9c0141a269513fdcf (~100 ETH)(3) 0xc9ec9a79be055bbaf88dd2ebefd4d0d6230a30de (~100 ETH)... Now, open up and set up your imports at the top of the file with: walkthrough.py import osfrom web3 import Web3, HTTPProviderfrom interface import ContractInterface This imports a few methods from the web3.py library along with the ContractInterface class from interface.py. Next, we’ll want to create our web3 instance using: w3 = Web3(HTTPProvider('http://127.0.0.1:8545')) Those are ganache’s default host and port. You can now interact with your node through python. Feel free to run the above commands in a python interperter, like: >>> from web3 import Web3, HTTPProvider>>> w3 = Web3(HTTPProvider('http://127.0.0.1:8545'))>>> w3.eth.accounts[0] #should return:>>> 0xf52cef744ccdd52e66856057d820d2b6677af63c #account 0 from above Nifty. On the next line set up the path variable for where your contracts are stored, if you’re just using the included contracts that will look like: contract_dir = os.path.abspath('./contracts/') And finally, you’ll want to create an interface instance with: greeter_interface = ContractInterface(w3, 'Greeter', contract_dir) ‘Greeter’ here is the name of the contract you wish to create an interface for. Since Greeter.sol inherits from Owned.sol, you’ll have access to all the methods within Owned from the Greeter interface. Let’s run briefly with the following to make sure we aren’t getting any errors on initialization: walkthrough.py python3 -i walkthrough.py>>> type(greeter_interface) Should return <class 'interface.ContractInterface'> Also, if you need a quick reference for any of the interface methods, you can run for docstring output. >>> help(ContractInterface) Compile Solc is (rightly so) pretty strict about compiling dependencies that aren’t passed explicitly. Everything was compiling fine when using Truffle, but Solc took some tinkering. If you’re importing contracts, make sure you use this specific format below your . The compiler should take care of the rest. Go ahead and add the following to : import "./contract.sol"; pragma walkthrough.py greeter_interface.compile_source_files() At this point, should look like walkthrough # Put your imports hereimport osfrom interface import ContractInterfacefrom web3 import HTTPProvider, Web3 # Initialize your web3 objectw3 = Web3(HTTPProvider(' )) http://127.0.0.1:8545' # Create a path object to your Solidity source filescontract_dir = os.path.abspath('./contracts/') # Initialize your interfacegreeter_interface = ContractInterface(w3, 'Greeter', contract_dir) # Compile contracts belowgreeter_interface.compile_source_files() Let’s look at the method in more detail: compile_source_files() deployment_list = [] for contract in os.listdir(self.contract_directory):deployment_list.append(os.path.join(self.contract_directory, contract)) self.all_compiled_contracts = compile_files(deployment_list) print('Compiled contract keys:\n{}'.format('\n'.join(self.all_compiled_contracts.keys()))) This method just created a list of absolute paths to be passed to py-solc’s . The compiler output is then saved to an instance attribute for later use. If you now run you should see the compiled contract keys print out. compile_files walkthrough Deploy Let’s add the deployment method to with the line: walkthrough greeter_interface.deploy_contract() This method first checks that the contracts are compiled and re-compiles them if not with the following: try:self.all_compiled_contracts is not Noneexcept AttributeError:print("Source files not compiled, compiling now and trying again...")self.compile_source_files() Next, it will find the name of the contract we specified to deploy earlier within one of the compiler output keys. It then creates a deployment instance using that contract’s application binary interface (ABI) and bytecode (BIN): for compiled_contract_key in self.all_compiled_contracts.keys():if self.contract_to_deploy in compiled_contract_key:deployment_compiled = self.all_compiled_contracts[compiled_contract_key] deployment = self.web3.eth.contract(abi=deployment_compiled['abi'],bytecode=deployment_compiled['bin']) This is our first time seeing the web3.py library in action. The web3.eth.contract is a contract class ready to be deployed on the blockchain. Next we’ll estimate the gas usage for deployment and deploy it if it’s below what was set during initialization. deployment_estimate = deployment.constructor().estimateGas(transaction=deployment_params) if deployment_estimate < self.max_deploy_gas:tx_hash = deployment.constructor().transact(transaction=deployment_params) A few things are happening here. The method builds the deployment transaction given . This is a dictionary with a number of defaults that can be overloaded if desired. The is set as a default during and is really just a safety feature for unexpected deployment gas usage. If that passes, the contract is deployed using and the transaction hash ( ) is returned. constructor() [deployment_params](http://deployment_estimate%20=%20deployment.constructor%28%29.estimateGas%28transaction=deployment_params%29%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20deployment_estimate%20%3C%20self.max_deploy_gas:%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tx_hash%20=%20deployment.constructor%28%29.transact%28transaction=deployment_params%29) max_deploy_gas __init__ constructor().transact tx_hash tx_receipt = self.web3.eth.waitForTransactionReceipt(tx_hash)contract_address = tx_receipt['contractAddress'] These two lines wait for the transaction to be mined, return the receipt, and pull the contract’s address from it. We’re going to write that to file with the following: vars = {'contract_address' : contract_address,'contract_abi' : deployment_compiled['abi']} with open (self.deployment_vars_path, 'w') as write_file:json.dump(vars, write_file, indent=4) This collects the deployment address and the contract’s ABI and saves them to a JSON file at the path specified in . We’ll see why in the next section. deployment_vars_path Now if you run you should see the output from compiling the contracts, some contract deployment output and a line telling you where those variables were saved. walkthrough Getting an Instance Once we deploy a contract to the blockchain, it will persist there indefinitely ( . We don’t want to re-compile and re-deploy every time we want to interact with it. This is why we saved the contract’s address and ABI in the JSON file in the last section. Add the following to : in theory at least) walkthrough greeter_interface.get_instance() The first part of this method will open up the JSON, check that it has an address, and then check that there’s something deployed at that address with: with open (self.deployment_vars_path, 'r') as read_file:vars = json.load(read_file) try:self.contract_address = vars['contract_address']except ValueError("No address found in {}, please call 'deploy_contract' andtry again.".format(self.deployment_vars_path)):raise contract_bytecode_length = len(self.web3.eth.getCode(self.contract_address).hex()) try:assert (contract_bytecode_length > 4), "Contract not deployedat {}.".format(self.contract_address)except AssertionError as e:print(e)raiseelse:print('Contract deployed at {}. This function returnsan instance object.'.format(self.contract_address)) Once those checks are done, we’ll build the contract instance again, using the address this time and return that instance. self.contract_instance = self.web3.eth.contract(abi = vars['contract_abi'],address = vars['contract_address']) return self.contract_instance This deployed instance exposes your contract’s properties and methods as outlined in the . For example, documentation >>>instance = greeter_interface.get_instance()>>>instance.functions.greet().call()>>>b'Hello'\x00\x00\x00\x00... The greeting shows up like this because it’s set as the type in . We’ll explore why I went with bytes, along with dealing with events, and cleaning outputs in part 2 of this tutorial! However, for a lot of people, this contract instance will be enough to suit their needs. bytes32 Greeter.sol Wrap Up I wrote this little interface because I wanted a clean way to compile, deploy, and interact with smart contracts from Python. It’s been a big learning experience for me and the result of a lot of tinkering — I hope it’s helpful to some of you out there. However, it is fairly opinionated and certainly not as robust as I’d like it to be. It’s very much a work in progress and I’m open to criticisms and improvements. Also, again, if you need any help along the way, leave a comment and I’ll do my best!
Share Your Thoughts