Jordi Moraleda

@ledfusion

Dip Dapp Doe — Anatomy of an Ethereum distributed fair game (part 2)

Photo by Mike Enerio on Unsplash

If you are one of the strong readers who made it to the end of my previous article, welcome back! If you are not, I strongly urge you to read the first part and get back after.

Today we are going to deploy the contracts and throw some color on the screen as we design and build a static web site to interact with the dapp. For speed and simplicity we will be using React to render the page, Redux to manage its state and Parcel to develop and bundle the static assets.

Picture by publicdomainphotos

As a reminder, the source code of the article can be found on the GitHub repo:

What are the main differences between a traditional web site and a Ðapp?

  • Traditional web sites connect to a remote API, while Ethereum Ðapps submit signed transactions to the blockchain using Web3.
  • Traditional web sites are hosted in centralized servers and updated can be rolled out at any time, while Ðapps are mode of static files identified by an immutable hash.
  • Traditional web sites need full net connectivity and HTTPS/TLS to trust the remote host and enforce data integrity/privacy. Ðapps could be opened from a local folder and transactions could be signed locally now and be relayed to the blockchain by an untrusted peer later.

The key component to interact with the Ethereum blockchain is Web3. It is available in Javascript when using browsers like Mist, when installing a plugin for Chrome / Firefox or when we manually import it in a Node script.

Before we use it to interact with any smart contract, the contract needs to be deployed to a public blockchain. And to do that, we need a wallet.

Deploying the contract

Among the alternatives to deploy contracts to the blockchain, we could use web wallets like MyEtherWallet, desktop wallets like Mist or send a transaction with binary data from Metamask.

In our case we prefer not to depend on human interaction and let a script compile, handle the bytecode, the linking and the transactions.

First, install the Solidity compiler globally on your computer. Go to the contracts folder and compile them:

$ [sudo] npm install -g solc
$ cd blockchain/contracts
$ solcjs --bin --abi --optimize -o ../build *.sol

For every contract, solc will generate an ABI file (JSON with the contract operations) and a BIN file (with the bytecode).

Next, create the folder contracts/deploy. Get into it, create an NPM package.json file and install a couple of dependencies:

$ npm init -y
$ npm install truffle-hdwallet-provider web3

And then, create a couple of JS files that will do the work for us.

A few of things to note:

  • As we saw in the previous post, we need to deploy the library first, link it to the contract and deploy the contract after
  • To link the library ourselves we need to replace a placeholder left by solc in the contract binary by the actual address of the library
  • If the library is already deployed, we can use the existing address and skip deploying again
  • The contract’s bytecode passed to deploy() needs to be prefixed with"0x"
  • Our constructor expects a timeout parameter: by leaving it to zero, a 10 minutes interval will be used
What are the PROVIDER_URI and WALLET_MNEMONIC?

A Web3 provider is the Ethereum API gateway that we will use to relay signed transactions to the blockchain. By now, our transactions will be sent to the Ropsten test network and we are going to use Infura.

In order to prevent abuse, Infura requires to sign up for an Api Key but the service is free to use. Follow the process and copy the Ropsten URL for later.

Getting a wallet

Wallet mnemonics are a friendly way for users to store and import crypto accounts among different wallets. They consists of plain English words that generate the public/private keys of a wallet and allow to restore it anywhere. More info here.

To create an account, open this web site and generate a random set of valid words:

Copy the words and keep them for later.

But if I use the seed they generate, they could go and steal my funds?

To deploy to the Ethereum main net, you can generate your own seed off line by combining 12, 15, 18… of these valid words. Test ether is free to get so we don’t care at this point. We would obviously need to care if the contract creator had a special privilege on it. Otherwise, keep in mind that a normal contract deployment should cost less than 1–2$ on the main net.

Let’s put the URL and the seed words we got into blockchain/env.json:

{
"PROVIDER_URI": "https://ropsten.infura.io/--YOUR-API-KEY-HERE--",
"WALLET_MNEMONIC": "morning evolve industry guitar retire warfare wage wasp busy point dish globe spoil seven uphold"
}

Our script is ready to run. However, if we do run it:

$ node deploy
The account used to deploy is 0x70cBDCC3C3A86c56deCBEcd3D4D5EE4017F65510
Current balance: 0
Deploying LibString...
Unable to deploy: insufficient funds for gas * price + value

We created the account, but have no money yet to pay for gas. How do we get any Ropsten ether if no exchange works with it? By using the Ropsten Faucet: https://faucet.ropsten.be/

Copy the address above 0x70cBD..., paste it into the form field and submit it. After a few seconds, your balance should be more than enough to pay for transactions gas.

Getting the contract deployed

Drum roll…

$ node deploy
The account used to deploy is 0x70cBDCC3C3A86c56deCBEcd3D4D5EE4017F65510
Current balance: 98802321000000000
Deploying LibString...
- LibString deployed at 0x5184C01F47FAE8869C154771b7a3C16FcDae2b38
Deploying DipDappDoe...
- DipDappDoe deployed at 0x232d56C9D7C8298f0ECFCf71a187CBAdDd85439c

Yay! Our contract is now carved in stone on the Ethereum test network:

Write down the DipDappDoe address, because that’s where our transactions will be sent to.

Let’s check that they just work. Copy the following into a local script blockchain/deploy/check-deployment.js and replace the content of CONTRACT_ADDRESS by your actual deployment address:

Above, we’re just creating a game and printing the current info of the game:

GAME CREATED 0
Result {
'0': [ '0', '0', '0', '0', '0', '0', '0', '0', '0' ],
'1': '0',
'2': '1000000000000000',
'3': 'James',
'4': '',
cells: [ '0', '0', '0', '0', '0', '0', '0', '0', '0' ],
status: '0',
amount: '1000000000000000',
nick1: 'James',
nick2: '' }

We get two versions of the same return data, but that contains the data that we care about. Cool!

Let’s note one thing, though. If you recall the Javascript tests that we wrote with Truffle, you may notice that function invocations and transaction results look different. We need to be aware of this, because Truffle may be using a different Web3 version than the one we get from NPM.

With that said, it’s time for a web to interact with the smart contract!

Ðapp frontend architecture

Unless our game players become expert developers, it would be unlikely for anyone to play DipDappDoe without a UI. Let’s craft a static web page so that they can play like human beings.

First off, inside the web folder, let’s start a NodeJS package.json file and install a few dependencies:

$ [sudo] npm install -g parcel-bundler
$ npm init -y
$ npm install react react-dom react-router-dom redux react-redux redux-thunk web3

Let’s create web/src/index.html with the following starter HTML:

Then, create Javascript file web/src/index.js referenced from index.html with a dummy instruction to check that it works:

alert("HELLO DIP DAP DOE");

Next, run the ParcelJS bundler:

$ parcel src/index.html

And visit http://localhost:1234/:

Frontend overview

Next, we need to define the global structure of our site:

  • When Web3 is not available, we will ask the user to install MetaMask for Chrome/Firefox
  • When Web3 is locked, we will ask the user to unlock it
  • Otherwise we will show the main view, with the list of open games
  • When we create or accept a game, we will jump to the game view

The main view will allow to join available games, as well as creating a new one. The game view will display the Tic-Tac-Toe board, as well as the Withdraw button and the game timers.

In a normal project, we will be working side by side with a designer, but in today’s article, our goal is to demonstrate the dapp’s technology. To the extent of my abilities, this is the design that I have come up to:

Main view and creation view design (Mobile) — Background image by Max Pixel
Main view with game and creation cards design (Desktop) — Background image by Max Pixel
Game view design (Mobile) — Background image by Max Pixel
Game view design (Desktop) — Background image by Max Pixel

Let’s add some code to start shaping our layout:

This is a very simple skeleton with basic detection of Web3 support to show dummy messages accordingly. The full casuistry is much deeper than the example above. Detecting the current browser and guiding the user is a mandatory UI/UX step but this is a topic for another article.

As soon as our views spread into separate files and different components need to work with the same state, we will realize that passing the local state from app.js to all of of its children is not viable.

That’s where redux comes into play. Redux provides global state management for React apps, so that any component can dispatch actions to modify the state and retrieve the subset of information that it needs. More about Redux here.

Let’s import the library, create a store and add a couple of reducers to the project:

Let’s get back to web/src/app.js. We left the main component with a few local state variables.

Now that we have a global state manager, let’s refactor our code to dispatch actions as soon as we become aware of Web3 events.

The connect(...)(App) wrapper injects thedispatch function and theaccounts/status reducers’ state into the component’s props. As soon as its state changes, Redux will trigger a re-render of the component.

If we refresh the web at this point, we will see the message “Please, install Metamask for Chrome or Firefox”.

If you haven’t done already, visit https://www.metamask.io/ and install the plugin in your browser. Feel free to import your wallet with the mnemonic you generated earlier. If you refresh again, you should see “Main View” text, which is our main view’s placeholder. So there we are!

A contract wrapper

We compiled and deployed the contract before. Now we need to tell Web3 again about the contract. This time, we only need the deployed address and the contract’s JSON ABI.

Since the ABI is already compiled, a clean thing to do is to link it from the already compiled file. Any contract operation added in the future will automatically reflect on the other side too.

$ mkdir web/src/contracts
$ cd web/src/contracts
$ ln -s ../../../blockchain/build/__contracts_DipDappDoe_sol_DipDappDoe.abi dip-dapp-doe.json

Also, let’s create and export a DipDappDoe contract’s instance:

Our contract is ready to be interacted with and our web app is capable of handling global state changes. Now it’s time for UI.

UI components

Let’s do some tweaks to the UI by adding React Media and Ant Design. React Media will be useful to render content targeted for mobile or desktop browsers. Ant Design will provide us with nice widgets, dialogs and UI notifications.

$ npm install react-media antd

Our main view skeleton would look like this with them in action:

As you can see:

  • We are retrieving openGames via the props we tell Redux to inject
  • We are using a media query to detect whether we will render the mobile components or the desktop ones
  • We also use the grid system from antd

After a bit of work, markup and styling, the main page will look like this:

Desktop version of the main view
Mobile version of the main view

The main difference will be that in order to create a game in the mobile version, we will need to tap the start button to reveal the creation card.

How would our first contract invocation look like?

We have added a new action called ADD_CREATED_GAME in the status reducer. When we create a game we need to store the random number and the salt in order to confirm it later.

Web3, MetaMask and events

Problem: Because of a limitation of MetaMask, we can not subscribe to events because they work on top of Web Sockets. But MetaMask only supports HTTP providers.

This leads us to using two Web3 instances in the meantime:

  • One instance using Infura as the Web Socket provider to listen to the contract events
  • The MetaMask instance to sign and launch transactions from our personal wallet
Warning: At the time of writing, Web3 version 1.0.0-beta-34 and beta.35 have issues in connecting to Infura via WS. Web3 beta 36 is expected to address the issue, but meanwhile, we will need to downgrade to Beta 33.

So we will have a read-only Web3 instance and a read-write one. Wouldn’t it be nice to wrap the logic into a helper and provide the singleton to everyone? No need to open many Web Sockets if a single instance could do the same job.

We will need to refactor our code and make sure that event listeners and transaction senders use the appropriate instance. But let’s do it!

  • Code where web3 was used, now invokes either getInjectedWeb3() or getWebSocketWeb3() instead.
  • Similar thing with the contract instance. Contract event listeners can retrieve getDipDappDoeInstance() and signing transactions can be done with getDipDappDoeInstance(true).

Now our event listeners look like:

const DipDappDoe = getDipDappDoeInstance()
this.creationEvent = DipDappDoe.events.GameCreated({
// filter: ...
fromBlock: this.props.status.startingBlock || 0
})
.on('data', event => this.onGameCreated(event))
.on('error', err => message.error(err && err.message || err))

As you see, we ask for events at the block number where we loaded the dapp or later.

Updating a contract

Shouldn’t we filter events to get only those about active games that affect us?

Absolutely, listening to just all events would be inefficient. To remain minimal, we need to filter by the opponent’s address. And for this filter to work, we need to add it as a parameter to the existing events.

What that means:

  • Making the change (quick)
  • Adding testcases (simple)
  • Ensuring that testing works (automated)
  • Compiling and deploying again (automated)

Luckily for us, the effort spent into TDD pays off with a seamless update. Now our events look like that:

GameCreated(uint32 indexed gameIdx);
GameAccepted(uint32 indexed gameIdx, address indexed opponent);
GameStarted(uint32 indexed gameIdx, address indexed opponent);
PositionMarked(uint32 indexed gameIdx, address indexed opponent);
GameEnded(uint32 indexed gameIdx, address indexed opponent);

On GameCreated we will just want to refresh the list of games, so there is no need to add any parameter.

After rerunning our deployment script, the new contract is deployed:

$ node deploy
The account used to deploy is 0x70cBDCC3C3A86c56deCBEcd3D4D5EE4017F65510
Current balance: 93111123000000000
Deploying LibString...
- LibString deployed at 0x5eF66e8df15eC7940639E054548a0d7B95535529
Deploying DipDappDoe...
- DipDappDoe deployed at 0xf42F14d2cE796fec7Cd8a2D575dDCe402F2f3F8F

After the update

When we tell our frontend to attach to the new instance, what will happen?

  • The current logic, transactions, games and money will remain in the old instance forever
  • We have no special control over it; we could have coded a mechanism to allow the creator kill() it or refund the money but it’s not the case
  • The only way now is to let timeout’s expire and withdraw any deposits from the player accounts
  • As soon as we update the frontend, users will be directed to a new contract with fresh data
  • There are ways to fork from one contract and allow the successor to take over the legacy state, but this would allow for a whole article on its own

Since we are developing the dapp, this is no issue at all now.

Event handling

Back to the events! Now we can start setting the opponent parameter and filter the emitted events.

How would it look now?

let event = this.DipDappDoe.events.GameAccepted({
filter: { opponent: this.props.accounts[0] },
fromBlock: this.props.status.startingBlock || 0
})
.on('data', event => this.onGameAccepted(event))
.on('error', err => message.error(err && err.message || err))
// later on
event.unsubscribe()

In places like the main view, we will want to be notified in any case. When anyone accepts, the game will disappear from the available games and we will want to know when to reload.

However, in the game view, we will want to be notified only when a game of ours is accepted, started, updated or ended. Only in this case, we will make use of the filter.

UI recap

After the tools to launch transactions and receive event notifications are ready, what’s to be expected from the frontend?

In the main view:

  • Whenever someone creates a game, we want to update the list of open games
  • Whenever a game is accepted, we need to update the list so that it doesn’t appear anymore
  • When we create a new game, we want to jump into its corresponding game screen
  • When we accept an existing game, we want to jump to the game screen as well

So, in the game view:

  • When an opponent accepts our game, our dapp should update the game status, retrieve our random number and salt and confirm the game for us
  • When our opponent confirms, the game status should be updated and the player that can start should be told to mark a cell
  • When our opponent marks a cell, the game status should be updated too, and we should be notified that it is now our turn
  • When the game ends, the game status should be refreshed and both players should be notified
  • Whenever a withdraw can be made, the button should be visible to the affected users

Summary

At this point, we have a clear image of the frontend’s architecture, how we should deal with events and what needs to be done at every stage of a game. We have also seen a few changes that the smart contract should include in order to improve the user experience.

What happens to Test Driven Development on the frontend?

So far, we have been exploring how to assemble a React+Redux application in order to get Web3 to connect to the blockchain and have the relevant state updated.

Now that the skeleton is ready, again, we should write the specs first and work the functionality after.

Shouldn’t you have started with TDD’s at the very beginning?

Sure, but in this case it’s quite helpful to showcase the frontend architecture instead of jumping into it with no introduction.

In normal frontend testing we can simulate clicks and events, but how can we simulate and automate MetaMask confirmations?

This is what we are going to explore in the third part of the article, along with bundling the app and publishing it with IPFS.

This concludes part #2 on our journey to build a distributed application running on the Ethereum blockchain.

The code featured on the article can be found in the GitHub repo:

If you found the article useful, clap 👏, smile 😊, like 👍 and share with the same 💚 as I wrote it.

After you take a well deserved break, part 3 is awaiting you, here:

Photo by Mike Enerio on Unsplash

More by Jordi Moraleda

Topics of interest

More Related Stories