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

In part 2 we’ll use React and uPort to interact with the smart contract, and IPFS for data storage.

Matt Wallace
Coinmonks
Published in
7 min readMar 10, 2018

--

This is Part 2 of a series. Part 1 can be found here.

The structure of the React app is fairly simple. The main React components are App.js which contains the login functionality and Dashboard.js where you can enter and view your data.

About uPort:

uPort is an identity management system which also make it easy to login and interact with Ethereum dapps. uPort stores your private key securely on your phone and uses this so sign transactions. Read more here.

To get started with uPort, download the uPort app on you mobile device and create your identity.

Back in the project files. Look at file: util/Uport.js This is the code that lets us communicate with uPort, get the users identity and log in to the application.

uPort uses the uport-connect npm package that has already been imported using:

npm install uport-connect --save

At the top of Uport.js we import uport-connect then created an instance of uPort:

import { Connect, SimpleSigner, MNID } from 'uport-connect'const uport = new Connect('React uPort IPFS DApp', {
clientId: '2omYPjiPUiR6jyNdSZRHgbpn9PxiVqbtVsR',
network:'rinkeby',
signer:SimpleSigner('ab1d56711bcf6f7ded8a8b7f768d59e8f3d3a168b75b037848aa142b93e4998f')})

The keys above are from a sample app that I’ve registered with uPort, but you will want to create your own identity for your app here: https://developer.uport.me/myapps.html. After adding your app, you will be presented with code like we have above that contains your apps keys. You can copy and paste this over the code above.

For sake of simplicity, we’re storing these keys in the fronted app, but in production you would store these securely on a server. See uPort docs on how to do this.

The next block of code is for requesting credentials:

const initAccount = async () => {   const user = await uport.requestCredentials({
requested: ['name', 'country', 'avatar'],
notifications: true
})
// get user details
const decodedId = MNID.decode(user.address)
const specificNetworkAddress = decodedId.address
return { specificNetworkAddress, user }
}

When initAccount() is called, uPort will inject html into your page that contains a QR code. You use the uPort mobile app to scan the QR code. When finished, the user object will be populated with your uPort identity details that were requested. This also makes a push notification connection between your phone and the app, so anytime a transaction is initiated from the app, you will be prompted on your mobile device to accept the transaction.

Also note the exports at the bottom. uPort uses it’s own implementation of web3 in order to detect and handle transactions through uPort.

const web3 = uport.getWeb3()
export { web3, uport, MNID, initAccount }

initAccount() is called from App.js when the login button is clicked. As seen in the code below, the React state is then populated with the users identity data. specificNetworkAddress is the users address that we’ll use to pass to the smart contract. Avatar is the users image that was set in uPort. userName is the name set in uport.

handleLogin = async (e) => {
e.preventDefault();
const identity = await initAccount();
this.setState({
specificNetworkAddress: identity.specificNetworkAddress,
userName: identity.user.name,
avatar: identity.user.avatar.uri
})
}

After handleLogin is called and the identity returned, the users name and address appear on the screen and the Dashboard screen appears.

Also take a look at this block of code in App.js:

{this.state.userName ? (
<Dashboard
specificNetworkAddress={this.state.specificNetworkAddress} />
)

This says that when the userName is set in state, display the Dashboard component and pass in the users address.

Before we continue to the dashboard component…Let’s review IPFS:

IPFS is a peer-to-peer hypermedia protocol. Think of it as a decentralized http protocol, where you can store anything that http can: files, images, html etc. Anyone can easily set up a node to be a IPFS host. In our case we’ll use a remote node that Infura has been generous enough to make available to the public.

IPFS files are stored using a hash that we can use to also retrieve those files. The hash is created based on the files contents. If anything in the file changes a different hash is returned, in effect, making the files tamper proof.

IPFS is a perfect match for Ethereum dapps. Storage on Ethereum is expensive. We can mitigate this by just storing the IPFS hash which can be connected to an unlimited amount of data.

Keep in mind that IPFS files are readable by anyone. If you need privacy, you’ll need to encrypt your data in some way before saving to IPFS.

We’ll make use of the handy npm library ipfs-mini to make storing and retrieving IPFS files easy. In the project files, open file: util/IPFS.js

On line 1, we’re requiring ipfs-mini This was installed using :

npm install ipfs-mini --save

Line 2 creates an instance of ipfs-mini and passes in the IPFS address that we will interact with. In our case this is ipfs.infura.io on port 5001 using the https protocol.

The two main functions setJSON and getJSON do just as the names suggest and make use of promises so we can later call these with async/await as seen later.

The full code for IPFS.js is here:

https://gist.github.com/zerostatic/3bcde0ca318386c1b09fa0dfec74bd17

Next is the final component: the Dashboard

Open up file: Dashboard.js In the imports, you’ll see we’re importing the two function we just described above:

import { setJSON, getJSON } from './util/IPFS.js'

We’re also importing a component that displays a loading animation that displays while the transaction is being mined. I won’t go into detail how the loader was created but you should always have a loading animation and/or message for the user when making transactions on Ethereum, because transactions can take 15 seconds or more.

import Loader from "./Loader"

Next we’re importing the two functions from DetailsService.js that was covered previously, in order to interact with the smart contract:

import { setContractHash, getContractHash } from './services/DetailsService';

In the constructor, you’ll see the state properties we’ll be using. With inline comments:

this.state = {
myData: "", // The text the user enters in the textfeild
ipfsData: "", // The text returned from IPFS
timestamp: "", // Timestamp returned from the contract
loading: false // Boolean to used to display loading animation
}

Next, take a look at the handleSubmit function that gets called when clicking submit. We’ll go over the important lines below this:

First we’ll display a loading animation for the user by setting the loading state to true:

this.setState({ loading: true });

Then we’ll store the data in IPFS and get the hash that’s connected to that data:

const hash = await setJSON({ myData: this.state.myData });

This line calls the setJSON function from IPFS.js explained previously. We’re passing it a simple object with a key of myData and a value of the text from the input field: this.state.myData Since we’re using async/await, the function will wait here until either a hash string or error is returned from the IPFS service.

Once a hash is returned, we’ll store that hash in our contract under the users address, as seen in this line:

await setContractHash(this.props.specificNetworkAddress, hash);

If there are any errors, we’ll alert the user and remove the loading animation:

this.setState({ loading: false });
alert("There was an error with the transaction.");

At the end of the function, we’ll call this.fetchData() to display the data we just save to Ethereum via the IPS hash. Let’s take a look at that function here:

The first line in the function, we are calling the getContractHash and passing it our Ethereum address, and waiting for it to return our data. Once the data is returned from the smart contract, we’ll store it the contractDetails variable.

const contractDetails = await getContractHash(this.props.specificNetworkAddress);

Next, we store the IPFS hash and timestamp into variables:

const ipfsHash = contractDetails[0];
if (!ipfsHash) { return } // if no hash store at this address
const timestamp = contractDetails[1].c[0];

Then we fetch the data associated with that IPFS hash:

const details = await getJSON(ipfsHash);

Once, we have that, we set the the users data and timestamp into the state, which in turn renders that data to the screen:

this.setState({ ipfsData: details, loading: false, timestamp })

And that’s a wrap!

You now have the base working knowledge to start you’re own Ethereum dapp!

To dive deeper into these technologies, visit these links:

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.

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

Get Best Software Deals Directly In Your Inbox

--

--