The vision of Ethereum was to create a platform that would become the next generation of the internet, Web 3.0. This new generation would allow users to explore both the traditional web and the decentralized web. On top of Ethereum, a new generation of applications would be created, decentralized apps (dApps), which use Ethereum as its back-end instead of placing security in servers.

Like the BitTorrent content sharing system, Ethereum network nodes will run on thousands of computers around the world and, short of shutting down the Internet, its operations cannot be halted.

— Joseph Lubin

While Bitcoin was a protocol built for storing and transferring value, acting as a central bank of the internet, Ethereum was a protocol built for creating decentralized applications, adding logic to financial transactions, making digital currencies programmable.

This programmable layer to digital currency is the smart contract. The smart contract allows the creation of binding agreements enforced by code. The enforcement of a smart contract may be a transfer of value from one party to another, becoming ideal for use in digital currencies.

Today, Ethereum is the world’s leading smart contract platform, becoming the world’s leading programmable network ledger. Because of Ethereum, developers don’t need to build a blockchain from scratch to use blockchain technology.

As an introduction to how to build on Ethereum, we’ll deal with the most fundamental parts of creating a dApp: communicating to a smart contract from a website/user interface.

For this project, our stack includes Ethereum and React. I prefer keeping files in separate directories to keep a given project organized.

project

|_ blockchain (Ethereum)

|_ client (React)

For the full project, please see the example project on Github.

Setting up our Blockchain

For this stack, we’ll be using the Truffle.js suite of development tools. First, install Truffle, if not on your machine already.

npm install -g truffle

I created a new directory to hold our files building our smart contract ./blockchain . Within ./blockchain , we can initialize our Ethereum code using Truffle. In your command line:

cd blockchain

truffle init

A few files and directories should fill ./blockchain now.

blockchain

|_ contracts

|_ migrations

|_ test

In ./contracts there should be a Migrations.sol smart contract. Migrating is the process of publishing a smart contract to a given blockchain. All starter code to set up migrations are included, except code to migrate new contracts we create.

For simply understanding the process of migrating locally, to test our UI, we will use a very simple smart contract to start. The Solidity docs contain a short smart contract example for simple storage. We’ll use that contract code. Create and fill SimpleStorage.sol .

// SimpleStorage.sol pragma solidity >=0.4.0 <0.7.0;



contract SimpleStorage {

uint storedData;



function set(uint x) public {

storedData = x;

}



function get() public view returns (uint) {

return storedData;

}

}

This smart contract does two things:

Stores a positive integer ( set() )

) Retrieves that positive integer from storage ( get() )

We have our smart contract for the project. We can compile our code. We do so with Truffle.

truffle compile

This should create a new directory called ./build/contracts . There should be two files for our project: Migrations.json and SimpleStorage.json . Each of these JSON files should contain a contract name, an ABI, and other information used in deployment.

Using the JSON files we compiled, we can create our migration files. In ./blockchain/migrations you should see an existing migration file 1_initial_migrations.js . The file should contain the following:

// 1_initial_migrations.js var Migrations = artifacts.require("Migrations");



module.exports = function(deployer) {

// Deploy the Migrations contract as our only task

deployer.deploy(Migrations);

};

In the variable Migrations , you can see "Migrations" being imported. "Migrations" is the contract name provided in ./build/contracts/Migrations.json . This 1_initial_migrations.js is using Migrations.json to deploy the Migrations contract. We shall create a new migration using the contract name for the new contract we created,

"SimpleStorage” .

Create a new migration file 2_first_contracts.js . We’ll populate it with code similar to 1_initial_migrations.js but include our SimpleStorage smart contract.

// 2_first_contracts.js const SimpleStorage = artifacts.require("SimpleStorage");



module.exports = function(deployer) {

deployer.deploy(SimpleStorage);

};

That is all the setup needed to deploy the code in our project locally. Setup for more complicated contracts may differ. However, without adding our contract to migration files, migrating in Truffle will only migrate the Migration.sol contract, and will not deploy the other contracts we created and compiled. So this step is required.

Next, we create a local Ethereum testing network (testnet) and deploy our code locally. We do so again with Truffle. Start a development blockchain:

truffle develop

Truffle should display a host and port, as well as a set of accounts with their corresponding private keys.

Truffle Develop started at http://127.0.0.1:9545/



Accounts:

(0) 0x627306090abab3a6e1400e9345bc60c78a8bef57

(1) 0xf17f52151ebef6c7334fad080c5704d77216b732

(2) 0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef

(3) 0x821aea9a577a9b44299b9c15c88cf3087f3b5544

(4) 0x0d1d4e623d10f9fba5db95830f7d3839406c6af2

(5) 0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e

(6) 0x2191ef87e392377ec08e7c08eb105ef5448eced5

(7) 0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5

(8) 0x6330a553fc93768f612722bb8c2ec78ac90b3bbc

(9) 0x5aeda56215b167893e80b4fe645ba6d5bab767de



Private Keys:

(0) c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3

(1) ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f

(2) 0dbbe8e4ae425a6d2687f1a7e3ba17bc98c673636790f1b8ad91193c05875ef1

(3) c88b703fb08cbea894b6aeff5a544fb92e78a18e19814cd85da83b71f772aa6c

(4) 388c684f0ba1ef5017716adb5d21a053ea8e90277d0868337519f97bede61418

(5) 659cbb0e2411a44db63778987b1e22153c086a95eb6b18bdf89de078917abc63

(6) 82d052c865f5763aad42add438569276c00d3d88a2d062d36b2bae914d58b8c8

(7) aa3680d5d48a8283413f7a108367c7299ca73f553735860a87b08f39395618b7

(8) 0f62d96d6675f32685bbdb8ac13cda7c23436f63efbb9d07700d8669ff12b7c4

(9) 8d5366123cb560bb606379f90a0bfd4769eecc0557f1b362dcae9012b548b1e5 truffle(development)>

The host will be used to configure our wallet to interact with these smart contracts. The accounts provided are funded for the given host network provided.

Using this development environment, we migrate our contracts to this local testnet:

truffle(development)> migrate

This should deploy the contracts defined in the migration files using Ether currency from the first account provided to this local testnet.

Let’s continue to the front-end.

Setting up our Frontend Client

For the frontend, we’ll use simple React bootstrap create-react-app . From the root of the project, use create-react-app to initialize our client.

npx create-react-app client

We should now have two folders in the project root, ./blockchain and ./client . The directory client should now contain a starter React app.

client

|_ public

|_ src

To interact with Ethereum smart contracts on a network, we use the library web3 . Let’s add web3 to our project.

cd client

npm install web3

For the client to interact with a given smart contract, it needs a template of expected inputs and outputs for functions in a smart contract. This template is provided in the ABI.

The ABI of a given smart contract is created when a smart contract is compiled in Truffle. If we go back to the blockchain directory, if your contracts have been compiled, there should be a path build/contracts containing a JSON file of the compiled contract. Within the JSON of our SimpleStorage contract, there should be a field named "abi" holding the contract’s ABI object, which is an array of objects for each function in a contract. We’ll copy that, and hold it in our client.

In a new file abis.js . In it, we will save our ABI for export. Let’s create this assuming we may want to save and retrieve multiple ABIs from this file. For each ABI object for a given smart contract, copying and pasting the object from the compiled contract’s JSON file should be enough when it comes to Javascript.

// abis.js export const simpleStorageAbi = [

{

"constant": false,

"inputs": [

{

"internalType": "uint256",

"name": "x",

"type": "uint256"

}

],

"name": "set",

"outputs": [],

"payable": false,

"stateMutability": "nonpayable",

"type": "function"

},

{

"constant": true,

"inputs": [],

"name": "get",

"outputs": [

{

"internalType": "uint256",

"name": "",

"type": "uint256"

}

],

"payable": false,

"stateMutability": "view",

"type": "function"

}

]

We can now start writing the code to connect React to Ethereum.

In App.js we’ll import web3 and our ABIs, and ready Web3. In addition, we’ll setup for React Hooks.

// App.js import React, { useState } from 'react';

import Web3 from 'web3';

import { simpleStorageAbi } from './abi/abis';

import './App.css'; const web3 = new Web3(Web3.givenProvider);

Given provider is the default Web 3 provider, a.k.a. smart contract compatible Ethereum wallet, provided by the user’s browser. In many cases, this may be the Metamask browser extension.

We can start by initiating the smart contract object we’ll use to interact with our deployed SimpleStorage smart contract.

// contract address is provided by Truffle migration

const contractAddr = '0x97EaC1d4C5eA22dE6ba7292FA5d01a591Aac83A7';

const SimpleContract = new web3.eth.Contract(simpleStorageAbi, contractAddr);

The contract address is the address of SimpleStorage provided by Truffle when we performed migrations. We then create a new contract instance called SimpleContract by passing the ABI simpleStorageAbi and contract address contractAddr into new web3.eth.Contract() . This contract instance will be used to call and send smart contract actions.

Let us initiate Hooks to store the variable we’ll send to our contract, and receive from our contract.

// ... App.js function App() {

const [number, setNumber] = useState(0);

const [getNumber, setGetNumber] = useState('0x00');

The string '0x00' is the hexadecimal form of “0.”

Before adding any functionality, we’ll start by configuring a form for displaying and pushing our data.

// ... App.js return (

<div className="App">

<header className="App-header">

<form onSubmit={handleSet}>

<label>

Set Number:

<input

type="text"

name="name"

value={number}

onChange={ e => setNumber(e.target.value) } />

</label>

<input type="submit" value="Set Number" />

</form>

<br/>

<button

onClick={handleGet}

type="button" >

Get Number

</button>

{ getNumber }

</header>

</div>

);

We save getNumber as a hexadecimal string in the Hook as that’s the form the smart contract will return our number in. We convert the hex form into a number using the Web3 tool hexToNumber() . By default, we set getNumber to display “0.”

Our app should now have a form that can save a number, and get a number. For now, we’re only focusing on functionality.

The form and button call the functions handleSet() and handleGet() . We will create those functions now.

In Ethereum smart contracts, there are two kinds of functions, or smart contract actions:

Actions which need gas (or send ETH)

Free actions

Whether an action needs gas depends if we are writing onto the blockchain or reading from the blockchain. Actions that commit information to the blockchain require gas as a fee. Actions that read information stored on the blockchain are free.

We can identify whether a smart contract action requires gas by looking at the access defined when declaring a function. Functions that only read have the following access modifiers:

pure

view

All other actions require gas, as they alter the state of the blockchain.

Contract methods that require gas are called with .send() on our front-end. While contract methods that don’t require gas are called with .call() on our front-end. Currently, no error or warning will be displayed if you use the wrong method, as sometimes you want to test state altering transactions with call() . However, this means the intended functionality will not happen if you choose the wrong type of call. It’s important to know which is the correct type of call for a given function.

If we look at the SimpleStorage contract, we can see which actions require gas.

function set(uint x) public {

storedData = x;

}



function get() public view returns (uint) {

return storedData;

}

The function set() takes in an integer and stores it in the blockchain. It requires gas. The function get() is set to view access, only reading the variable storedData which has been stored. It does not require gas.

For functions requiring gas, the gas must be calculated, and permission to access a user’s Ethereum account to provide that gas must be provided by the user. All other functions don’t require gas and user permissions to execute. To start, we’ll create a call to the action that doesn’t need gas, get() .

const handleGet = async (e) => {

e.preventDefault();

const result = await SimpleContract.methods.get().call();

setGetNumber(result);

console.log(result);

}

The statement e.preventDefault prevents default events for the button.

prevents default events for the button. We asynchronously call the contract method get() on the contract instance SimpleContract with await SimpleContract.methods.get .call() .

on the contract instance with . The Hook setter setGetNumber() stores the number in the state to display in the UI.

For all the other smart contract transactions, our app needs permission to access user funds to pay for gas fees, and any payable functions requesting Ether or any ERC20 token. An Ethereum provider is injected into a user’s browser when a user has Web 3 enabled software, such as Metamask. This provider is accessible by a website through the global variable window in window.ethereum . This provider is called for an app to access permission.

const handleSet = async (e) => {

e.preventDefault();

const accounts = await window.ethereum.enable();

const account = accounts[0];

const gas = await SimpleContract.methods.set(number)

.estimateGas();

const result = await SimpleContract.methods.set(number).send({

from: account,

gas

})

console.log(result);

}

We request a user’s permission to access their wallet by calling window.ethereum.enable() .

. If a user provides permission to access their account, window.ethereum.enable() will return an array of addresses, starting with their currently enabled address, which we save in accounts .

will return an array of addresses, starting with their currently enabled address, which we save in . We isolate a user’s current account by selecting the first address in the list of addresses with accounts[0] .

. We estimate gas required for our smart contract transaction using .estimateGas() on the method we choose within our contract instance.

on the method we choose within our contract instance. We create our smart contract transaction by passing in our function parameters to the smart contract method methods.set() , and estimated gas and user account address to .send() .

, and estimated gas and user account address to . The response is logged to the console for testing purposes.

Note: As this project is now, the entire application could exist in App.js alone. For this reason, the contract and Web3 instance are held at the top of App.js . However, with a more complex app where multiple components require access to the contract or Web3 instance, a state management library may be beneficial for storing these instances, making them accessible everywhere in the app.

Testing our App Locally

Now, we can test the app locally. Run a local version of the React app:

npm run start

This should open the app in localhost:3000.

To interact with Ethereum through the app, we need a wallet that provides signatures for each smart contract action. For this example, we’ll use Metamask as our signature provider, however, more signature providers exist. Metamask is a browser extension that serves as an Ethereum wallet.