

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:
Now itโs time to sell some coffee โ๏ธ
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
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:
artifacts.require("PointOfSale")
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!
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:
shortenAddress
ย will turn tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb intoย tz1VSUโฆ8Cjcjb, which is, I am sure you will agree with me, easier on the eyes!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.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.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:
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.
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!
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:
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.
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!
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 ๐ฅณ
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:
It is also important to remember the different steps your users will go through when developing a dapp:
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!
Create your free account to unlock your custom reading experience.