Ethereum ÐApps Programming Distilled — Part 4

Creating and deploying the frontend with HTML, JS, Web3 and MetaMask.

Marco Bellinaso
6 min readMar 6, 2018

This is a multi-part article. Here are the links to the other parts:

Once the Smart Contract (which is the app’s business logic) has been deployed, it’s time to create a frontend for it, so that users can interact with it. At the moment, the most common (only?) approach is to create a webpage that uses html/css for rendering the UI + javascript and web3.js to work with the data. Web3 is an “Ethereum compatible JavaScript API which implements the Generic JSON RPC spec”, which means that it allows you to connect to Ethereum and call Smart Contracts to read/write data.

Sending a transaction or reading data means interacting with a blockchain node, however you can’t really pretend that your potential users dedicate 100+ GBs on their machine to have a full node and use Mist as a browser just to enjoy your app. MetaMask is a Chrome/FireFox extension that comes to rescue: you install it with a couple of clicks from the Chome Web Store and it sits there on your toolbar until you want to run a transaction, at which points it opens a popup asking you to create/select an account, potentially fund it with money, and confirm the transaction. What it then does in background is sending the transaction to some externally-hosted blockchain nodes, provided by a company called Infura. Reading data also works by requesting it to those nodes, even if in this case it’s even more transparent to the users because not being a transaction you don’t see any popup from MetaMask.

I recommend watching the official intro video, where they explain how it works and how to set it up:

From a developer’s point of view, MetaMask also injects a Web3 instance into your page, already configured with a provider. The first thing a webpage does when loaded is therefore checking that web3 is present, which means that MetaMask is available on the browser, or show an error message otherwise:

// Import libraries
import { default as Web3} from 'web3';
if (typeof window.web3 !== 'undefined') {
window.web3 = new Web3(web3.currentProvider);
} else {
alert('You must have MetaMask installed');
}

If Web3 is available, the next thing you do is loading your Smart Contract from an address on the blockchain, and this is super easy thanks to the truffle-contract helper library (part of the Truffle Suite of course):

import { default as contract } from 'truffle-contract';// Import the contract artifact and turn it into usable an abstraction
import messagestorage_artifacts from '../../build/contracts/MessageStorage.json';
var MessageStorage = contract(messagestorage_artifacts);

MessageStorage.json is the ABI file, described before, which is generated by Truffle after you compile and deploy. It contains everything truffle-contract needs to load the contract and know how to preparare the raw function calls and transactions. With than it place, it’s now easy to call the contract’s postMessage function (shown before in the Smart Contract section) with some data:

function postMessage() {
MessageStorage.deployed().then(function(instance) {
return instance.postMessage(
'John', 'Hello world', false, true, 0,
{from: web3.eth.defaultAccount, value: fee});
}).then(function(result) {
watchingForEvents = true;
// show some confirmation to the user
...
}).catch(function(e) {
console.log(e);
});
}
...
<button onclick="postMessage();">POST NEW MSG</button>

All the calls are asynchronous, and “JavaScript Promises are used for easier callback handling and chaining of calls (you could also use async/await as a more modern alternative). The code above passes hardcoded data to simplify things, but of course the actual page on msgblocks.com has a form like this:

When the user clicks Submit, here’s the MetaMask popup that asks to confirm the transaction:

Note that on the callback function I set watchingForEvents=true. When the page loads, the script also start listening for MessageStored events, broadcasted by the contract when a new messaged is saved in its messages array. However, since this event is raised also for messages stored by other clients, the postMessage function uses watchingForEvents to indicate that from that moment it’s actively interested in handling new events, which presumably are for the new message being posted by the current user. That’s not safe enough though, and when the event is intercepted the page also checks that the event was actually generated for a new message created by the current user (if you check the Smart Contract’s code, you’ll see that the event has a from parameter and a msgId parameter). Here’s the code:

// Listen for events
MessageStorage.deployed().then(function(instance) {
var event = instance.MessageStored();
event.watch(function(error, result) {
if (watchingForEvents &&
result.args.from == web3.eth.defaultAccount) {

console.log('New message saved: ' + result.args.msgId);
}
});
});

Finally, here’s how to call getMessage with a message ID. If you recall, the function returns a tuple with all the message’s details, which is mapped to an array in JS:

function loadMessage(msgId) {
MessageStorage.deployed().then(function(instance) {
return instance.getMessage(msgId);
}).then(function(value) {
var sender = value[0];
var senderName = value[1];
var content = value[2];
var isPwdProtected = value[3];
var allowsReplies = value[4];
var parentId = value[5];
var blockNumber = value[6];
alert('Content: ' + content);
});
}
...
<button onclick="loadMessage(42);">READ MSG WITH ID 42</button>

This is of course a simplified version of the code used in msgblocks.com, but it’s very similar at its core. The actual page supports password-protected messages, shows replies, and offers many more details. This is how it looks (or check it out live):

Once the webpage is ready you can load it on the browser, but it must be served from a local webserver, rather than straight from the file system. The easiest option is to download lite-server, cd into the project’s folder, and execute “lite-server” from the command-line. Your pages will now load from localhost:3000/somepage.html, and the other nice thing is that lite-server installs a browser extensions that understands when something changes on the file system and automatically reloads the page in the browser. Convenient, right?

NOTE: I used the good old jQuery to interact with the page’s DOM, because I wanted to do it quickly and the pages were very simple. For something more complex, or if you just want (or are already used) to work with a more modern approach based on React+Redux, check out Drizzle, also from the Truffle team!

Fun Fact: while testing the app on my local machine, all of a sudden I stopped getting the MessageStore event notifications. After getting tired of banging my head against the wall, I found this bug report on GitHub: yes, it seemed like a new version of MetaMask (that I got installed automatically because Chrome extensions are updated silently) broke it. After some time someone posted a fix, and I had to download the MetaMask’s source code, compile it locally and make Chrome use it rather than the official version on the Chrome Store in order to keep working on the app with a functioning event handler. The MetaMask extension that is now live in the store is fortunately fixed (and everyone has got it, again thanks to the auto-update)…but, considering how important events are in this architecture, this is just to tell you how all this is still not super mature yet…

Who am I / what do I do? I proudly work as a Solutions Architect in the Mobile Team @ ASOS.com (iOS app | Android app), and we’re always looking for strong, friendly and talented developers that want to have an impact on how tens of millions of customers shop online. ASOS is the biggest online-only retailer in UK and, let’s be real, the best tech+fashion company in the world. Some of the technologies we use are Swift for iOS, Kotlin for Android, React and Node on the web frontend, .NET and Azure on the backend. If that sounds interesting to you, and you happen to live in beautiful London (or are willing to move here…after all it’s the best city in Europe except for some in Italy!), do get in touch with me!

--

--

Marco Bellinaso

Principal Architect @ASOS.com (and iOS / full-stack dev for fun)