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.
Dip Dapp Doe — Anatomy of an Ethereum distributed fair game (part 1)_Today we will start building a Distributed Application from the ground up. It will use the Ethereum Blockchain…_hackernoon.com
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:
ledfusion/dip-dapp-doe_dip-dapp-doe - Distributed app featured in a Medium article_github.com
What are the main differences between a traditional web site and a Ðapp?
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.
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:
solc
in the contract binary by the actual address of the librarydeploy()
needs to be prefixed with"0x"
What are the
PROVIDER_URI
andWALLET_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.
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 deployThe account used to deploy is 0x70cBDCC3C3A86c56deCBEcd3D4D5EE4017F65510Current 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.
Drum roll…
$ node deployThe account used to deploy is 0x70cBDCC3C3A86c56deCBEcd3D4D5EE4017F65510Current 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 0Result {'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!
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/:
Next, we need to define the global structure of our site:
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!
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.
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:
openGames
via the props we tell Redux to injectantd
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.
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:
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!
web3
was used, now invokes either getInjectedWeb3()
or getWebSocketWeb3()
instead.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.
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:
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 0x70cBDCC3C3A86c56deCBEcd3D4D5EE4017F65510Current balance: 93111123000000000
Deploying LibString...- LibString deployed at 0x5eF66e8df15eC7940639E054548a0d7B95535529
Deploying DipDappDoe...- DipDappDoe deployed at 0xf42F14d2cE796fec7Cd8a2D575dDCe402F2f3F8F
When we tell our frontend to attach to the new instance, what will happen?
kill()
it or refund the money but it’s not the caseSince we are developing the dapp, this is no issue at all now.
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 onevent.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.
After the tools to launch transactions and receive event notifications are ready, what’s to be expected from the frontend?
In the main view:
So, in the game view:
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:
ledfusion/dip-dapp-doe_dip-dapp-doe — Distributed app featured in a Medium article_github.com
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:
Dip Dapp Doe — Anatomy of an Ethereum distributed fair game (part 3)_Today we are going to follow the Test Driven Development methodology on our frontend, along with Web3…_hackernoon.com
Photo by Mike Enerio on Unsplash