A Gentle Introduction to Building a Full Stack DApp on Ethereum — Part 1

In this tutorial, we’ll build a simple dapp (decentralized application). The goal is to introduce all the pieces of the tech stack and show how they work together. We won’t go deep into any single technology, but instead cover just enough to start as a base for your own project. This dapp will simply store and retrieve user input text on Ethereum and IPFS and use uPort for login.

The tutorial is broken down into two parts:
Part 1— Writing the smart contract in Solidity, Truffle for compiling and migration and a service to use for our frontend.
Part 2— Using React, uPort and IPFS to integrate with the smart contract.

We’ll be using the following tech stack:
React - Front end library
Solidity - The language used to build smart contracts that runs on Ethereum
Truffle - Framework for compiling, migrating and testing smart contracts.
IPFS - Decentralized storage
uPort - Identity management, digitally sign transactions and easy login to dapps.

Knowledge of React is not required but nice to have. We’ll build a service that can easily be used in any front end framework.

The dapp architecture

To follow along, download the dapp repo here: https://github.com/zerostatic/dapp-starter-kit

Once downloaded, install the dependencies:

yarn install

And to run the app:

yarn start

Part 1:

In part one, we’ll write a smart contract in Solidity using Remix. We’ll use Truffle for compiling and migration and a create a frontend service that we can use to interact with the contract.

Solidity is the language used to write smart contracts that run on the Ethereum network. Our Solidity smart contract will be very basic and contain just two functions. One function that will save an IPFS hash and timestamp, stored under the users address. The other function will return the IPFS hash and timestamp for a specific users address. More on IPFS later.


We will write Solidity code using the online tool Remix. Remix is an online IDE that makes writing and debugging Solidity very easy. You can find the Remix IDE here: https://remix.ethereum.org

In Remix, create a new file by selecting the “plus” icon in the upper left. Then name the file: MyDetails.sol.

Select for new file

Next, on the right side of the screen, select “run”. Then next to “Environment” select “JavaScript VM”. This will allow you to test and run the smart contract all within the browser.

First in the code, we need to tell the compiler which version of Solidity to use. Add this to the top of the file:

pragma solidity ^0.4.18;

To create the contract class, add the following:

MyDetails {

Within the class we’ll add two mappings. One to store the IPF hash and one to store the timestamp. Inside the class definition, add the following two lines:

mapping (address => string) ipfsHashes;
mapping (address => uint) timestamp;

What is a mapping? Mappings can be thought of as hash tables. To define a mapping, you first set the type as mapping. Next to this we set the key and value types. In our first mapping, the key is an address and type is a string: (address => string) In other words: Each entry in the mapping will store a string under a unique address. Last we set a variable name for the mapping: ipfsHashes

In the second mapping, we’ll store the timestamp as a uint (unsigned integer) also under the users address.

Next is the function for setting our data:

function setHash(string ipfsHash) public {
ipfsHashes[msg.sender] = ipfsHash;
timestamp[msg.sender] = now;

This function receives the ipfs hash string as an argument. Then we store the ipfs hash under the users address using our mapping. Shown here:

ipfsHashes[msg.sender] = ipfsHash;

In Solidity, msg.sender is automatically set as the address of the sender, so we can use it as the key to the mapping and store the ipfs hash as the value.

The second line in the function is:

timestamp[msg.sender] = now;

Here we also use the user address as the key and setting the value now,which will save the timestamp.

Next is the function to retrieve data:

function getHash(address account) public view returns(string, uint) {
return (ipfsHashes[account], timestamp[account]);

This function accepts a address as the argument and returns the ipfs hash and timestamp that are stored under that users address.

In solidity we need to set a few visibility key words: public which means it can be called externally from the contract and view which means this function is read only.

Solidity also requires that you define the return type(s) Here we specify: returns(string, uint) Which means we are returning a string and a uint (an ipfs hash and timestamp)

Here is the full code of our smart contract:

We can now test the code in Remix by selecting “Create”

Then below that you will see a listing of the getters and setters in your contract:

Go ahead and enter any string next to “setHash” Be sure to enter the value in quotes to denote a sting. Then select “setHash”.

Next, enter the address account next to “gethash”. The address is listed at the top right next to “Account” — select the copy icon to get the address. Click “getHash” and you should see the string you entered when setting the hash.

Now that the contract is finished. Let’s see how we can integrate it into our project.

Truffle is a framework for compiling, migrating and testing contracts. It also can handle network management for deploying to different private and public networks.

In the project files, open the directory at src/ethereum . This is the directory that we’ll be using to compile and migrate the Solidity smart contract. The ethereum directory already contains all the directories needed for compiling and migrating. Lets take a look how this was done. First you’ll need to do a global install of Truffle:

npm install -g truffle

The directories: build, contracts, migrations and test were automatically created by running:

truffle init

The following two files were also auto generated when truffle init ran: contracts/Migrations.sol and migrations/1_initial_migration.js Truffle requires you to have this initial Migrations contract in order to use the migrations feature. For most projects, this contract will be deployed initially as the first migration and won’t be updated again.

Now take a look at file: contracts/MyDetails.sol This is the Solidity file we created above using Remix. To mark this for migration, I’ve added a file at migrations/2_myDetails_migrations.js The naming of this file is important. It starts with the number 2 because we want this to compile after the first contract.

Here is the content of this file:

var Mydetails = artifacts.require("./MyDetails.sol");
module.exports = function (deployer) {

This is a standard Truffle migration boilerplate which points to the Solidity file we want to migrate.

Now let compile the contracts. In the terminal make sure your current directory is: src/ethereum Then run:

truffle compile

When finished compiling, you’ll see the compiled json files in directory: build/contracts We’ll use these files later to interact with the contract.

Next we’ll set up the configuration file for deploying the contract. We’ll deploy to the Rinkeby test network because this is the network that uPort currently uses. uPort will be available soon on other test nets and later on the main Ethereum network.

We’ll be using Infura to connect to the Rinkeby network. First, go to Infura and sign up: https://infura.io/signup Once signed up, you will receive an email with a list of Infura url’s that are linked to your account. Copy the url provided for Rinkeby.

Open the file truffle.js Then configuration is already set up and looks like this:

var HDWalletProvider = require('truffle-hdwallet-provider');
var mnemonic = 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat';
module.exports = {
rinkeby: {
provider: new HDWalletProvider(mnemonic,
network_id: 5,
gas: 6712388,
gasPrice: 10000000

Notice the import for truffle-hdwallet-provider This is required for configuring deployments using Truffle. This is installed with command:

npm install --save truffle-hdwallet-provider

Replace the rinkeby.infura url with the url you received after signing up for Infura.

The gas and gasPrice set in the config file work at the time of writing but you may need to adjust for futures cases.

Now we’re ready to migrate the smart contract to Rinkeby. In the terminal enter:

truffle migrate --network rinkeby

The migration will take 15 seconds or more. If successful, we’re ready to start interacting with the contract!

Contract Sevice:

Next, look in the project directory at the /service directory. Inside is service I’ve already created named: DetailService.js We can use this to interact with the contract. Open this file and we’ll walk through it.

First, notice the web import at the top

import { web3 } from "../util/Uport";

web3.js is a collection of libraries which allow you to interact with a local or remote Ethereum node. Because we’re using uPort, we’ll use uPorts implementation of web3 for the contract interactions. If you would like to use a different provider like MetaMask instead of uPort, you could use the standard version of web3 here.

The next import shows we’ll be using truffle-contract Which was installed using

npm install truffle-contract --save

Truffle contract makes it easy to interact with the contracts by providing better control flow and using promises (i.e., transactions won’t finish until you’re guaranteed they’ve been mined). As well as other features listed here.

Next we are importing the compiled contract that we created earlier with Truffle:

import MyDetails from '../ethereum/build/contracts/MyDetails.json';

Then we configure truffle-contract and set the web3 provider:

const DetailsContract = contract(MyDetails);

The first function simply returns an instance of the contract:

const getInstance = async () => {
const instance = await DetailsContract.deployed();
return instance;

The next function will call a transaction on the contract. Here we pass an address and a IPFS hash. The hash gets passed as an argument, and the address gets set as the from field which our contract receives as msg.sender Notice the function name setHash is the same name we use in the Solidity contract.

export const setContractHash = async (account, hash) => {
const instance = await getInstance();
const items = await instance.setHash(hash, { from: account });
return items;

The second function is used to get the IPFS hash. Here we pass the address that will be used to look up the stored hash:

export const getContractHash = async (account) => {
const instance = await getInstance();
const items = await instance.getHash(account);
return items;

That’s all we need for the service. You could use this service in any front end application, but for this tutorial, I’ll show you how to use it with React.

On to Part 2!

All code and opinions expressed here are my own and not the views of my employer.