Programming an Ethereum based dApp — Part 2
This is short guide on how to program APIs for an Ethereum-based DApp using web3.js. This article is in continuation of another article by Blockwala publication written by Karan Ahuja on how to setup Ethereum Development Environment. Please make sure you read that one before continuing with this.
Communicating with the Ethereum node
We have learnt how to deploy contracts on the ethereum like blockchains, now lets see how to communicate with them:
There are two ways to do this:
- Truffle Console :
It is an interactive console that connects to an Ethereum client such as ganache or geth. All the compiled and migrated contracts are available and ready to use through the console. - APIs using web3 :
Make APIs that not only interact with the blockchain, but also run your off-blockchain logic, handle HTTP requests, responses etc.
Both the methods are made possible by the use of a library called Web3 which allows us to communicate with the Ethereum node. For the rest of the article, I am using javascript.
About Web3.js
What is web3 ?
Contracts are written in a high-level language like solidity and then compiled into EVM (Ethereum Virtual Machine) byte-code. This isn’t in a human readable format and we need something that can make it easier for us to deploy and interact with our contract on the blockchain. This is where Web3.js comes of use.
Web3.js or simply web3, is a collection of functions for communicating directly with the smart contracts on a local or a public Ethereum node. Basically, it is like a layer between our contract and the outside world (servers/clients). It can be used for:
1. Calling functions on our contracts
2. Listening to events fired in the contract
3. Get details of accounts controlled by node
4. Get details of block
5. Get gas needed to run a function
6. Transfer and ether and other ERC standard tokens to addresses
and many other functions …
But, before we move forward with using this library in our projects let us try to understand what goes on under the hood, i.e. how it communicates to our contract in the form of an EVM bytecode.
Working of Web3
JSON, as you may know, is a popular data-interchange format, that stores data in a syntax similar to a javascript object, i.e. using name-value pairs. It is often used to interchange data between APIs, processes, over sockets, HTTP protocol, etc.
The Ethereum node interacts with the outside world using such JSON files that follow a JSON-RPC (Remote Procedure Call) protocol. JSON-RPC isn’t in a format that humans will be comfortable reading or writing. Web3 makes it easier for us to send and receive messages to the Ethereum node in JSON-RPC, thus saving us from the hassle of writing long and boring JSON objects by doing this for us.
Calling every contract functions has different requirements for eg. number of arguments might be different, there types are different etc. Thus, different functions will have different kinds of JSON-RPC queries. Before sending the message, how does web3 know how to form a query for a particular function ?
This where use of ABI (Application Binary Interface) comes into picture.
Use of an ABI is a standard way to interact with contracts in Ethereum network. It is used for both communication of the contract with the outside world and for contract-contract communication. ABI is basically our contract functions converted into a JSON object by the compiler. This can be used by web3 for making queries to the blockchain in a standard format specified by the protocol.
Using Web3 in our Project at Blockwala.io
To begin interaction with our contracts, lets make an API which uses web3.
In your project’s directory, create a folder for APIs (or if you already have a folder switch to it). Inside this folder, create another folder for our web3 API file. I like to call it helper and the file as web3-helper, as you can see below:
Implementations may change from person to person. What I have tried to do here is use a separate module to wrap all web3 functions.
Importing Web3
To use web3 in any npm initialized project, open your project’s directory in the console and enter the following commands:
npm install --save web3
This will install web3 library in the node_modules folder in your project directory. Now, to use this in your code you have to import this into your script.
In web3-helper file type following to import web3:
var Web3 = require('web3');
Or, if you are using web3 in your web page’s front end type the following in the header of your HTML file:
<script src="./node_modules/web3/dist/web3.min.js"></script>
Now, you can use use this library for your further calls.
Setting a web3 provider
Enter the following code in your helper file or in an empty script tag in the body of your HTML file, depending on your preferred implementation of the DApp:
//Initiating web3// Connect to test-RPC networkif (typeof web3 !== 'undefined') { web3 = new Web3(web3.currentProvider);} else {// set the provider you want from Web3.providers web3 = new Web3(new Web3.providers.HttpProvider("http://127.0.0.1:8545"));}
The currentProvider of web3 is the socket on which the Ethereum node is attached and listening to (example: Infura, localhost or any public node). It is the address of the Ethereum node where your contracts were deployed in, and it has to be set so that web3 can interact with the contracts (or can be read from a config file, in order to make project more modular). In the above example, I have connected to private network using ganache-cli.
If this script is running on a Google Chrome with Metamask extension enabled or on Mist browser, chances are web3 is defined as web3.currentProvider has already been set. But in an off-case where this could not happen, or if you are running your script in backend server, you can set the currentProvider using the above code snippet.
Setting the Web Socket Provider
In order to listen to the events fired by the contracts we need to establish a web socket connection between our Ethereum node and Node.js. This is done in the following manner:
var web3Socket = new Web3(new Web3.providers.WebsocketProvider('ws://localhost:8545'));
Here, in this example I have set up a web socket between node.js and ganache-cli ethereum network.
Getting ABI of our contract
As explained in the beginning of this blog, web3 requires an ABI to interact with the smart contract. When you compile your contracts using truffle compile, it automatically creates .json files with the names same as the name of your contracts in the “build” folder of the project directory. ABI for communicating with a contract is in the “abi” field of that particular contract’s build file.
So include that in your file, like this:
const fs = require('fs');//INIT ABIvar result = JSON.parse(fs.readFileSync(<path of build file of your web3 facing contract>));var contractAbi = result.abi;
Creating a Contract Instance
Create a java-script contract object:
var contractInstance;
var contractAddress = <contract's address>;function getContractInst() {
if(! contractInstance) {
contractInstance = new web3.eth.contract.(contractAbi, contractAddress);
}
return contractInstance;
}
web3.eth.contract makes it easier to interact with the contracts on the Ethereum network. It contains the ABI of the contract and thus helps web3 to convert all function queries to low level ABI calls over JSON-RPC.
Thus, we will need contract object frequently to call the functions on the contract.
Calling Functions on the Contract
Finally, after all the above set-up it is finally time to actually interact with the contracts, i.e. call the functions on them. For calling functions on the contract, we would first need the contract object’s instance that we defined just now.
var contractInst = getContractInstance;
There are two types of functions in solidity:
1. Those that change the state of the blockchain (write), these functions cost some gas also
2. Those functions that only either read the data on the blockchain or do not interact with it at all. These are called view and pure functions respectively and do not cost any gas at all
The method to call function of each type is also different.
To call view or pure functions (also called constant functions) we use the syntax :
contactInst
.methods
.myFunction(<all the args here>)
.call({<options in name:value pairs>}, callback)
the options are optional for view functions, and are as follows:
from: (optional) takes a string, which is the msg.sender for the function.
gasPrice: (optional) a string, which is the gas price in wei for the transaction
gas: (optional) a number, maximum gas provided with this transaction
Note: Although EVM does not expect gas on view or pure functions, but a user might decide to charge gas for accessing such data.
This function returns a promise (which may return anything, int or string or array, etc. depending on the function’s return value(s). If there are multiple return values they are returned as an array.). So you should use a ‘.then’ after the promise to do something with the return value of the contract’s function. Or you can use the callback function, pass it the error and result respectively and deal with it there. Both are fine as long as the call becomes blocking.
To call a function that writes on the blockchain, use the following:
contractInst
.methods
.myFunction(<args>)
.send({<options>}, callback)
.on('confirmation', (confirmationNumber, receipt) => {
// Something with confirmation msg, maybe console.log
}).on('transactionHash', hash => {
// something with transaction hash
}).on('receipt', receipt => {
// something with receipt
}).on('error', error => {
// some error handling
});
Where, the options can be:
from: takes a string, which is the msg.sender for the function
gasPrice: (optional) a string, which is the gas price in wei for the transaction
gas: (optional) a number, maximum gas provided with this transaction
value: (optional) is a number/string/BN/BigNumber, which is the amount in wei transferred to the transaction. The contract myFunction belongs to will receive it as msg.value.
This .send() method retuens a promise combined with the event emitter. The promise is resolved when the transaction hash is produced. The events emitted are:
1. transactionHash: returns transaction hash as a String after the transaction is sent to the blockchain and the transaction hash is available.
2. receipt: returns receipt as a javascript object when the transaction has been made and the receipt is available.
3. confirmation: returns a number(confirmation number) or an object every time a gets confirmed by a block and the event is fired till the 24th confirmation
4. error: returns an Error, if the transaction sending fails, it also returns receipt as the second parameter if there is an out gas error.
Callbacks work the same way as in .call() above.
Listening To Events
To listen to events that may be fired by contract we need to establish a web socket connection with our contract using the web socket connection we established with the Ethereum node above. Do the following changes to the part where you create a new contract instance:
function getContractInst() {
if(! contractInstance) {
contractInstance = new web3.eth.contract(contractAbi, contractAddress); contractSocket = new web3Socket.eth.contract(contractAbi, contract Address); subscribeToEvents(contractSocket);
}
return contractInstance;
}function subscribeToEvents(contract) {
contract
.events
.eventName(options, callback)
.on('data', event => {
console.log(event.returnValues);
// or anything else to with values returned by event, like maybe save it on a database or something
})
.on('changed', event => {
// something
})
.on('error', event => {
// something
}):
}
Callback will be fired for each event, with error as the first argument and event object as the second argument.
The method contract.event.eventName() returns an event emitter and the event object has the following useful attributes:
- returnValues: the values returned by the event, if the values returned are multiple, then they will be returned as an array
- address: address of the contract this event was fired from,
and many other attributes like transactionHash, blockHash, blockNumber etc.
Conclusion
This concludes the second part of “Programming an Ethereum based dApp”. I hope this gave you some insight on the internal working of web3.js.
In a final part, we will see how to write a customized contract that conducts an ERC20 ICO.
Ref:
About Me: I am 3rd year undergrad from IIT Delhi currently interning as
Software Engineer @ Blockwala.io