B9lab Logo
Tezos Developer Portal
Developer PortalDeveloper Portal

Taquito

Interacting with the Tezos blockchain


After writing smart contracts for the Tezos blockchain, we will now dive into clients.

As we know the Tezos-node offers a JSON/RPC interface. In this chapter, we want to communicate with the Tezos-node without using the Tezos-client. The reason is that, at the end of the day, we want to write applications, which we can hand over to the client without more ado.

We need a possibility for the average user to interact with a smart contract. We will work with a smart contract similar to the one for student certification, which you already deployed previously.

info icon

We don't store the name of the students to keep our first clients simple.

For this, we will need:

  • a user interface to check if a student is certified, and
  • a user interface for the certifier.

Taquito is a Tezos TypeScript library. You can find the source code in the official repository.

It includes various features, among others:

  • the @taquito/taquito package that builds upon the other packages in the Tezos TypeScript Library Suite and offers higher level utility,
  • the @taquito/rpc package that wraps the RPC endpoints,
  • the @taquito/signer package to sign with tezos keys, and
  • the @taquito/tezbridge-wallet, @taquito/beacon-wallet, and @taquito/temple-wallet packages implementing the Wallet API.

In addition, it is very well documented.

There is a npm package for Taquito that we can use. But our first contact with Taquito will be without Node.js. Later, we will also talk about it with Node.js.

Certificate verifier

Since fetching the contract storage is relatively simple, it qualifies well to see Taquito in action for the first time.

We will keep all our GUI project files in a folder. So go ahead and create a folder for it, and download jQuery into it.

In addition, we can embed Taquito into a HTML page with:

<script src="https://unpkg.com/@taquito/taquito@8.0.6-beta.0/dist/taquito.min.js"
crossorigin="anonymous" integrity="sha384-Nl9zWZPEZwA7IDtkqHP9BThh6Os+lYrN0JOM3qzCIdsQqxKChg57TaoeCIcDfwex"></script>

The verifier has to fulfil a simple task: Verify the certification of a given student's address. Therefore, a single input field is sufficient. It will look like this:

screen alice

We are taking the primrose path: We will first create a HTML page, and with it contact a public node to fetch data from the Tezos blockchain.

tip icon

You can find a list of community Tezos-nodes in the documentation.

Let's start with the index.html file:

<html>
<head>
  <link rel="stylesheet" type="text/css" href="./app.css">
  <title>Check Certification</title>
  <script src="jquery.slim.min.js"></script>
  <script src="https://unpkg.com/@taquito/taquito@8.0.6-beta.0/dist/taquito.min.js"
crossorigin="anonymous" integrity="sha384-Nl9zWZPEZwA7IDtkqHP9BThh6Os+lYrN0JOM3qzCIdsQqxKChg57TaoeCIcDfwex"></script>
</head>
<body>
  <div id="app-container">
    <div class="title-bar">Check Certification</div>
    <div class="content-box">
      <table class="form-table">
        <tr><td class="col-1">
          <input class="form-input" type="text" id="address" placeholder="Address"/>
        </td><td>
          <a href="#"><div class="form-submit" onclick="getCertStatus('KT1SgX1ZehTysGcNoSwypwg6Xs5Cj7cpokwK','#address','#result-bar')">Check</div></a>
        </td></tr>
        <tr><td colspan="2">
          <div id="result-bar" class="result-bar"></div>
        </td></tr>
      </table>      
    </div>
  </div>
  <script src="verifier.js"></script>
</body>
</html>

There you can see our contract's address: KT1SgX1ZehTysGcNoSwypwg6Xs5Cj7cpokwK.

You will notice that the script verifier.js at the end doesn't exist yet. Let's create it in the same folder:

const tezos = new taquito.TezosToolkit('https://edonet.smartpy.io/');

function updateStatusUI(status, itemSelector) {
  const bar = $(itemSelector).removeClass().addClass("result-bar");

  if (status == "loading") {
    bar.addClass("result-load").html("Loading...");
  } else if (status == "True") {
    bar.addClass("result-true").html("True");
  } else if (status == "False") {
    bar.addClass("result-false").html("False");
  } else {
    bar.addClass("result-false").html("Error: " + status);
  }
}

function getCertStatus(contractAddress, inputId, outputId) {
  updateStatusUI("loading", outputId);

  return tezos.contract.at(contractAddress).
  then(contract => {
    return contract.storage()
    .then(contractStorage => {
      console.log(JSON.stringify(contractStorage, null, 4));
      const students = contractStorage.certified;
      const inputVal = $(inputId).val();

      const found = students.find(student => student == inputVal);

      if(found !== undefined) {
        updateStatusUI("True", outputId);
      } else {
        updateStatusUI("student not found", outputId);
      }
    })})
    .catch(e => {
      updateStatusUI(e, outputId);
      console.error(e);
    });
}

Save this. It is brief, but will get the job done.

The interesting part is:

  return tezos.contract.at(contractAddress).
  then(contract => {
    return contract.storage()
    .then(contractStorage => {
      console.log(JSON.stringify(contractStorage, null, 4));
      const students = contractStorage.certified;
      const inputVal = $(inputId).val();

      const found = students.find(student => student == inputVal);

Note that it returns us the whole storage.

Afterwards, we are going to look for the requested student.

tip icon

Taquito offers MichelsonMap for working with Map and BigMap types.

You can download this DApp and test it. The smart contract is hardcoded at the address KT1SgX1ZehTysGcNoSwypwg6Xs5Cj7cpokwK, and we added a student with the address tz1Q2zkgZENNF2g95NN7g1CtxAqKynWViSeN.

Certificate issuer

Ok, now we have the verifier. Time to issue some certificates to the students!

The important point is that this time we don't just enquire for information, instead we want to update the storage on the blockchain, and will thus need to sign a transaction. Therefore, we need a public-private key pair.

As is common in the "private-key-land", your private key can be generated from a mnemonic. In this case, a 12-word BIP-39 mnemonic. This is how Taquito will generate and encrypt your private key:

tezos.setProvider({signer: InMemorySigner.fromFundraiser(email, password, mnemonic)});

Now, you already have a private-key JSON "wallet" obtained from the faucet. This private key was actually defined in the genesis of the testnet. To mimic the mainnet, they were issued with passwords and emails. All 3, mnemonic, password, and the email can be found in your JSON file.

warn icon

InMemorySigner is a local signer implementation and is NOT meant to be used in production. For production, please have a look at the Beacon section.

With the in-browser private key generation covered, let's move on to sending a transaction!

That is where we will use:

tezos.contract.at(accountSettings.contractAddress)
                       .then((contract) => {
                        reportResult("Sending...", "info", "#result-bar");
                        return contract.methods.default(studentAddress).send();})

to call the contract.

info icon

You can see that Taquito can create a contract abstraction with the address of the smart contract.

We can pass to the contract.methods.entrypoint as many parameters as the entrypoint needs, where entrypoint is the method you want to call on your smart contract.

So we could do:

contract.methods.default(studentAddress, studentName).send()

if we want to test our certification smart contract, which also stores the names of the students.

For an entry point without parameters, you can use:

[['unit']]

as a parameter. In the present case, we only need to pass the address of the student to certify, which is easily done.

The testnet network processes transactions with a fee of 0, but with this wallet from the faucet we are rich. Thus, if you want to specify any fees, or set limits, you can do so with send.

In addition, you can use Taquito's estimation methods to pick up reasonable values.

Let's continue with the HTML code. This time, to be able to pack all the necessary information, we need more than one input field:

screen certifier
<html>
<head>
  <link rel="stylesheet" type="text/css" href="./app.css">
  <title>Issue Certificate</title>
  <script src="./jquery.slim.min.js"></script>
  <script src="https://unpkg.com/@taquito/taquito@8.0.6-beta.0/dist/taquito.min.js"
crossorigin="anonymous" integrity="sha384-Nl9zWZPEZwA7IDtkqHP9BThh6Os+lYrN0JOM3qzCIdsQqxKChg57TaoeCIcDfwex"></script>

</head>
<body>
  <div id="settings-bar">
    <a href="#" class="btn btn-settings" id="btn_settings">Toggle Settings</a>
    <a href="#" class="btn btn-load" id="btn_load">Load File</a><input type='file' id='upl_input'>
    <div id="settings-box">
        <p>Settings</p>
        <p class="form-title">Provider</p>
        <input type="text" id="provider" class="form-input form-input-small">
        <p class="form-title">Mnemonic</p><input type="text" id="mnemonic" class="form-input form-input-small">
        <p class="form-title">Password</p><input type="text" id="password" class="form-input form-input-small">
        <p class="form-title">Email</p><input type="text" id="email" class="form-input form-input-small">
        <p class="form-title">Secret</p><input type="text" id="secret" class="form-input form-input-small">
        <p class="form-title">Contract Address</p><input type="text" id="contractAddress" class="form-input form-input-small">
      </div>
  </div>

  <div id="app_container">
    <div class="title-bar">Issue Certificate</div>
    <div class="content-box">
      <table class="form-table">
        <tr><td class="col-1">
          <input class="form-input" type="text" id="inp_address" placeholder="Address"/>
        </td><td>
          <a href="#" id="btn_issue" class="btn btn-submit">Issue</a>
        </td></tr>
        <tr><td colspan="2">
          <div id="result-bar" class="result-bar"></div>
        </td></tr>
      </table>      
    </div>
  </div>
  <script src="./signer.js"></script>
  <script src="./certifier.js"></script>
</body>
</html>

And for the certifier.js script:

// This is for demonstration purposes only! Don't handle live keys like this.
// You can hardcode your account settings and contract address here for local testing.
function initUI() {
    updateUISetting({
        provider: "https://edonet.smartpy.io/",
        mnemonic: "pen orphan heavy bone fever reform never detail zone parade act civil bench space adapt",
        password: "dBKYO9LP1O",
        email: "lihaeain.eflfwsqy@tezos.example.org",
        contractAddress: "KT1SgX1ZehTysGcNoSwypwg6Xs5Cj7cpokwK"
    });

    // setup all UI actions
    $('#btn_issue').click(() => certify($('#inp_address').val()));
    $('#btn_settings').click(() => $('#settings-box').toggle());
    $("#upl_input").on("change", loadJsonFile);
    $('#btn_load').click(() => $("#upl_input").click());
}

function updateUISetting(accountSettings) {
    $('#provider').val(accountSettings.provider);
    $('#mnemonic').val(accountSettings.mnemonic);
    $('#password').val(accountSettings.password);
    $('#email').val(accountSettings.email);
    $('#contractAddress').val(accountSettings.contractAddress);
}

function readUISettings() {
    return {
        provider: $('#provider').val(),
        mnemonic: $('#mnemonic').val(),
        password: $('#password').val(),
        email: $('#email').val(),
        contractAddress: $('#contractAddress').val()
    };
}

function loadJsonFile() {
    // This doesn't work in IE
    const file = $("#upl_input").get(0).files[0];
    const reader = new FileReader();
    const accountSettings = readUISettings();
    reader.onload = parseFaucetJson(accountSettings);
    reader.onloadend = () => updateUISetting(accountSettings);
    reader.readAsText(file);
}

// Parses the faucet JSON file 
function parseFaucetJson(settingsToFillIn) {
    return function(evnt) {
        const parsed = JSON.parse(evnt.target.result);
        settingsToFillIn.mnemonic = parsed['mnemonic'].join(" ");
        settingsToFillIn.password = parsed['password'];
        settingsToFillIn.email = parsed['email'];
        return settingsToFillIn;
    }
}

function reportResult(result, type, itemSelector) {
    return $(itemSelector)
        .html(result)
        .removeClass()
        .addClass("result-bar")
        .addClass(type == "error" ?
            "result-false" :
            type == "ok" ?
            "result-true" :
            "result-load");
}

// This is the main function, interacting with the contract through Taquito
function certify(studentAddress) {
    const accountSettings = readUISettings(),
    tezos = new taquito.TezosToolkit(accountSettings.provider);

    tezos.setProvider({
        signer: InMemorySigner.InMemorySigner
                .fromFundraiser(accountSettings.email, 
                  accountSettings.password, 
                  accountSettings.mnemonic)
    });

    return tezos.contract.at(accountSettings.contractAddress)
        .then((contract) => {
            reportResult("Sending...", "info", "#result-bar");
            return contract.methods.default(studentAddress).send();
        })
        .then((op) => {
            reportResult("Waiting for confirmation...", "info", "#result-bar");
            return op.confirmation(1).then(() => op.hash);
        })
        .then((hash) => {
            reportResult("Operation injected: " + hash, "ok", "#result-bar");
        })
        .catch((error) => {
            reportResult(error.message, "error", "#result-bar");
        });
}

$(document).ready(initUI);

You can see that we wait for the operation be included in a block. For production, you would wait more than a few blocks before accepting the confirmation.

You can see that we use:

    tezos.setProvider({
        signer: InMemorySigner.InMemorySigner
                .fromFundraiser(accountSettings.email, 
                  accountSettings.password, 
                  accountSettings.mnemonic)
    });

Well InMemorySigner.InMemorySigner looks strange because of how we import @taquito/signer. It is not included in the @taquito/taquito because it is not relevant for production.

You will notice that we miss the script signer.js. It includes InMemorySigner and you can generate it with a JavaScript file like:

const InMemorySigner = require("@taquito/signer");

window.InMemorySigner = InMemorySigner;

and Browserify via:

$ browserify abovescript.js -o signer.js

or you can download it from the certifier repository. You just need to open index.html to test the application.

Taquito with Node.js

There are many tasks, which we would rather handle from the server side. For such cases, we can use Taquito with Node.js.

You just have to create a node project with:

$ npm init

Afterwards, you can include the Taquito npm package in your project with:

$ npm i @taquito/taquito

Then create an index.js and try the first sample from the documentation:

const { TezosToolkit } = require("@taquito/taquito");
const Tezos = new TezosToolkit('https://api.tez.ie/rpc/edonet');

Tezos.tz
  .getBalance('tz1h3rQ8wBxFd8L9B3d7Jhaawu6Z568XU3xY')
  .then((balance) => console.log(`${balance.toNumber() / 1000000}`))
  .catch((error) => console.log(JSON.stringify(error)));
tip icon

Have a look at the official samples in the documentation. You will see one sample working with Parcel and another one with React. They are written in TypeScript like the Tezos TypeScript Library Suite.

Let's try another sample and validate a signature:

const {
    validateSignature
} = require("@taquito/utils");

//valid
const signature =
    'edsigtkpiSSschcaCt9pUVrpNPf7TTcgvgDEDD6NCEHMy8NNQJCGnMfLZzYoQj74yLjo9wx6MPVV29CvVzgi7qEcEUok3k7AuMg';
const validation = validateSignature(signature);
console.log(`Calling the validateSignature function with ${signature} returns ${validation}.`);

//invalid checksum
const invalidSignature =
    'edsigtkpiSSschcaCt9pUVrpNPf7TTcgvgDEDD6NCEHMy8NNQJCGnMfLZzYoQj74yLjo9wx6MPVV29CvVzgi7qEcEUok3k7AuM';
const invalidValidation = validateSignature(invalidSignature);
console.log(
    `Calling the validateSignature function with ${invalidSignature} returns ${invalidValidation}.`
);

The result of validateSignature will be 0 (NO_PREFIX_MATCHED), 1 (INVALID_CHECKSUM), 2 (INVALID_LENGTH), or 3 (VALID).

reading icon
Discuss on Slack