B9lab Logo
Tezos Developer Portal
Developer PortalDeveloper Portal

Beacon

Connecting wallets and applications


A wallet will store your keys and can also fulfil other tasks, like signing transactions.

Beacon is an interface between such a wallet and your application. At the time of writing, Beacon supports:

We have different options for how we can use the Beacon SDK. We will work with Taquito's Wallet API, and extend our certifier application so it can be used with a wallet. This is possible because there is a wallet interaction specification called TZIP-10, which is implemented by Beacon.

Wallet API

The Wallet API supports different wallets. The npm package for Beacon is @taquito/beacon-wallet.

Create a folder for this project, and run in it:

$ npm init

Then you can install the necessary packages:

$ npm install @taquito/taquito
$ npm install @taquito/beacon-wallet

You have seen how we can use such libraries in the browser with the help of Browserify. Here we want to work with the Parcel bundler, so it is best if you install it globally:

$ npm install -g parcel-bundler

Then we can again create an index.html:


<html>
<head>
  <link rel="stylesheet" type="text/css" href="./app.css">
  <title>Issue Certificate</title>
  <script src="./certifier.js"></script>
</head>
<body>
  <div id="settings-bar">
    <a href="#" class="btn btn-settings" id="btn_settings">Toggle Settings</a>
    <a href="#" class="btn btn-connect" id="btn_connect">Connect Wallet</a>
    <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">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>
</body>
</html>

Ok, so far we just removed the input fields for the faucet account. Instead, we now have a button to connect a wallet.

Now you need to rewrite parts of certifier.js:


const $ = require("jquery");
const { TezosToolkit } = require('@taquito/taquito');
const { BeaconWallet } = require('@taquito/beacon-wallet');

function initUI() {
    updateUISetting({
        provider: "https://edonet.smartpy.io/",
        contractAddress: "KT1UhNWxpfnDkxYBQz1BwXHTvhikXueFwfU3"
    });

    // setup UI actions
    $('#btn_issue').click(() => certify($('#inp_address').val()));
    $('#btn_settings').click(() => $('#settings-box').toggle());
    $('#btn_connect').click(() => connectWallet());
}

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

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

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

let tezos, wallet;

// This function will connect your application with the wallet
function connectWallet() {
    const accountSettings = readUISettings();
    tezos = new TezosToolkit(accountSettings.provider);

    const options = {
        name: 'Student Certifier'
    };
    wallet = new BeaconWallet(options);

    wallet
        .requestPermissions({
            network: {
                type: "edonet"
            }
        })
        .then((_) => wallet.getPKH())
        .then((address) => console.log(address))
        .then(() => tezos.setWalletProvider(wallet));

}

// This is the main function, interacting with the contract through the Taquito Wallet API
function certify(studentAddress) {
    const accountSettings = readUISettings();
    return tezos.wallet.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 see our new function connectWallet(). What is new there?


    const options = {
        name: 'Student Certifier'
    };
    wallet = new BeaconWallet(options);

There are other useful options, like eventHandlers which should be utilised for better user experience in a real application. Anyway we will keep it simple and set only what must be set.

    wallet
        .requestPermissions({
            network: {
                type: "edonet"
            }
        })
        .then((_) => wallet.getPKH())
        .then((address) => console.log(address))
        .then(() => tezos.setWalletProvider(wallet));

We first call for a requestPermissions for the Edonet testnet, fetch the address of the user, and print it into the console. Then we set the wallet as our provider.

Now the wallet will sign our transactions:


function certify(studentAddress) {
    const accountSettings = readUISettings();
    return tezos.wallet.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");
        });
}

As you can see, it is just slightly changed: We use tezos.wallet.at instead of tezos.contract.at to get the contract abstraction.

Test run

Let's test this!

First, you need the Spire extension for Chrome. After installation you can turn on the developer mode:

beacon1

In this case, you can setup a local secret and you don't need a wallet to pair it with.

beacon2

Now we can set the network to Edonet:

beacon3

Now let's start our application!

Run in your project folder:

$ parcel index.html

...

Server running at http://localhost:1234

...

It will tell you where the server is running so you can test your application.

In addition, show the console to see the outputs:

beacon4

Now, when you hit the Connect Wallet button, you most likely will see in the console something similar to:

TypeError: sodium.crypto_generichash is not a function
    at Object.<anonymous> (index.9c3bd9ee.js:102295)
    at step (index.9c3bd9ee.js:102228)
    at Object.next (index.9c3bd9ee.js:102177)
    at fulfilled (index.9c3bd9ee.js:102140)

This is an issue with Parcel we need to fix first. Thus, in your project folder run:

$ sed -i 's/require("libsodium-wrappers"));/require("libsodium-wrappers")).default;/g' ./dist/index.9c3bd9ee.js

$ sed -i 's/qrcode(/qrcode.default(/g' ./dist/index.9c3bd9ee.js

Please adjust the name index.9c3bd9ee.js into the correct one, which will be generated by Parcel in the dist folder.

tip icon

If you work on MacOs, you will need the command to start with -i '' like sed -i '' 's/require("l....

beacon5

Now, we can select our wallet Spire to use our local keys:

beacon6

You have to confirm the permission request:

beacon7

You should see your address in the console.

...
index.9c3bd9ee.js:114026 0ac90fc8-a9fc-5908-5d6f-1b0e9a77db4c: 9.77783203125 ms sending
index.9c3bd9ee.js:114031 0ac90fc8-a9fc-5908-5d6f-1b0e9a77db4c: 14.703857421875 ms sent
index.9c3bd9ee.js:112904 0ac90fc8-a9fc-5908-5d6f-1b0e9a77db4c: 262.90283203125 ms acknowledge
index.9c3bd9ee.js:112925 0ac90fc8-a9fc-5908-5d6f-1b0e9a77db4c: 2754.495849609375 ms response
index.9c3bd9ee.js:112926 0ac90fc8-a9fc-5908-5d6f-1b0e9a77db4c: 2754.822998046875 ms
index.9c3bd9ee.js:502 tz1PseT4U3hKdkC5dWhvHwxcnhSYc5qmtBfn

At the time of writing, unfortunately we cannot import faucet accounts into Spire. Instead it will generate a new account with 0 tez in it.

So to continue, you will need to transfer some tez from a faucet account on the testnet to your address in Spire first. You can do this, like we did in the CLI section, with the tezos-client or via SmartPy - You will need to visit SmartPy anyway to deploy the certification contract again for your new address.

After that, you can try to issue a certificate.

Spire will pop up and let you confirm the transaction:

beacon8
reading icon
Discuss on Slack