A Comprehensive Guide to Ethereum Blockchain (Part 3)
This series of articles aims to share my practical experience in Ethereum Blockchain and to demonstrate how things are connected to each other.
This series consists of three parts, please read end-to-end and I’m hopeful It will be a time-saver for many of you!
Create & Deploy Node.js app based on Ethereum BlockChain
Here I will describe you how you can create a simple node.js app which have a user interface and interaction with smart contracts using web3.js API further advancing your smart contract structure to use Events, Modifiers, Struct, Mapping, Inheritance and at the end about its deployment on lite-server, following are the tools/APIs you will use in this subject.
Remix IDE — for creating smart contracts
Ganache — to set up private blockchain and run tests
web3.js API — provides an interface to interact with smart contracts
Node.js
Lite-server
Let me explain the complete flow:
Setup the Project
After downloading all these tools, lets setup the project by running following commands on console:
mkdir instructor-eth
cd instructor-eth
Next, run the npm init command to create a package.json file, which will store project dependencies:
npm init
Then install web3.js which will register its entry in package.json:
npm install ethereum/web3.js — save
Run Ganache
Run the Ganache tool to setup private blockchain with 10 test accounts provided and it will start listening on this port http://localhost:8545 for transactions.
Create Smart Contract
Open Remix IDE tool and create Smart Contract, here is the sample code:
pragma solidity ^0.4.18;
contract Instructor {
string fName;
uint age;
function setInstructor(string _fName, uint _age) public {
fName = _fName;
age = _age;
}
function getInstructor() public constant returns (string, uint) {
return (fName, age);
}
}
Deploy Smart Contract
Switch over to the Remix IDE tool, click on the Run tab, and then change the Environment dropdown from Javascript VM to Web3 Provider.
Hit “OK” and then specify the Ganache localhost address (by default, it is http://localhost:8545)
Hit “Create”, you will need the address of this contract shortly, so leave this window open.
Create User Interface
Now you can create a user interface to play with, open up your preferred code editor with the project folder you created. Here, you will notice a node_modules folder, which includes web3 that you installed via npm earlier.
Let’s create an index.html in the project folder.
It will consist of a simple UI, having form with 2 input fields for a name and age.
To get started, paste the following code into the empty index.html file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" type="text/css" href="main.css">
<script src="./node_modules/web3/dist/web3.min.js"></script>
</head>
<body>
<div class="container">
<h1>Coursetro Instructor</h1>
<h2 id="instructor"></h2>
<label for="name" class="col-lg-2 control-label">Instructor Name</label>
<input id="name" type="text">
<label for="name" class="col-lg-2 control-label">Instructor Age</label>
<input id="age" type="text">
<button id="button">Update Instructor</button>
</div>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
<script>
// Our future code here..
</script>
</body>
</html>
Create CSS file
As you can see, you are referencing a main.css file, so create that file and paste in the following rulesets real quickly:
body {
background-color:#F0F0F0;
padding: 2em;
font-family: 'Raleway','Source Sans Pro', 'Arial';
}
.container {
width: 50%;
margin: 0 auto;
}
label {
display:block;
margin-bottom:10px;
}
input {
padding:10px;
width: 50%;
margin-bottom: 1em;
}
button {
margin: 2em 0;
padding: 1em 4em;
display:block;
}
#instructor {
padding:1em;
background-color:#fff;
margin: 1em 0;
}
Configure Web3.js
I — Setup Default Address
Now It’s time to interact with smart contracts using web3.js, just replace the <script></script> tags with this piece of code, It will connect to your Ganache client to do transactions with the default address mentioned below, using first address of provided 10 accounts:
<script>
if (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://localhost:8545"));
}
web3.eth.defaultAccount = web3.eth.accounts[0];
</script>
II- Setup Smart Contract Interface
Next, you need to use the web3.eth.contract() method to initialize (or create) the contract on an address. It accepts one parameter, which is referred to as the ABI (Application Binary Interface).
This ABI allows you to call functions and receive data from your smart contract.
If you switch back to the Remix IDE tool, click on the Compile tab and click Details. Scroll down until you see the Interface — ABI section and click the copy icon as shown below:
Then going back to index.html and paste it where mentioned:
<script>
if (typeof web3 !== 'undefined')
…..
web3.eth.defaultAccount = web3.eth.accounts[0];
var myContract= web3.eth.contract(PASTE ABI HERE!);
</script>
III- Setup Smart Contract Address
After having the interface object to interact, the last thing to do is to define the actual contract address, to do this Goto Remix IDE -> Run tab, create the contract and click on the copy icon next to the contract that you just created on the right column.
Back to index.html and update it with the address:
<script>
if (typeof web3 !== 'undefined')
…..
web3.eth.defaultAccount = web3.eth.accounts[0];
var myContract= web3.eth.contract(PASTE ABI HERE!);
var contract = myContract.at('PASTE CONTRACT ADDRESS HERE');
console.log(contract);
</script>
Now, let’s open index.html to see if its connected to your smart contract or not.
CTRL-SHIFT-I (i) will show the console. You will see something similar to the following:
Interacting with Smart Contract
Smart Contract has been connected, now let’s use jQuery to make function calls based on your form, update the index.html:
<script>
……..
contract.getInstructor(function(error, result){
if(!error)
{
$("#instructor").html(result[0]+' ('+result[1]+' years old)');
console.log(result);
}
else
console.error(error);
});
$("#button").click(function() {
contract.setInstructor($("#name").val(), $("#age").val());
});</script>
Awesome, save and refresh it then give it a go!, now UI will look like this:
But here, after clicking on “Update Instructor” values set successfully but not reflected on the UI instantly, for which you will need to refresh the page and then see the results
Events & Modifiers
To get rid of manually refreshing the UI, Solidity introduces the concept Events, here you will add it in your smart contract and also listen for the events in the UI script tags, the other thing you are going to add is Modifiers which is just like, to whom you want to give permission to your contract interface, after adding Event & Modifier, your smart contract will look like this:
After updating the contract, you must need to re-Create the contract from Remix IDE tool then update its ABI and address in index.html file.
Now you are going to make some changes on the UI part. First, no longer need to call contract.getInstructor(), so you are going to remove that.
Then, you are going to create a variable to reference your event and adding the watch() method on instructorEvent with a callback. Beneath the above line of code, paste this:
<script>
……..
var instructorEvent = contract.Instructor();
instructorEvent.watch(function(error, result){
if (!error)
{
$("#loader").show();
$("#instructor").html(result.args.name + ' (' + result.args.age + ' years old)');
} else {
$("#loader").hide();
console.log(error);
}
});
</script>
Also, let's add the loader as well:
In index.html body tags,
<img id="loader" src="https://loading.io/spinners/double-ring/lg.double-ring-spinner.gif">
And in the main.css file:
#loader {
width: 100px;
display:none;
}
After doing all this stuff refresh the index.html file on the browser, try to update values, you will see UI will be refreshed instantly:
Mapping using Struct
Let’s start with the new contract named Courses.sol, first you need to create struct of Instructor in it which will store its name, first name and last name, just like model class in java referred from Hashmap, further details can be seen on Solidity site, here is the complete contract, just copy/paste it at your end:
pragma solidity ^0.4.18;
contract Courses {
struct Instructor {
uint age;
string fName;
string lName;
}
mapping (address => Instructor) instructors;
address[] public instructorAccts;
function setInstructor(address _address, uint _age, string _fName, string _lName) public {
var instructor = instructors[_address];
instructor.age = _age;
instructor.fName = _fName;
instructor.lName = _lName;
instructorAccts.push(_address) -1;
}
function getInstructors() view public returns(address[]) {
return instructorAccts;
}
function getInstructor(address _address) view public returns (uint, string, string) {
return (instructors[_address].age, instructors[_address].fName, instructors[_address].lName);
}
function countInstructors() view public returns (uint) {
return instructorAccts.length;
}
}
Inheritance
A common pattern in smart contracts when it concerns inheritance is to define an owner address and a modifier that gives only the smart contract owner address the ability to access certain functions.
Above the contract Courses , you are going to create a new base contract for that purpose:
pragma solidity ^0.4.18;
contract Owned {
address owner;
function Owned() public {
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
}
contract Courses is Owned {
…..
You can assure that this works by adding the onlyOwner modifier to the setInstructor() function in the derived contract:
…..
function setInstructor(address _address, uint _age, bytes16 _fName, bytes16 _lName) onlyOwner public {
…..
Strings to Bytes
In the above contract, you will notice your smart contract consists of two strings first name and last name. Strings are costworthy on the Ethereum blockchain, and when possible, you should actually use type of bytes.
Modify the smart contract so that each reference of first and last name, is no longer a type of string, but bytes16 instead as mentioned in above setInstructor() method
Deploying on Lite-Server
Your smart contract is ready to deploy, before deploying it, you need to install lite-server & MetaMask chrome plugin, which will allow you to connect to the Ropsten test network to more accurately simulate the experience of the dApp on the live Ethereum blockchain, So at this point you will turn off Ganache client to use Ropsten test network instead. Because of this, you have to install a lite-server in order for MetaMask to inject an instance of the Web3 API. If you do not, MetaMask will not work by just loading your index.html file into the browser straight from the hard drive.
Visit your console in the project folder you have been working in, and use npm to install the lite-server package:
npm install lite-server --save-dev
Note: If you have problems running this command as I did, make sure you are running your console as administrator.
Next, open up your code editor and inside of the package.json file, add this under scripts:
"scripts": {
"dev": "lite-server"
},
Now, you can run the following command to start the lite-server, which will automatically load the index.html file you have been working on, in your browser at a localhost address:
npm run dev
Installing MetaMask
Going forward, you will use MetaMask, which is a chrome extension that brings Ethereum to your browser. It provides you with a wallet, and the ability to connect to different test networks, as well as the main Ethereum blockchain.
It provides you with a way to interact with decentralized apps by providing you with a way to manage multiple accounts on the live network and test network. It also allows you to sign/approve transactions that are initiated by your Web3 project.
Install the plugin and follow this guide to getting started with setting up the plugin for the first time. You want to get to the point at which you are logged in to the Ropsten test network. If you need to, you can click Buy, which will provide you with free test Ether for use in your project.
Deploying the Contract to the Ropsten Test Network
Since you have updated the contract, so, let’s go back to the Remix IDE and redeploy this contract.
Click on Run and change the Environment to Injected Web3.
This will use MetaMask’s injected Web3 instance, which will deploy the contract on the Ropsten test network. After moving back to index.html, again you need to update contract Address, ABI to reflect the changes and also updating the UI part a little bit:
<div class=”container”><h1>Contract Instructor</h1><span id=”countIns”></span><h2 id=”instructor”></h2><span id=”insTrans”></span><hr><img id=”loader” src=”https://loading.io/spinners/double-ring/lg.double-ring-spinner.gif"><label for=”fName” class=”col-lg-2 control-label”>First Name</label><input id=”fName” type=”text”><label for=”lName” class=”col-lg-2 control-label”>Last Name</label><input id=”lName” type=”text”><label for=”age” class=”col-lg-2 control-label”>Instructor Age</label><input id=”age” type=”text”><button id=”button”>Update Instructor</button></div>
As you are now using Ropsten test network on MetaMask instead using your own private-net on Ganache, therefore need to do some changes mentioned below:
// Change this:
var instructorEvent = contract.Instructor();
// To this:
var instructorEvent = contract.instructorInfo({},'latest');
// Update this listener
instructorEvent.watch(function (err, result) {
if (!err) {
if (result.blockHash != $("#instrans").html())
$("#loader").hide();
$("#insTrans").html('Block hash: ' +result.blockHash);
$("#instructor").html(web3.toAscii(result.args.fName) + ' ' + web3.toAscii(result.args.lName) + ' (' + result.args.age + ' years old)');
} else {
$("#loader").hide();
}
});
// Add it to show instructors count
contract.countInstructors((err, res) => {
if (res)
$("#countIns").html(res.c + ' Instructors');
})
// And at last, update button click function
$("#button").click(function() {
$("#loader").show();
contract.setInstructor(web3.eth.defaultAccount, $("#age").val(), $("#fName").val(), $("#lName").val(), (err, res) => {
if (err==null || err) {
$("#loader").hide();
}
});
});
After deployment lets run it on lite-server, now you see the results:
After pressing submit it will take some time depends upon the Gas price, higher for quick processing of the transaction.
here is the Full Solidity contract which you have deployed, just make sure at your end:
pragma solidity ^0.4.18;
contract Owned {
address owner;
function Owned() public {
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
}
contract Courses is Owned {
struct Instructor {
uint age;
bytes16 fName;
bytes16 lName;
}
mapping (address => Instructor) instructors;
address[] public instructorAccts;
event instructorInfo(
bytes16 fName,
bytes16 lName,
uint age
);
function setInstructor(address _address, uint _age, bytes16 _fName, bytes16 _lName) onlyOwner public {
var instructor = instructors[_address];
instructor.age = _age;
instructor.fName = _fName;
instructor.lName = _lName;
instructorAccts.push(_address) -1;
instructorInfo(_fName, _lName, _age);
}
function getInstructors() view public returns(address[]) {
return instructorAccts;
}
function getInstructor(address _address) view public returns (uint, bytes16, bytes16) {
return (instructors[_address].age, instructors[_address].fName, instructors[_address].lName);
}
function countInstructors() view public returns (uint) {
return instructorAccts.length;
}
}
This was the last Article of this series, hope you enjoyed it.
Please reach out to me in case of any query contact@farhanhabib.com and also don’t forget to applaud if you like this Article. Thanks