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

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:

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:

Facebook Comments

More Stuff

Decent is proud to be a founding member of the Blo... Decent’s the first healthcare project joining leaders like Coinbase and Digital Currency Group to help shape the future of crypto.Policy matters. W...
How to Explain Bitcoin to an Asshole 2 Simple Ways to Explain Where Bitcoins Value Comes From I get a lot of questions about where the value of Bitcoin is derived. I love this question. M...
How Crypto Saved My Life in July 2017 “…you would be crazy to gamble on infinity like that.” What’s going on here and where’s the Intro/Table of Contents? On Saturday, July 22, 2017, I...
Making sense of the Blockchain Landscape in 2018 BitCoin, Ethereum, Smart Contracts, Proof-of-Stake, Cardano, IOTA, ERC20 Tokens, Casper and Solidity. The Blockchain The BlockChain technology is a ...
Spread the love

Posted by News Monkey