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’ll display the different kinds of coffees we just initiated our storage with and other interactions.

There’s also a little wallet component that’ll 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’ll allow us to create a nicer interface quickly.

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

App.js

We import the different dependencies we need and 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 into the 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’ll make our interface more user-friendly:

shortenAddress will turn tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb into tz1VSU…8Cjcjb , which is, I’m sure you’ll agree, easier on the eyes

will turn into , which is, I’m sure you’ll agree, easier on the eyes mutezToTez will turn values in micro tez into tez. As a rule, I only work with mutez values. In my opinion, they’re easier to work with and 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 of handling mutez while you thought they were tez — and vice versa!

Adding TezBridge

As of today, there’s no npm package for TezBridge, so you can’t import it like any other dependency.

If you open the index.html file located in the public folder, you’ll 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 you approve the transaction. In the example of Café Tezos, we’ll sign a transaction when we want to buy a coffee. The TezBridge plugin will give us a few useful functions 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’ll keep it in the window object so we can have access to it at any time, and we’ll 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’ll set up Taquito. Taquito is an amazing library that’ll 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’ll import two functions from two packages: @taquito/taquito is the main package with all the sweet functionalities we’ll 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 go back to 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.

Taquito setup

Short React aside: The functions we’ll use are asynchronous, but you can’t 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’ll 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 we can use in our JavaScript: It’ll 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’ll save the contract instance because we’ll 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’ll 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

One of the first things your users will probably do when using your dApp is to connect 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 TezBridge a try. It didn’t appeal to me the first time I saw it because the interface is a little — should we say — stern!

TezBridge page

Eighty percent of the page is just whitespace, and clicking on the menus opens jumpy windows (and more whitespace) 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.

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’ll 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:

Import key in TezBridge

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’s only associated to this account for your development environment, and you’ll type it quite a few times).

Hit “Confirm,” and you’re 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’s a good time for them to connect their wallet. Café Tezos has a button in the top-right corner that’ll initialize the wallet when our user presses it. We want a few things to happen when the users connect their wallet:

We’ll get their address and display it on the button to show them they are, indeed, connected We’ll 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’ll present them with a button to withdraw the balance of the contract

This is how it’ll look like:

Wallet initialization

The tezbridge object exposes a method called request , where we’ll 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’re 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’ll 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’ll also get the total balance of the contract and display the button to withdraw it. If you think it may not be safe to have that kind of button in our interface, remember 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 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:

Buying a coffee

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’ll 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 , which has the amount of tezzies the user is paying for the coffee, and mutez , which sets to true to tell Taquito the value we’re passing is in micro tez. Then, the transaction returns an object with different properties we’re 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’ll be prompted to approve (or reject, if they change their mind) the transaction.

As soon as it’s 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 one 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’s extremely important to inform the users of what’s 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’s 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 into the Taquito docs to figure out:

Withdraw function

As you can see, we’re 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’s 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.