Step by Step Guide to Build a Dapp

Dapp — Decentralised Application. This post will help you build an Ethereum Dapp from scratch!


Context:

What’s a DAPP?

DApp is an abbreviated form for decentralized application.
A DApp has its backend code running on a decentralized peer-to-peer network. Contrast this with an app where the backend code is running on centralized servers.
A DApp can have frontend code and user interfaces written in any language (just like an app) that can make calls to its backend. Furthermore, its frontend can be hosted on decentralized storage such as Swarm or IPFS.
If an app=frontend+server, since Ethereum contracts are code that runs on the global Ethereum decentralized peer-to-peer network, then:
DApp = frontend + contracts

source: https://ethereum.stackexchange.com/questions/383/what-is-a-dapp

Types of DAPP

Johnston states that there are three types of dapps.
Type I decentralized applications have their own block chain, such as Bitcoin.
Type II decentralized applications use the blockchain of a type I decentralized application but are “protocols and have tokens that are necessary for their function” like the Omni Protocol.
Type III decentralized applications use the protocol of a type II decentralized application and “are protocols and have tokens that are necessary for their function,” such as the SAFE Network that uses the Omni Protocol to issue ‘safecoins.”
Think of Dapps as an operating system like Windows, Mac OS X, Linux, Android, iOS as a Type I classification. The programs on these systems, such as a word processor or Dropbox, would be Type II. A Type III example would then be a blogging platform that integrates Dropbox.

source:https://due.com/blog/why-build-decentralized-applications-understanding-dapp/


Big Picture

Part 1: Environment Setup + Smoke Test

Part 2: List Candidates

Part 3: Cast Votes

Part 4: Watch Events


Part 1: Environment Setup + Smoke Test

1. Install Home Brew:

Open Terminal

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

brew update
brew doctor
brew prune

Uninstall:

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/uninstall)"

Install Node

brew install node
<Issues>
brew link --overwrite node
sudo chown -R $(whoami) /usr/local/share/systemtap

Test node , npm, homebrew versions

node -v
npm -v
brew -v

2. Install Truffle:

npm install -g truffle

3. Install metamask chrome extension

4. Install Sublime Text

Open Sublime Text and install the Ethereum Package

Follow these steps:

Package Control is driven by the Command Palette. To open the palette, press ctrl+shift+p (Win, Linux) or cmd+shift+p (OS X). All Package Control commands begin with Package Control:, so start by typing Package.
The command palette will now show a number of commands. Most users will be interested in the following:
Install PackageShow a list of all available packages that are available for install. This will include all of the packages from the default channel, plus any from repositories you have added.

5. Install Ganache

6. Make Project Folder

mkdir election
cd election

7. Unbox Pet-shop truffle package

truffle unbox pet-shop

8. Open Sublime text and open the entire folder election

Project Folder Structure

9. Make a file election.sol [election contract]

Mosess-MacBook-Pro:election Sam$ ls
box-img-lg.png migrations src
box-img-sm.png node_modules test
bs-config.json package-lock.json truffle.js
contracts package.json
Mosess-MacBook-Pro:election Sam$ cd contracts
Mosess-MacBook-Pro:contracts Sam$ touch Election.sol
Mosess-MacBook-Pro:contracts Sam$ ls
Election.sol Migrations.sol

10. Open Election.sol in Sublime Text

pragma solidity ^0.4.17;
/**
* The contractName contract does this and that...
*/
contract Election {
// Store candidate
// Read Candidate
string public candidate; //state variable
//constructor
function Election () public {
candidate = "Kamal Haasan";

}
}

11. Create 2_deploy_contracts.js under migrations folder

  • use the touch command within Terminal (or)
  • create a new file within migrations folder from sublime text editor itself
  • use the code below within the 2_deploy_contracts.js file and save it
var Election = artifacts.require("./Election.sol");
module.exports = function(deployer) {
deployer.deploy(Election);
};

12. Migrate the contract using Truffle

within the Terminal at election folder run the below code

truffle migrate

output:

truffle console
truffle(development)> Election.deployed().then(function(instance) {app = instance })
undefined
truffle(development)> app.address
'0xf12b5dd4ead5f743c6baa640b0216200e89b60da'
truffle(development)> app.candidate()
'Kamal Haasan'

Part 2: List Candidates

1. Change Contract code

Open Election.sol in sublime text and edit the code

pragma solidity ^0.4.2;
/**
* The contractName contract does this and that...
*/
contract Election {
// Model a candidate
struct Candidate{
uint id;
string name;
uint voteCount;
}
// Store a candidate
//Fetch Candidate
mapping(uint => Candidate) public candidates;

// Store candidate count
uint public candidatesCount;
//constructor
function Election () public {
addCandidate("KamalHaasan");
addCandidate("RajniKanth");
}
function addCandidate (string _name) private {
candidatesCount ++;
candidates[candidatesCount] = Candidate(candidatesCount, _name, 0);
}
}

2. Open Terminal to play with Truffle commands

Ganache:

see how the first node’s balance has reduced because we have deployed contracts / written on the blockchain
truffle migrate --reset

truffle console
Election.deployed().then(function(i){app =i; })
app.candidates(1)
app.candidates(2)
app.candidates(1).then(function(c) {candidate =c; })
candidate
Output from Truffle Console run on Terminal — Mac

3. Fetching a candidate

truffle(development)> candidate[0].toNumber()
1
truffle(development)> candidate[2].toNumber()
0

4. Introducing Web3

This will help us pull the voter details . Web.eth.accounts should give you the list of accounts / addressses connected to the blockchain. In our case, this is the list of accounts within the Ganache (local blockchain)

truffle(development)> web3.eth.accounts
[ '0x627306090abab3a6e1400e9345bc60c78a8bef57',
'0xf17f52151ebef6c7334fad080c5704d77216b732',
'0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef',
'0x821aea9a577a9b44299b9c15c88cf3087f3b5544',
'0x0d1d4e623d10f9fba5db95830f7d3839406c6af2',
'0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e',
'0x2191ef87e392377ec08e7c08eb105ef5448eced5',
'0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5',
'0x6330a553fc93768f612722bb8c2ec78ac90b3bbc',
'0x5aeda56215b167893e80b4fe645ba6d5bab767de' ]
truffle(development)>

Ganache screengrab

Ganache Nodes

To reference them individually simply go through the indexes.

truffle(development)> web3.eth.accounts[0]
'0x627306090abab3a6e1400e9345bc60c78a8bef57'

5. Testing

Two reasons to test everything before we deploy the contract to the blockchain.

  • Everything written in the blockchain is fucking immutable
  • It costs Ether / Gas to write to the contract

So it makes sense to test every fucking thing before we deploy the contract to the actual blockchain

Framework

Assertion Library

Open a new Terminal window and create a election.js within a test folder

touch test/election.js

Open up sublime text and use this code within the newly created election.js within the Test folder

var Election = artifacts.require("./Election.sol");
contract("Election", function (accounts){
it("initializes with two candidates", function() {
return Election.deployed().then(function(instance) {
return instance.candidatesCount();
}).then(function(count) {
assert.equal(count, 2);
});
});
} );

Test it on the console:

truffle test
Test Passed

What about a negative scenario ?!

Now if I change the count to 4

var Election = artifacts.require("./Election.sol");
contract("Election", function (accounts){
it("initializes with two candidates", function() {
return Election.deployed().then(function(instance) {
return instance.candidatesCount();
}).then(function(count) {
assert.equal(count, 4);
});
});
} );

Look at the test result!

The above code tested if there are 2 candidates…we gotta test the details of the candidates as well right?

var Election = artifacts.require("./Election.sol");
contract("Election", function (accounts){
it("initializes with two candidates", function() {
return Election.deployed().then(function(instance) {
return instance.candidatesCount();
}).then(function(count) {
assert.equal(count, 2);
});
});
it("it initializes the candidates with the correct values", function() {
return Election.deployed().then(function(instance) {
electionInstance = instance;
return electionInstance.candidates(1);
}).then(function(candidate) {
assert.equal(candidate[0], 1, "contains the correct id");
assert.equal(candidate[1], "Candidate 1", "contains the correct name");
assert.equal(candidate[2], 0, "contains the correct votes count");
return electionInstance.candidates(2);
}).then(function(candidate) {
assert.equal(candidate[0], 2, "contains the correct id");
assert.equal(candidate[1], "Candidate 2", "contains the correct name");
assert.equal(candidate[2], 0, "contains the correct votes count");
});
});
});

All right, so if I run Truffle test on my terminal would it pass?

Nope! That’s right cos the names of the candidates are not candidate 1 & candidate 2 but KamalHaasan & RajniKanth. So I change that in the .js file and try to test it..boom! Pass!


Front end code — HTML & Javascript

1. index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<title>Election Results</title>
<!-- Bootstrap -->
<link href="css/bootstrap.min.css" rel="stylesheet">
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="container" style="width: 650px;">
<div class="row">
<div class="col-lg-12">
<h1 class="text-center">Election Results</h1>
<hr/>
<br/>
<div id="loader">
<p class="text-center">Loading...</p>
</div>
<div id="content" style="display: none;">
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Votes</th>
</tr>
</thead>
<tbody id="candidatesResults">
</tbody>
</table>
<hr/>
<p id="accountAddress" class="text-center"></p>
</div>
</div>
</div>
</div>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="js/bootstrap.min.js"></script>
<script src="js/web3.min.js"></script>
<script src="js/truffle-contract.js"></script>
<script src="js/app.js"></script>
</body>
</html>

2. app.js

App = {
web3Provider: null,
contracts: {},
account: '0x0',
init: function() {
return App.initWeb3();
},
initWeb3: function() {
// TODO: refactor conditional
if (typeof web3 !== 'undefined') {
// If a web3 instance is already provided by Meta Mask.
App.web3Provider = web3.currentProvider;
web3 = new Web3(web3.currentProvider);
} else {
// Specify default instance if no web3 instance provided
App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545');
web3 = new Web3(App.web3Provider);
}
return App.initContract();
},
initContract: function() {
$.getJSON("Election.json", function(election) {
// Instantiate a new truffle contract from the artifact
App.contracts.Election = TruffleContract(election);
// Connect provider to interact with contract
App.contracts.Election.setProvider(App.web3Provider);
return App.render();
});
},
render: function() {
var electionInstance;
var loader = $("#loader");
var content = $("#content");
loader.show();
content.hide();
// Load account data
web3.eth.getCoinbase(function(err, account) {
if (err === null) {
App.account = account;
$("#accountAddress").html("Your Account: " + account);
}
});
// Load contract data
App.contracts.Election.deployed().then(function(instance) {
electionInstance = instance;
return electionInstance.candidatesCount();
}).then(function(candidatesCount) {
var candidatesResults = $("#candidatesResults");
candidatesResults.empty();
for (var i = 1; i <= candidatesCount; i++) {
electionInstance.candidates(i).then(function(candidate) {
var id = candidate[0];
var name = candidate[1];
var voteCount = candidate[2];
// Render candidate Result
var candidateTemplate = "<tr><th>" + id + "</th><td>" + name + "</td><td>" + voteCount + "</td></tr>"
candidatesResults.append(candidateTemplate);
});
}
loader.hide();
content.show();
}).catch(function(error) {
console.warn(error);
});
}
};
$(function() {
$(window).load(function() {
App.init();
});
});

Notice: Package.json has the liteserver

3. Terminal — Migrate the contract

truffle migrate --reset

As expected this should reduce the ether in my first node in the local blockchain (Ganache)

4. New tab within the Terminal — Run the lite server

npm run dev

5. Holy shit it worked! — The client page opens up in the browser!!!

It’s still loading because this is not connected to the local blockchain and here comes the Metamask!

connect to custom RPC

http://localhost:7545/ — you can see your port number from the Ganache block chain

Voila…

see the webpage that opened up

you see the account address below the candidates. Let’s see if we can connect to another account / node. Where do we go to access our nodes in the private blockchain ? Ganache! yes.

Follow steps given below each image in the sequence given below.

We are gonna login to Meta Mask with Index 3 node.
click on the key button at the right end of the Index 3 row. Copy the private key.
In Metamask, click on the small profile icon. Click import account
Click — Import Account and paste the private key
Now, if you refresh the webpage..the account number should match the index 3 node’s public address

Part 3: Cast Votes

Till now we have just access the contract and displayed the contents. Now we are gonna interact with it. Yes, we are gonna cast a vote!

sublime — Election.sol

// store accounts that have voted
mapping(address => bool) public voters;

vote function

function vote(uint _candidateId) public {
//record that voter has voted
voters[msg.sender] = true;
// update candiadte voteCount
candidates[_candidateId].voteCount ++;
}

Terminal

migrate the contract

truffle migrate --reset
truffle console

within the truffle console

Election.deployed().then(function(i) { app = i })
web3.eth.accounts
web3.eth.accounts[0]
app.vote(1,{ from:web3.eth.accounts[0] })
  • The last line of code is very interesting at east to me because you can pass more than just a parameter to a function. You can pass metadata as a parameter! when did this happen in programming? 😮 Anyway, what we are passing here is the first node address of the ganache local blockchain.

First of all, this is a transaction receipt. Quick recap about blockchain.

The basic unit of blockchain is transaction, which are clubbed together to form and block which in turn are chained together to form a block chain. So every “write” into the blockchain is a transaction and the output is a transaction receipt.
Candidate 1’s vote has increased by 1 now

Test for the voting function

we gotta edit the election.js within the test folder in sublime

var Election = artifacts.require("./Election.sol");
contract("Election", function(accounts) {
var electionInstance;
it("initializes with two candidates", function() {
return Election.deployed().then(function(instance) {
return instance.candidatesCount();
}).then(function(count) {
assert.equal(count, 2);
});
});
it("it initializes the candidates with the correct values", function() {
return Election.deployed().then(function(instance) {
electionInstance = instance;
return electionInstance.candidates(1);
}).then(function(candidate) {
assert.equal(candidate[0], 1, "contains the correct id");
assert.equal(candidate[1], "KamalHaasan", "contains the correct name");
assert.equal(candidate[2], 0, "contains the correct votes count");
return electionInstance.candidates(2);
}).then(function(candidate) {
assert.equal(candidate[0], 2, "contains the correct id");
assert.equal(candidate[1], "RajniKanth", "contains the correct name");
assert.equal(candidate[2], 0, "contains the correct votes count");
});
});
it("allows a voter to cast a vote", function() {
return Election.deployed().then(function(instance) {
electionInstance = instance;
candidateId = 1;
return electionInstance.vote(candidateId, { from: accounts[0] });
}).then(function(receipt) {
return electionInstance.voters(accounts[0]);
}).then(function(voted) {
assert(voted, "the voter was marked as voted");
return electionInstance.candidates(candidateId);
}).then(function(candidate) {
var voteCount = candidate[2];
assert.equal(voteCount, 1, "increments the candidate's vote count");
})
});
});

terminal

truffle test

output:

let’s add more logic:

  1. Voter shouldn’t have voted already
  2. The candidate is a valid candidate

where do we add logic? yes! that’s right in the solidity file..

sublime → Election.sol

function vote(uint _candidateId) public {
// require that the voter hasn't voted before
require (!voters[msg.sender]);
// require a valid candidate
require (_candidateId > 0 && _candidateId <= candidatesCount);

now that the contract is updated..what do we do? that’s right..we gotta migrate / deploy the contract

truffle migrate --reset

..

truffle console
truffle(development)> Election.deployed().then(function(i) { app = i })
truffle(development)> app.vote(1,{ from:web3.eth.accounts[0] })

output

Transaction Receipt

Let’s try and vote again.

app.vote(1,{ from:web3.eth.accounts[0] })

output

error! I don’t think you will be ever so happy to see a fucking ERROR

Few other scenarios to test

app.vote(2,{ from:web3.eth.accounts[1] })
app.vote(31,{ from:web3.eth.accounts[1] })
app.vote(2,{ from:web3.eth.accounts[0] })

now let’s code test these.

where do we write the test codes? election.js inside the test folder

var Election = artifacts.require("./Election.sol");
contract("Election", function(accounts) {
var electionInstance;
it("initializes with two candidates", function() {
return Election.deployed().then(function(instance) {
return instance.candidatesCount();
}).then(function(count) {
assert.equal(count, 2);
});
});
it("it initializes the candidates with the correct values", function() {
return Election.deployed().then(function(instance) {
electionInstance = instance;
return electionInstance.candidates(1);
}).then(function(candidate) {
assert.equal(candidate[0], 1, "contains the correct id");
assert.equal(candidate[1], "KamalHaasan", "contains the correct name");
assert.equal(candidate[2], 0, "contains the correct votes count");
return electionInstance.candidates(2);
}).then(function(candidate) {
assert.equal(candidate[0], 2, "contains the correct id");
assert.equal(candidate[1], "RajniKanth", "contains the correct name");
assert.equal(candidate[2], 0, "contains the correct votes count");
});
});
it("allows a voter to cast a vote", function() {
return Election.deployed().then(function(instance) {
electionInstance = instance;
candidateId = 1;
return electionInstance.vote(candidateId, { from: accounts[0] });
}).then(function(receipt) {
return electionInstance.voters(accounts[0]);
}).then(function(voted) {
assert(voted, "the voter was marked as voted");
return electionInstance.candidates(candidateId);
}).then(function(candidate) {
var voteCount = candidate[2];
assert.equal(voteCount, 1, "increments the candidate's vote count");
})
});
it("throws an exception for invalid candiates", function() {
return Election.deployed().then(function(instance) {
electionInstance = instance;
return electionInstance.vote(99, { from: accounts[1] })
}).then(assert.fail).catch(function(error) {
assert(error.message.indexOf('revert') >= 0, "error message must contain revert");
return electionInstance.candidates(1);
}).then(function(candidate1) {
var voteCount = candidate1[2];
assert.equal(voteCount, 1, "candidate 1 did not receive any votes");
return electionInstance.candidates(2);
}).then(function(candidate2) {
var voteCount = candidate2[2];
assert.equal(voteCount, 0, "candidate 2 did not receive any votes");
});
});
it("throws an exception for double voting", function() {
return Election.deployed().then(function(instance) {
electionInstance = instance;
candidateId = 2;
electionInstance.vote(candidateId, { from: accounts[1] });
return electionInstance.candidates(candidateId);
}).then(function(candidate) {
var voteCount = candidate[2];
assert.equal(voteCount, 1, "accepts first vote");
// Try to vote again
return electionInstance.vote(candidateId, { from: accounts[1] });
}).then(assert.fail).catch(function(error) {
assert(error.message.indexOf('revert') >= 0, "error message must contain revert");
return electionInstance.candidates(1);
}).then(function(candidate1) {
var voteCount = candidate1[2];
assert.equal(voteCount, 1, "candidate 1 did not receive any votes");
return electionInstance.candidates(2);
}).then(function(candidate2) {
var voteCount = candidate2[2];
assert.equal(voteCount, 1, "candidate 2 did not receive any votes");
});
});
});

output

all 5 scenarios are handled

Front end for voting! Final Piece, I can see it come together, can you?~

Download the index.html & app.js files from here

self explanatory pics

get private key from ganache and change accout in meta mask to vote from different accounts.

Since we have written (copy-pasted 👐 ) the code only to hide the form once a vote is cast and “loading” comes up we have to refresh each time. Event tracking isn’t there so we are gonna add that to the logic now.


Part 4: Watching Events

Final Code Base -

contract file — Election.sol —

test file — election.js

test cases

Migrate the contract

Now you can refresh the front end page and have fun!

Auto-refresh of the page happened as soon as the gas money was spent! #EventListenedTo

Thanks,

Moses Sam Paul