visit
This article is also available on my IPFS-hosted blog .
This article is a “fork” of my series of tutorials about smart contract development with Ligo (, and ). 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 on other blockchains (like ), some of these things will look similar, but other things will be completely different. You also have to remember that 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 to run a sandboxed node, Tezbridge to connect our wallet with the dapp and sign transactions and finally Taquito to interact with the 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:$ npm i -g truffle@tezos
$ git clone //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
$ cd ..
$ npm run start-sandbox -- carthage
artifacts.require("PointOfSale")
$ npm run compile
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
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: 2021-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 , just select the “sandbox” option and copy-paste the contract address into the input field.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="//www.tezbridge.com/plugin.js"></script>
just before the <title> tag. This line is necessary to import . 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.$ npm install @taquito/taquito
$ npm install @taquito/tezbridge-signer
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
//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 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] });
});
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:
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 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 will check their address.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 🥳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!