Everything you need to know to create a simple Dapp on Tezos
This article is also available on my IPFS-hosted blogĀ DecentraDev.
This article is a āforkā of my series of tutorials about smart contract development with Ligo (Part 1,Ā Part 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:
- 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.
- 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:
- It fetches the contract to deploy ->Ā
artifacts.require("PointOfSale")
- It fetches the information of the account we will use to deploy the contract (in theĀ aliceĀ variable).
- It sets the initial storage (the values will be automatically converted to Ligo compatible values by Taquito).
- 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:
Ā will turn tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb intoĀ tz1VSUā¦8Cjcjb, which is, I am sure you will agree with me, easier on the eyes!shortenAddress
Ā 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.mutezToTez
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"></script>
Ā 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);
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:
- We will get their address and display it on the button to show them that they are indeed connected.
- We will fetch their balance so they can know very quickly if they have enough money to purchase a coffee.
- 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:
- They connect their wallet.
- They request a transaction to be sent to the smart contract.
- They sign the transaction.
- The transaction is checked and processed by the smart contract.
- 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 š·āļøš·āāļø
If you liked this tutorial, consider sending some tezzies toĀ tz1SjrNeUE4zyPGSZpogDZd5tvryixNDsD2vĀ and donāt hesitate to leave your opinions or suggestions!