Blog

The Impact of Blockchain Technology Part 3: Smart Contracts Developer Tutorial with React

This is post 3 of 3 in the series “The Impact of Blockchain Technology Series”

  1. The Impact of Blockchain Technology Part 1: What Is It?
  2. The Impact of Blockchain Technology Part 2: Ethereum Blockchain Technology and Smart Contracts
  3. The Impact of Blockchain Technology Part 3: Smart Contracts Developer Tutorial with React

Mario Merino introduced blockchain fundamentals in the first post. Sr. Consultant Pablo Orozco explored the impact of Ethereum blockchain technology and Smart Contracts in the second post. In this third post, Web Practice Lead Daniel Prado shows you how to be the coolest kid on the block with a smart contracts developer tutorial with React.

Recommended snack and song:

220ml of dark roasted full bodied coffee brewed in french press accompanied by a banana while listening to Liquid Tension Experiment

tl;dr: You can go ahead and clone this repo, it’s got the end result of this in-depth tutorial below.

Here’s what we will be doing to become smart-contract heroes! – Install the dev environment plus MetaMask. – Develop a very simple voting smart contract. – Develop a front-end for our DAPP using React and Redux. – Configure and deploy our contract to Ropsten.

If you don’t know what Solidity is, please go check it out. There’s an awesome video library you can watch to get a firm grasp on developing smart contracts with Solidity. This is not a requisite for this blog post; I just thought it was important to share the videos, since their content is pretty solid.

The Dev Environment

We need to install our dev environment, so we’re going to install truffle, which will help us get started quickly:

$ npm install -g truffle

And now, there are several ways to run a local blockchain for our development environment, but the easiest is to download Ganache. Once you’ve downloaded and executed it, you’ll have a fully functional blockchain ethereum network for you to deploy and test your contracts.

Smart Contracts 1

Note your network ID and RPC Server Port; typically the network ID is 5777 and the server hosted in 127.0.0.1:7545.

Let’s Start Playing!

We don’t have to start from scratch to develop our app; we’re going to use truffle boxes, which are boilerplate projects that include a bunch of functionalities already built in to bootstrap our DAPP ideas:

$ mkdir voting-dapp && cd $_ $ truffle unbox react $ npm install --save env-cmd history material-ui@next material-ui-icons react react-dom react-redux react-router-dom react-router-redux@next redux redux-promise-middleware truffle-hdwallet-provider

In order to get the environment started the right way, add a file to the root of the application folder called .env:

NODE_PATH=src/ REACT_APP_LOCAL_BLOCKCHAIN_PORT=7545

The first bit will allow our React application to import modules using an absolute path instead of a relative one; this will avoid the typical import foo from ‘../../../../foo’, which are hard to read and maintain. The second one lets you configure which local network blockchain port to use; it’s set up to use the default Ganache network port.

Now, in the package.json file, let’s edit the scripts section so it looks like this:

 "scripts": {    "start": "env-cmd ./.env node scripts/start.js",    "build": "env-cmd ./.env node scripts/build.js",    "test": "env-cmd ./.env node scripts/test.js --env=jsdom"  },

Once we’ve set up the dependencies and scripts, lets go ahead and delete the contracts/SimpleStorage.sol file and create our own contracts/Voting.sol:

pragma solidity ^0.4.18; contract Voting {  mapping (bytes32 => uint8) public votes;  bytes32[] private candidateList;  event UpdateCandidates();  function getCandidateVotes(bytes32 candidate) public view returns (uint8) {    assert(doesCandidateExist(candidate));    return votes[candidate];  }  function listCandidates() public view returns (bytes32[]) {    return candidateList;  }  function postulateCandidate(bytes32 candidate) public {    assert(!doesCandidateExist(candidate));    candidateList.push(candidate);    UpdateCandidates();  }  function voteForCandidate(bytes32 candidate) public {    assert(doesCandidateExist(candidate));    votes[candidate] += 1;    UpdateCandidates();  }  function doesCandidateExist(bytes32 candidate) internal view returns (bool) {    for (uint i = 0; i < candidateList.length; i++) {      if (candidateList[i] == candidate) {        return true;      }    }    return false;  } }

This is just a sample solidity contract. Not the best solidity code out there, but it will do the trick.

Now open up migrations/2_deploy_contract.js and replace its contents with:

var Voting = artifacts.require("./Voting.sol"); module.exports = function(deployer) {  deployer.deploy(Voting); };

This will instruct the migrate command to always look for our contract and deploy it.

And for testing of our smart contract, delete all files in the test/ folder and place the test/voting.js file in there:

const Voting = artifacts.require('./Voting.sol'); const toAscii = function(hex) {    let str = '',        i = 0,        l = hex.length;    if (hex.substring(0, 2) === '0x') {        i = 2;    }    for (; i < l; i+=2) { const code = parseInt(hex.substr(i, 2), 16); if (code === 0) continue; // this is added str += String.fromCharCode(code); } return str; }; contract('Voting', (accounts) => {  it('...should start with an empty candidates list', () => {    let votingInstance;    return Voting.deployed()      .then(instance => {        votingInstance = instance;        return votingInstance.listCandidates.call({from: accounts[0]});      })      .then(candidateList => assert.equal(candidateList.length, 0, 'The candidate list is not empty.'))  });  it('...should postulate a new candidate', () => {    let votingInstance;    const candidate = 'John Doe';    return Voting.deployed()      .then(instance => {        votingInstance = instance;        return votingInstance.postulateCandidate(candidate, {from: accounts[0]})      })      .then(() => votingInstance.listCandidates.call({from: accounts[0]}))      .then(candidateList => candidateList.map(toAscii))      .then(candidateList => {        assert.include(candidateList, candidate, 'Candidate is not on the list');      });  });  it('...should vote for candidate', () => {    let votingInstance;    const candidate = 'John Doe';    return Voting.deployed()      .then(instance => {        votingInstance = instance;        return votingInstance.voteForCandidate(candidate, {from: accounts[0]});      })      .then(() => votingInstance.getCandidateVotes.call(candidate, {from: accounts[0]}))      .then(response => response.c[0])      .then((votes) => {        assert.equal(votes, 1, 'Candidate does not have votes');      })  });  it('...should get votes for the candidate', () => {    let votingInstance;    const candidate = 'John Doe';    return Voting.deployed()      .then(instance => {        votingInstance = instance;        return votingInstance.votes.call(candidate, {from: accounts[0]});      })      .then(() => votingInstance.getCandidateVotes.call(candidate, {from: accounts[0]}))      .then(response => response.c[0])      .then((votes) => {        assert.equal(votes, 1, 'Candidate does not have votes');      })  }) });

We also need to configure our deployment scripts by replacing the contents of the truffle.js file:

var HDWalletProvider = require('truffle-hdwallet-provider'); var infuraApiKey = 'YOUR GENERATED INFURA API KEY'; var mnemonic = 'YOUR 12 WORDS MNEMONIC'; module.exports = {  // See <http://truffleframework.com/docs/advanced/configuration>  // to customize your Truffle configuration!  networks: {    development: {      host: "127.0.0.1",      port: 7545,      network_id: '5777'    },    ropsten: {      provider: function() {        return new HDWalletProvider(mnemonic, 'https://ropsten.infura.io/' + infuraApiKey);      },      network_id: 3,      gas: 4612388    }  } };

Note: we’ll configure lines 3 and 5 later on when we’re preparing for Ropsten deployment.

Cool! Now we can start testing our smart contract:

$ truffle test

Smart Contracts 2

Since our tests are passing flawlessly, let’s push them to our Ganache local blockchain network (make sure Ganache is running first):

$ truffle compile $ truffle migrate

Smart Contracts 3

The next part is developing the front-end app, and because it’s an app with React + Redux + React-Router, there’s a whole bunch of files; so instead of copying/pasting every file, type these commands in the terminal (inside your project root of course):

$ mv src src-backup $ curl -L https://www.dropbox.com/s/zx16pkwyp1u86eq/src.zip\?dl\=1 | bsdtar -xvf-

That should create a backup of the src folder, and then download and unzip the source code of the voting-dapp front-end.

However, I’m going to explain some key files so you understand how to communicate with your smart contracts. For instance, in the build/contracts/ folder, you’ll find several .json files, one per contract; those files are known as ABI, which stands for Application Binary Interface, and they hold all of the information regarding each contract.

These ABI files are made when you truffle compile, and if you open src/contracts-api/VotingContract.js:

import { toAsciiFromByte32, getWeb3 } from 'utils' import VotingContractMeta from '../../build/contracts/Voting.json' import contract from 'truffle-contract' let instance = null export default class VotingContract {  constructor() {    if (!instance) {      instance = this      this.web3 = getWeb3()      this.contract = contract(VotingContractMeta)      this.contract.setProvider(this.web3.currentProvider)    }    return instance  }  async updateCandidatesEventListener(callback) {    const contractInstance = await this.contract.deployed()    const updateCandidatesEvent = contractInstance.UpdateCandidates()    return updateCandidatesEvent.watch(callback)  }  async getVotes() {    const contractInstance = await this.contract.deployed()    return contractInstance.votes();  }  async proposeCandidate() {    // TODO: This could receive a candidate's name as a parameter    const getRandomUserURL = 'https://randomuser.me/api/?nat=us'    const { eth: { accounts: [ account ] } } = this.web3    const contractInstance = await this.contract.deployed()    const {      results: [{        name: { first, last },        id: { value: id },      }]    } = await fetch(getRandomUserURL)      .then(response => response.json())    return contractInstance      .postulateCandidate(`${first} ${last} ${id}`, { from: account })  }  async castVote(candidateName) {    const { eth: { accounts: [ account ] } } = this.web3    const contractInstance = await this.contract.deployed()    return contractInstance.voteForCandidate(candidateName, { from: account })  }  async getAllCandidates() {    const contractInstance = await this.contract.deployed()    const candidateList = (await contractInstance.listCandidates())      .map(candidate => toAsciiFromByte32(candidate))    return Promise.all(candidateList.map(        (candidate) => contractInstance.getCandidateVotes.call(candidate)      ))      .then(allVotes => allVotes.map((votes, index) => ({        name: candidateList[index],        votes: Number(votes.toString()),      })))  }  async getCandidateVotes(candidate) {    const contractInstance = await this.contract.deployed()    const result = await contractInstance.getCandidateVotes.call(candidate.name)    return {      candidate: candidate.name,      votes: Number(result.toString())    } } }

You can see in the constructor (from lines 11 to 14) where we’re getting a reference to the Web3 provider instance; then, we create a contract using the ABI file and set its provider.

After that, it becomes super easy to operate on the contract; we get an instance reference of the deployed contract using async/await and then just call the function we’re interested in and then return the value of said function. Truffle-contract already abstracts all of the complexity for you and gives you a nice API to use.

This file acts as an abstraction layer for each function’s custom behavior.

This leads me to the other important aspect, the src/modules/candidates/candidates.actions.js file:

import {  GET_CANDIDATES,  VOTE,  NEW_CANDIDATE, } from './candidates.constants' import { VotingContract } from 'contracts-api' export const getCandidates = () => {  return {    type: GET_CANDIDATES,    payload: (new VotingContract()).getAllCandidates(),  } } export const postulateNewCandidate = () => {  return {    type: NEW_CANDIDATE,    payload: (new VotingContract())      .proposeCandidate({ type: 'random' })      .then(() => (new VotingContract()).getAllCandidates()),  } } export const vote = (candidateName) => {  return {    type: VOTE,    payload: (new VotingContract())      .castVote(candidateName)      .then(() => (new VotingContract()).getAllCandidates()),  } }

These are pretty standard redux action creator functions, but they’re using redux-promise-middleware, which makes the actions look a lot simpler. You can see how easy it is to call each contract function using our custom abstraction layer.

Also, you can listen to events being published by your contract; there’s an event called UpdateCandidates, and if you take a peek at src/modules/candidates/candidates.event-listeners.js file:

import { getCandidates } from 'modules/candidates' import { VotingContract } from 'contracts-api' export default async (dispatch, getState) => {  await (new VotingContract())    .updateCandidatesEventListener((error, candidateList) => {      dispatch(getCandidates())    }) }

This is a very simple function that is called from the src/store.js file in line 36:

applyCandidatesEventListeners(store.dispatch, store.getState);

It gets the dispatch and getState functions passed from the store instance, and that’s how it dispatches actions when events are called.

And that’s about it; everything else is a pretty standard react application. Now, we can go ahead and start our brand new front-end:

$ npm start

_Note: While in development mode, it’s better to deactivate MetaMask if you already have it installed; it has some nasty bugs regarding events and synchronization with local RPC servers (like Ganache) _

Smart Contracts 4

Success! You should have been presented with something like the picture above; you should be able to hit the plus fab button and the app should be creating new “candidates” each time. While you’re at it, go ahead and take a look at the Transactions and Blocks tabs in Ganache; you’ll see that each time you either vote or create a new candidate, a transaction is made and a block is added.

Deployment to Ropsten network

We will need MetaMask installed; this will keep things nice and easy. We’ll use MetaMask to create ourselves an Ethereum account and fill it with free ETH (using a faucet) so we can deploy our smart contracts to the Ropsten network (that’s a testing network, so that’s why we can get ETH for free).

First, create yourself a secure password:

Smart Contracts 5

Then, copy the 12 word mnemonic you’ve been given somewhere safe:

Smart Contracts 6

Copy them somewhere safe; seriously, they’re important. Now you can go to Infura.io to get your API key by signing up. Once you do, put those 12 words and the Infura API key in their respective places in your truffle.js file:

//... var infuraApiKey = 'YOUR GENERATED INFURA API KEY'; var mnemonic = 'YOUR 12 WORDS MNEMONIC'; //...

Then, on your MetaMask UI, select the Ropsten network:

Smart Contracts 7

Last thing—go to the MetaMask faucet and request some ether for your account, you’ll need it to deploy your contracts:

Smart Contracts 8

Wait for the transaction to finish, and then that’s it! Now, let’s deploy to Ropsten:

$ truffle compile $ truffle migrate --network ropsten

Done. You’re now officially on Ropsten; now you only need your MetaMask pointing towards Ropsten Network (like you already have), and you can interact with your deployed smart contract using your local react app (at least while your MetaMask is pointing towards the Ropsten Network).

If you want to deploy your app as a real distributed application, you can use IPFS.io, which will help you deploy your application in a P2P protocol static hosting. In case you want to deploy your front-end in a typical centralized way, surge.sh is pretty good for that and super easy to use.

That’s it for this tutorial; hope you enjoyed the music and the snack! You can find a presentation here and the source code for this application is at this repo; should you have any questions about implementations or the code in general, feel free to create issues on the Github repo.

Ready to be Unstoppable? Partner with Gorilla Logic, and you can be.

TALK TO OUR SALES TEAM