Hoe to build Your First Dapp on Tezos

Everything you need to know to create a simple Dapp on Tezos

This article is a “fork” of my series of tutorials about smart contract development with Ligo (Part 1Part 2 and Part 3). We are going to take some time to see how a front-end application interacts with a smart contract deployed on the Tezos blockchain. If you have developed dapps on other blockchains (like Ethereum), some of these things will look similar, but other things will be completely different. You also have to remember that dapps are still at an early age on Tezos and they are not ready yet to work at their full capacity. However, you can still do a lot of pretty cool stuff on Tezos right now!
We will build a point of sale system for a virtual café called “Café Tezos”. One of the things I am the most impatient to see coming in the near future is the possibility to use cryptocurrencies for everyday life purchases. If you are a developer, you probably drink a lot of coffee! So that will be a great example.

The dapp will be React-based. To be honest, I am more of a Svelte guy, but React being the most popular front-end framework at the moment, it seems logical to use it in order to share this knowledge with as many people as possible. In addition of React, we will also use different tools developed to work with the Tezos blockchain: Truffle to compile and deploy contracts, a modified version of the Tezos Starter Kit from Stove Labs to run a sandboxed node, Tezbridge to connect our wallet with the dapp and sign transactions and finally Taquito to interact with the blockchain and the smart contracts. These tools are generally presented separately but I am confident it will be interesting to see how they work together.

Before diving into the code, here are two points that I would like you to keep in mind:
  1. This dapp is not production-ready! Although it is useable as you will see, there are a few considerations that were not taken for the sake of simplicity. For example, the React code is not optimized to remain simple in order for beginners as well as advanced users to follow along.
  2. This is not a React tutorial! This tutorial requires a basic knowledge of React.js and JavaScript. I will copy here only the important pieces of code, the entire code for the dapp is available in the associated Github repository.
Now it’s time to sell some coffee ☕️

Setting up the environment

Before starting, you must make sure that you have Docker and Node.js installed on your computer. If you have them, start the Docker Desktop.
Next, you must install Truffle Tezos globally. Open a new terminal window and type:
$ npm i -g truffle@tezos
I have already prepared everything you will need for this tutorial in this Github repo. Type in the terminal window:
$ git clone https://github.com/claudebarde/tezos-react-tutorial
$ cd tezos-react-tutorial
$ npm install
After this step, you have two solutions: you can either leave the client folder and just follow this tutorial and check the code or delete the client folder and create a new React app at the root of the folder. I would recommend deleting the client folder only to React power users as you will have to fill the gaps of the code that will not appear in this tutorial.

Now, let’s install React dependencies:

$ cd client
$ npm install
At this point, most of the setup is done, we can start the sandboxed node:
$ cd ..
$ npm run start-sandbox -- carthage

Compiling the contract and deploying it

The contract we will use is already present in the project. This is the same one that we created in Part 3 of the Ligo tutorial. Truffle makes it very easy to compile and deploy a new contract in a process called “migration“. The deployer file looks like this:
This file does a few simple things:
  1. It fetches the contract to deploy -> artifacts.require(“PointOfSale”)
  2. It fetches the information of the account we will use to deploy the contract (in the alice variable).
  3. It sets the initial storage (the values will be automatically converted to Ligo compatible values by Taquito).
  4. It exports the deployer function and the initial storage.
Now we can compile the contract:
$ npm run compile
It will just take a few seconds and if I haven’t done any mistake, the contract is compiled ????
If you are curious, you probably noticed that this step created a build folder. Inside this folder, another folder contracts contains two JSON files, one of which has the same name as our contract. If you open it, you will see the Michelson compiled from our Ligo contract.

Now we can deploy the contract to our sandboxed node:

$ npm run migrate
The output in the console should look similar to this one:
Starting migrations...
======================
> Network name: 'development'
> Network id: NetXdQprcVkpaWU
> Block gas limit: 10400000
1_deploy_point_of_sale.js
==========================
Replacing 'PointOfSale'
-----------------------
> operation hash: oo3JG8jJhGLtQ71qMtqZqKzAniadiZDeQNqe6sA7W8ZXuydSfeS
> Blocks: 1 Seconds: 8
> contract address: KT1SVc1wpWdBmJUMruacMjfjS9Gib5WLmz28
> block number: 6631
> block timestamp: 2020-03-13T14:47:47Z
> account: tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb
> balance: 1999999.536464
> gas used: 42199
> storage used: 1208 bytes
> fee spent: 5.73 mtz
> burn cost: 1.465 tez
> value sent: 0 XTZ
> total cost: 1.47073 XTZ
> Saving artifacts
-------------------------------------
> Total cost: 1.47073 XTZ
Summary
=======
> Total deployments: 1
> Final cost: 1.47073 XTZ
You will notice that some values are different, like the operation hash or the contract address, which is totally normal. This gives you also a lot of valuable information for later use, like the gas used and the total cost. I would also recommend you check your newly deployed contract using Better Call Dev explorer, just select the “sandbox” option and copy-paste the contract address into the input field.
Now that we have set up our development environment, compiled and deployed our contract, it’s time to build our dapp!

Building the front-end

Setting things up
As you can see from the client folder, our React app will be pretty simple: the App.js file is the entry point and contains a Menu component that will display the different kinds of coffees we just initiated our storage with and other interactions. There is also a little wallet component that will display login buttons and the user’s balance. Some basic styles and the loading animation styles are located in the App.css file and you can also see a Bulma CSS file that will allow us to create a nicer interface quickly.

Now let’s look at the top of the App.js file:

We import the different dependencies we need + two functions from the Taquito package (more on them below).
Whatever contract address you may find at this point, replace it with the address you input in Better Call Dev interface. Contract addresses on the Tezos blockchain start with KT1, that’s how you know you have the right kind of value ????
Next, I added two simple functions that will make our interface more user-friendly:
  1. shortenAddress will turn tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb into tz1VSU…8Cjcjb, which is, I am sure you will agree with me, easier on the eyes!
  2. mutezToTez will turn values in microtez into tez. As a rule, I only work with mutez values, in my opinion, they are easier to work with, less prone to calculation errors, you can always easily display a user-friendly value on the front-end and you avoid errors of tez <=> mutez conversion or handling mutez while you thought they were tez and vice versa!
Adding Tezbridge
As of today, there is no NPM package for Tezbridge, so you cannot import it like any other dependencies. If you open the index.html file located in the public folder, you will see
<script src=”https://www.tezbridge.com/plugin.js”> just before the <title> tag. This line is necessary to import Tezbridge. Tezbridge is a tool that allows you to use any Tezos address you want to sign transactions. In a blockchain context, “signing a transaction” simply means that you approve the transaction. In the example of Café Tezos, we will sign a transaction when we want to buy a coffee. Tezbridge plugin will give us a few useful functions that we can use to sign transactions and do other important actions.
Adding the script tag for the Tezbridge plugin will expose a tezbridge object in the window object. We will keep it in the window object so we can have access to it at any time and we will just write const tezbridge = window.tezbridge to make it easier to use in our React code.
Setting up Taquito
After our component is mounted, we will set up Taquito. Taquito is an amazing library that will allow us to communicate with the contract and the Tezos blockchain in general. As you can see at the top of the App component, we will import two functions from two packages: @taquito/taquito is the main package with all the sweet functionalities we will use and @taquito/tezbridge-signer is a second package we need in order to use Tezbridge as our signer. In order to install them, just come back at the root of the client folder and type in the console:
$ npm install @taquito/taquito
$ npm install @taquito/tezbridge-signer
Now it’s time to actually use Taquito!
Short React aside: the functions we will use are asynchronous but you cannot pass an asynchronous function to useEffect. The trick is to use an asynchronous function declared as an IIFE inside the function passed to useEffect.
First, we need to set up our provider. The RPC protocol is an API exposed by our sandboxed node (or any Tezos node for that matter) that allows us to communicate with the node. In the case of this tutorial, the node will use the port 8732 to expose the RPC, so we use http://localhost:8732 as our RPC. As we will use Tezbridge as our signer, we must also instantiate the Tezbridge signer and pass it to the Taquito provider. We call the setProvider function of the Tezos object to set it up. Once the Tezos object is set up, we can finally use it!
First, we want to create an instance of the contract. Think of the instance as a copy of the contract that we can use in our JavaScript: it will contain the entry points of the contract as well as its storage (and other useful information). Simply type:
const contract = await Tezos.contract.at(contractAddress);

This is why it was important to update the contract address earlier with the one returned from the console after truffle migrate, Taquito will look for your contract using the port you provided for the RPC. Then, we will save the contract instance because we will use it again later with the useState hook.

Now let’s get the storage of the contract:
const storage = await contract.storage();
The contract instance has a storage method you can use to return the storage. Taquito makes it very easy to handle the storage as the fields you declared in the contract storage in Ligo will be properties of the storage object. The menu property is given as a MichelsonMap with different methods whose functions are outside of the scope of this article. For example, keys() returns a generator object with the different keys of the bigmap, values() does the same with the values associated with the keys. We will here use the forEach() method that allows us to loop through the MichelsonMap and gets the fields/values:

 

storage.menu.forEach((value, key) => {
 coffees.push({ name: key, price: value.c[0] });
});
We simply populate a JavaScript array with objects containing the name of each coffee and its price.
Adding the wallet to Tezbridge
Probably one of the first things that your users will do when using your dapp is connecting their wallet. Coming from Ethereum, it was a little bit frustrating at first not to have a solution like MetaMask and I tried a couple of solutions before giving a try to Tezbridge. It didn’t appeal to me the first time I saw it because the interface is a little, how to say, stern!
80% of the page is just white space and clicking on the menus opens jumpy windows (and more white space!) with the most minimalist style possible. But don’t let that fool you, Tezbridge is a great tool that’s easy to use and that provides valuable information for both your user and yourself, the developer! Now before we can use Tezbridge, we have to import our private key to set up an account. If you go back to the root of the tezos-react-tutorial folder, you will find a scripts folder and inside, a sandbox folder. Inside the latter, open the accounts.js file. There, copy the sk (as in “secret key”) property of the alice object (namely “edsk3QoqBuvdamxouPhin7swCvkQNgq4jP5KZPbwWNnwdZpSpJiEbq“). Go back to the Tezbridge page and click on Import key before pasting the key in the field that just appeared:

You know you got the right secret key because it generates the right public key (the one you can find under the pkh property of the alice object). As a manager name, type “alice” (or any other name) and choose a simple password (it is only associated to this account for your development environment and you will type it quite a few times). Hit Confirm and you are ready to use Tezbridge!

Initializing the wallet

As a good practice in a dapp, we don’t want to automatically connect or have startling windows welcoming our users. We want to let them decide when it is a good time for them to connect their wallet. “Café Tezos” has a button at the top right corner that will initialize the wallet when our user presses it. We want a few things to happen when the users connect their wallet:

  1. We will get their address and display it on the button to show them that they are indeed connected.
  2. We will fetch their balance so they can know very quickly if they have enough money to purchase a coffee.
  3. If the owner of the contract logs in, we will present them with a button to withdraw the balance of the contract.

This is how it will look like:

The tezbridge object exposes a method called “request” where we will pass an object with the method property set to get_source. This allows the users to select the wallet they want to connect to the dapp and returns the associated address. Tezbridge will open a new tab next to the dapp, you can leave it open after you are done or close it. After the users are connected, we save their address to update the button.

With the help of Taquito, we use await Tezos.tz.getBalance(userAddress) to fetch the user’s balance. Once again thanks to the incredible functions provided by Taquito, we can fetch the state of the storage. We only need the contract instance we created a bit earlier and we call the storage method on it. The storage object will have properties named after the fields of our storage. As we explained before, a few functions are now available on each property of the storage object. We want to get the number of points of the current user, so we use storage.customers.get(userAddress) to find it. Because the user’s address could be missing from the map and have an undefined value in JavaScript, we will set the number of points to 0 in our front-end dapp if this is the case.

If the user is the owner of the contract, we will also get the total balance of the contract and display the button to withdraw it. If you think that it may not be safe to have that kind of button in our interface, remember that even if someone with bad intentions gets access to the button, they won’t be able to use it as the smart contract will check their address.

Buying a coffee

One of the most fascinating things in programming is that, from a user’s point of view, it’s just a click on a button, but from a developer’s point of view, it may be a dozen or hundreds of calculations and checks to change a value!

This will be the case when you send a transaction to a Tezos contract. The function is located in the Menu component. You must be sure to have the right parameters before sending it, you must verify that the transaction has been applied and you must wait for its completion. Once again, Taquito will be of great help in order to achieve it. This is how buying a coffee will look like:

The transaction starts with the following function:
const op = await contractInstance
 .methods
 .buy(coffee)
 .send({amount: price, mutez: true})

Let’s break it down: first, we need the contract instance that we declared earlier. This contract instance gives us access to the methods method which contains the different entry points of the contract. We will use buy. If you remember, the buy entry point in the smart contract expects the name of the coffee the user wants to buy, so we pass it as a parameter to the buy method. To finish, we call the send method and pass to it an object with two properties: amount with the amount of tezzies the user is paying for the coffee and mutez sets to true to tell Taquito the value we are passing is in microtez. Then the transaction returns an object with different properties that we are going to use next.

This operation will switch the user’s screen to the Tezbridge tab (or open a new one if they closed it) and they will be prompted to approve (or reject if they change their mind) the transaction. As soon as it is done, the status property of the object returned by the transaction should change to “applied“. This means the transaction was sent. Now, we can sit down and relax while the sandboxed node confirms the transaction and includes it in a block. In order to do that, we call await op.confirmation(1) which will wait for 1 confirmation before going on with your code. Once the transaction has been included in a block, the includedInBlock property will change from Infinity to a number (the block number). This is the signal, you can start brewing some coffee ????

As you can notice from the code, during this flow, we update the state of the dapp. It is extremely important to inform the users of what is happening, as the confirmation of transactions can take up to a minute and you want to prevent them from clicking multiple times on the button and ordering 10 coffees!

After spending so much time brewing coffee, it is time to reap the fruits of your labor!
Withdrawing the contract balance
The implementation of the withdraw function in the App component will be very similar to the buy function with a huge difference that takes a little digging in Taquito docs to figure out:

As you can see, we are also using the contract instance to send the transaction but remember, the withdraw entry point doesn’t expect any parameter. However, the main function in the contract does! To make it work, you pass an array containing another array with the string “unit” as the first element to the method representing your entry point, so withdraw([[“unit”]]). For the send method, there is no need to pass any parameter.

As shown above, you then wait for the confirmation of the transaction being included in a block and you update the state of the dapp.

And that’s it! Now you know how to build a simple dapp using React, Ligo, Tezbridge and Taquito

Recap

This tutorial has been the occasion to see how different tools used to develop dapps on Tezos work together. These tools are generally presented individually but it’s good to remember that they are actually different pieces of your global dapp. Here is what we introduced in this tutorial:
  • React: no need to introduce the most widely used front-end framework, React works seamlessly with the rest of the tools and its Hook API allows us to update efficiently the state of our dapp to keep it synced with the storage of the smart contract.
  • Taquito: Taquito is an exceptionally useful library that allows multiple types of interaction with smart contracts and data manipulations.
  • Tezbridge: this small but convenient tool allows dapp users to choose a wallet to sign their transactions. If you are familiar with Ethereum, it is similar to what MetaMask achieves.
It is also important to remember the different steps your users will go through when developing a dapp:
  1. They connect their wallet.
  2. They request a transaction to be sent to the smart contract.
  3. They sign the transaction.
  4. The transaction is checked and processed by the smart contract.
  5. The dapp interface displays a confirmation (or error) message.

Conclusion

After spending a few weeks looking for the best solution, I am confident that you cannot go wrong if you want to build a dapp on Tezos if you use the following stack: [Your favorite front-end JS framework] / Ligo / Taquito / Tezbridge.

You have now a better knowledge of how to build a simple dapp on Tezos. The tools are pretty easy to use and as long as you follow the right steps, you cannot go wrong ????

It is also worth remembering that developing dapps on Tezos is quite new and the libraries are continuously updated. Taquito is bundled in your dapp, so this won’t be a problem, but an update in Tezbridge, for example, could break your dapp. It is then very important that you follow the developments of the Tezos community and update your dapp accordingly.

You may have noticed that we didn’t implement any solution for our users to redeem their points, you can consider it an exercise! Otherwise, what kind of improvements can you think of for the dapp? Maybe a better synchronized state of the storage in your React code? More data validation? Cuter loading animations? I can’t wait to see what you build ????‍️????‍♂️

 

This article has been published from the source link without modifications to the text. Only the headline has been changed.