In this chapter, we will build a full DApp from scratch. While we will not be going in-depth on the steps involved, this chapter will help you identify all the components involved in the construction of a decentralized application. In the upcoming chapters, we will focus on each of the different parts, but you can always refer back to these pages to understand how each section fits within the bigger picture.
About Our DApp
We will be creating a global counter implemented as a DApp. Users should be able to view the value of the counter and increase it by sending a transaction to it. Even though this application has no practical uses, it will be of help to go through each of the components of a DApp.
Our Requirements
Our application will hold a single counter as its state, and allow any Ethereum user to increase it through a transaction. At the same time, any user accessing the DApp should be able to see real-time updates to the counter’s value.
The application will not manage any ETH at all and will not have any kind of access control. As long as a user can reach the DApp, they should be able to freely interact with it.
The Smart Contract
Initial implementation of the smart contract backing our sample DApp
Updated implementation to emit an event every time the increase method is invoked
A getter to query the internal state of the contract, value. It is autogenerated by the use of the public keyword when declaring the field. Querying a contract is fast and does not involve sending a transaction, and so it does not require gas or even having an Ethereum account.
A function to modify the state of the contract, increase. These functions require a transaction to be sent, which require ETH to be executed. As such, they require having a funded account.
The Increased event, to listen for updates to the contract state. Any client can request to subscribe to a set of events from a contract, to get notifications of any changes to it.
We will go into detail on smart contracts in the next chapter. For now, these concepts will do in order to build our DApp.
The Architecture
We will build a traditional DApp for this example. The application will be backed by a smart contract in the Ethereum network, which will act as a distributed database for the Dapp’s users. The front-end will be set up as a regular client-side javascript application.
For the glue between the javascript front-end and the Ethereum network, we will rely on a web3-enabled web browser. This is a kind of browser that allows the user to connect not only to the Internet but also to the Ethereum network via a node of their own choice. It also manages the user’s private keys and transaction signing.
The most popular web3-enabled browser is MetaMask,1 which is not a browser per se but a plugin, that provides all the required features. MetaMask keeps track of the user private keys, allows connections to a list of predefined nodes or to a custom one, and prompts the user to accept any transaction that the current page is trying to send on their behalf.
From the developer’s perspective, MetaMask injects a global connection provider to an Ethereum node, and intercepts all transactions for signing them with its own keys.
Setting Up the Environment
Before we start building the DApp, we will set up our development environment, as well as the required tools for interacting with and testing our DApp.
Development Toolchain
The basic setup for our development environment will be the one required to build a simple client-side-only javascript application. Make sure to have nodejs and npm installed2 on your development machine to get started.
By default, ganache immediately mines a new block for every transaction sent, eliminating the time to wait until a transaction is confirmed. This makes it easy for using it as a back end while coding, but keep in mind that a ganache environment will be drastically different, especially in terms of user experience, to a real one.
Web3 Browser
We will now set up a web3-enabled browser, using MetaMask. Remember that there are other web3-ready browsers, but at the time of this writing, MetaMask is the most popular way to interact with DApps.
Start out by installing the MetaMask plugin5 for your browser – it supports Chrome, Firefox, Opera, and Brave. After installation, MetaMask will prompt you to create a password to encrypt your accounts and will present you with the secret backup phrase. Make sure to write down this phrase: in the event that you lose your MetaMask wallet, you can regenerate it using this phrase. Otherwise, all funds contained in it will be irremediably lost.
Warning
Be extra careful when installing MetaMask. Most software related to managing user keys or transactions is prone to be subject to phishing attacks. Always ensure you are accessing the official MetaMask site when downloading.
Ropsten (id 3) is the only proof-of-work-based testnet, which makes it the most similar one to mainnet, but is also very unreliable. As there is not much actual work being done on the network, block times are unpredictable, and the network is highly susceptible to attacks.
Rinkeby (id 4) is a proof-of-authority-based testnet, which means that there are a set of trusted nodes which have the authority to add new blocks to the blockchain. This makes it much more stable and reliable than Ropsten. However, due to limitations of the consensus algorithm used, only Geth clients can connect to Rinkeby.
Kovan (id 42) is similar to Rinkeby, in that it is a proof-of-authority-based testnet, but its consensus algorithm is compatible with Parity clients instead of Geth.
Goerli (id 6) is the most recent testnet set up. It uses proof-of-authority as well, with the advantage that it is compatible with both Geth and Parity clients.
There are several online faucets6 to obtain testnet ETH to play around. Use one of them to request funds for one of the accounts you have just created on MetaMask.
Note
How did you find the onboarding process on MetaMask? If you think it was complicated or a bit long, now think of your users. All first-time users to Ethereum need to go through a similar process, with the additional burden of having to sign up in an exchange to purchase real mainnet ETH to interact with your app, which often requires a full KYC process. This is why user onboarding is such a challenge on Ethereum. There are techniques to mitigate this issue, such as not requiring your users to have an Ethereum account until it is absolutely needed, or alternative onboarding flows built on meta transactions. More on this later in Chapter 7!
Building Our Application
We will start building from the create-react-app template. Make sure to have run all the steps on the “Setting up the environment” section, and you should have a simple javascript app with a handful of files under the src folder, built around index.js. To verify that everything is running smoothly, run npm run start and open your browser in localhost:3000. You should see the default initial screen of the create-react-app package, including a rotating React logo.
Compiling the Smart Contract
Smart contract implementation in Solidity that we will use in our application
The contract’s state, value, defined as an unsigned integer of 256 bits, the default size in Solidity
The getter to access value, generated by the use of the public keyword in the declaration of the field
The increase function to increment value via a transaction
The Increased event used to signal when a modification of value has occurred
Note
The flags needed to output this information from the compiler may change depending on the version you are working with. The preceding code works for Solidity 0.5.1.
The preceding command will generate a file build/contracts/combined.json with all the compilation output. Take a look at it, and we will soon use it to interact with our contract.
Connecting to the Network Via Web3
As mentioned before, we will be using web3.js to connect to the Ethereum network. This requires a web3 provider , which is a small object that knows which node to connect to in order to place calls to smart contracts and send transactions to the network. In other words, as its name implies, the provider provides a connection to an Ethereum node and, from it, to the entire network.
Depending on the library you are working with, the provider is sometimes conflated with the signer. The signer is another component that has the responsibility of signing transactions with the user’s keys, in the case that the keys are not managed by a local node. This is the case for most Dapps, since your average user will not have a node running, but depend on a public one. Because of this, the web3 provider injected by MetaMask acts both as a provider and as a signer. We will review these differences in depth later in the book.
Snippet for creating a web3 object using the MetaMask provider. Note that this code will not work if the user does not have MetaMask installed.
Function for retrieving the user’s current default account
Function updated to handle Ethereum browsers’ privacy mode
Now that we have a web3 object set up, as well as access to the user’s accounts, we will use them to build our interface to the Counter smart contract deployed on the Ethereum network.
The Contract Interface
A connection to the Ethereum network where our contract is deployed
The address of the contract in the network
The specification of the contract’s public functions, also known as the ABI (Application Binary Interface)
Remember that everything in the blockchain is public and indeleble, so once a contract is deployed, it becomes publicly available for all users to interact with and cannot be deleted. This means we can freely use this instance for testing the DApp.
Note
If you do not want to work with that particular contract instance or prefer to work on a different network, there is a deploy script on the code repository that you can use to set up your own instance.
Function to create a Counter web3 contract object. Note that the function does not deploy a new contract, it just creates a javascript object that acts as a wrapper to a contract previously deployed at a specified address
The web3 Contract abstraction is an object that acts as a facade for the actual Ethereum contract. It exposes javascript methods for all its public functions, which get translated into calls and transactions to the network under the hood. We will be using it in the following section to actually interact with the contract.
Code for obtaining a web3 contract instance of the Counter contract deployed on the Rinkeby network. Note that, to avoid hard-coding the address, you can also store the address as an environment variable, and retrieve it from process.env7
Now that we have the means to interact with the deployed contract, we can build the React visual component for our user interface.
Interacting with Our Smart Contract
We will now progressively build our Counter visual component to allow the users of our DApp to interact with it. We will begin by retrieving the current value of the counter, then provide a means to send a transaction to modify its state, and then subscribe to real-time updates to it.
Wiring Our Component
Empty React component for rendering the Counter contract and interacting with it. We will be iteratively adding features to it
Code for the App root component. We use the root App state to store the contract instance, and pass it to the child component as a property
We are relying on the componentDidMount React event to load the Counter contract instance9 and storing it in the component’s state. Only when this instance is available we render the Counter visual component.
Note
By now, you may have realized that we are missing error management in this DApp. For instance, we are not handling the case where the user does not have MetaMask, or if the contract’s address is incorrect, or if the connection to the network is lost. This is a deliberate decision, as the goal is to focus on the happy path and provide a quick overview of what constitutes a DApp. In the upcoming chapters, as we go deeper into each subject, we will also cover everything that can potentially go wrong.
At this point, you should be able to run your application via npm start and check that everything is rendering correctly. Make sure to have MetaMask installed, unlocked, and connected to the Rinkeby network.
Now that we have all of our application wired, it’s time to focus on the Counter visual component itself.
Querying the Contract’s State
Retrieving the initial value of the Counter when the component is mounted
The methods property grants access to all the public methods defined in the contract’s ABI. They are not set at the contract instance itself, to prevent clashing with other methods specific to the web3 contract object.
The value() call does not actually query the network, but simply builds a method call. If the function required any parameters, they would have to be supplied here.
The call() invocation finally issues the query to the blockchain. We will review in the next chapter the difference between querying a method and issuing a transaction, but for now, all we need to know is that call() is used when we want to retrieve data from a contract.
Render method to display the counter’s value. Note that the value is only available after the initial query to the contract returns, so we need to handle the case where the value is not yet at our disposal
By this point, you should be able to reload your application in your browser and see the value of the Counter instance on the Rinkeby network.
Tip
You can double-check the value displayed against the one reported by a blockchain explorer, such as Etherscan.11 Etherscan is a blockchain explorer, a web site that provides a visual interface to addresses and transactions, and is available for mainnet and most test networks. Look for the address of the contract, and under the Read Contract tab, you will be able to check the value of the counter.
Our next step will be to allow the user to increase the value of the counter, by issuing a transaction.
Sending a Transaction
After the previous section, the call to send the transaction should be more familiar. Note that in this case we are using send() instead of call(). This is because we need to actually send a transaction to affect the contract’s state, instead of just querying data from the network.
Updated render method to display a button that increases the counter
Query the counter’s value after the transaction is mined
The send() method returns an event emitter, which allows us to listen for different events in the lifetime of a transaction. For now, we are interested only on the event when the transaction is mined, that is, included in a block in the chain. This event is referred to as receipt, since it corresponds to when the transaction receipt object is available. If we wanted, we could also check when the transaction was actually sent to a node, or when it has reached a reasonable number of confirmations.
Now, even if the updated code does refresh the counter’s value, we need to let the user know what is going on. The transaction’s confirmation takes several seconds, which is definitely more than what a regular web 2.0 application usually takes.
Updated increaseCounter function to keep track of a flag to identify pending transactions and any error returned
Updated render method to display a notification when a transaction is pending or has failed, and disable the button until the transaction is finished
Note
As an alternative to waiting for the transaction to be mined, we could also optimistically update the value. An optimistic update is a technique, used in many asynchronous applications beyond DApps, that consists in assuming that a transaction performed by the user will succeed and immediately updating the value client-side. This way, the user perceives that the application reacts almost instantly and can keep interacting with it and has immediate feedback on the result of their actions.
While this solution is good enough for a single user interacting with a contract, it falls short when there are multiple users involved. You can try this out by opening the web site from two different browser windows: any changes made in one window will not affect the other, unless the contract is queried again by reloading the page. To solve this issue, we will rely on the last concept we introduced in the contract’s interface: events.
Monitoring Updates Via Events
A contract’s public interface is not just composed of public functions. A contract may also emit custom events upon certain transactions. Our Counter contract emits an event named Increased every time the increase function is called, and includes the new value of the counter as an argument.
Subscription to the Increased event, which updates the component’s state whenever a new instance of the event is emitted
Note that here we are referring to the counter.events property instead of to the counter.methods like we did before. Here, the event emitter fires a data event every time a new event is found, and includes the arguments of the event.
With this new setup, you can now receive real-time updates on a contract, regardless of where the state change originated from. Try again opening two browser windows and increasing the counter from one of them, and see how the change is reflected on both of them once the transaction is confirmed. And if you are lucky, you may even stumble upon a change issued by another reader of this book on the same contract instance.
Deploying the Application
As you may have noticed, our sample application runs exclusively client-side. All logic takes place on the browser, and the blockchain is used as a back end to perform simple computations and persist a shared state among all users, acting as a consensus layer on the state of the counter. This makes deployment straightforward, since the DApp needs only to be hosted as a static site.
Summary
In this chapter, we have gone through the process of developing a simple DApp, providing our users with a basic web-based interface for a single smart contract. We have explored how to read state from a contract and send transactions to it and how to monitor events for real-time updates.
We have built our entire application relying on just two libraries: web3js for interacting with the Ethereum network and React as a presentation framework. Given the pace at which libraries and frameworks change in both the javascript and the Ethereum ecosystems, the goal has been (and will be throughout this book) to use as few dependencies as possible and focus on the concepts behind building a DApp instead of on the specific APIs of the tools of the moment. Of course, this does not mean that you should not rely on such tools when building your own DApp, since they may be of great help. Make sure to check out OpenZeppelin, Truffle, Buidler, Etherlime, Embark, Clevis, and whatever is available by the time this book reaches your hands.
All in all, this chapter should have helped as an overview to the entire development process and components of a DApp. We have glossed over the deployments of the contracts themselves, as well as account and ETH management in general. We have not covered many edge cases or error situations that arise when dealing with a blockchain-based back end. Nevertheless, throughout the book, we will go in-depth in all these topics, plus new and more advanced ones, and revisit each step the building of a DApp on more interesting examples.