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!