How to Read Polkadot Identities with JavaScript

This tutorial will show you how to read on-chain identities of registered addresses from Polkadot or any other Substrate-based chain with the Identity pallet in its runtime (like, for example, Kusama).


For those unfamiliar with the Identity pallet: the Identity pallet gives a Substrate-based chain the functionality required to let users register real-world information about themselves on the blockchain. This information can be something like avatars, Twitter handles, email, real name, or any number of custom fields. The information can also be verified by trusted third parties we call registrars. You can learn more about this whole process in the Polkadot Wiki entry on Identities

If you haven’t already, boot up a copy of Polkadash as per this guide.

Skill Level Prerequisites

  • JavaScript: intermediate
  • VueJS: beginner
  • Polkadot: essentials

Creating a New Component

We’ll build two components in this tutorial. One will be a standalone component that shows an address’ identity information in the form of a card, and the other will be the page on which we’ll use this component.

Profile Page

Let’s start with the page. Create src\pages\Profile.vue:

<template>
  <div class="content">
    <card> </card>
  </div>
</template>
<script>
import { Card } from "@/components/index";

export default {
  components: {
    Card,
  },
};
</script>
<style></style>

Add a link to it to the src\pages\Layout\DashboardLayout.vue file:

<sidebar-link to="/profile">
  <i class="tim-icons icon-single-02"></i>
  <p>Profile</p>
</sidebar-link>

And remember to include it in the router.js routes:

{
  path: "profile",
  name: "Profile",
  component: () => import("@/pages/Profile.vue"),
},

Identity Component

Next, let’s start building the identity component. Create components/Cards/Identity.vue.

Let’s build it out from scratch. A component needs a template part, and a script part:

<template>
  <card type="user">
    <div class="identity"></div>
  </card>
</template>
<script>
import { Card } from "@/components/index";
</script>

Since Polkadash uses cards to lay out the content, we import that component and use it in our template.

Now let’s add some meat to the div.identity block. We’ll assume we’ll be able to grab the identity’s full name, email, and crypto address for now:

    <div class="identity">
        <a href="#">
        <img class="avatar" src="../../assets/img/anime6.png" alt="..." />
        <h5 class="fullName">{{ user.fullName || address }}</h5>
      </a>
      <p class="address" v-if="user.fullName">
        {{ address }}
      </p>
    </div>
    <p class="card-description" v-if="user.email">
      <a href="mailto:user.email">{{ user.email }}</a>
    </p>
    </div>

We add a basic avatar placeholder image, included with Polkadash. Next, we print out the identity’s full name, or if the name is not set, just the address. If the name is set, we echo the address below, otherwise we skip this block with v-if because the address is already printed above in that case. Finally, we add a link which lets users send an email to the registered mail of this identity, if one is provided.

Given that we are using properties of this component, we have to also define them in the <script> part, otherwise VueJS will complain.

<script>
import { Card } from "@/components/index";

export default {
  components: {
    Card
  },
  data() {
    return {
      user: {
          fullName: "Test",
          email: "test@test.com"
      },
      address: "Some address"
    };
  },
};
</script>

With this placeholder data in place, let’s include our component in the Profile.vue page to see how it looks. We need to import the component first, and then include it in the template as a custom HTML element: <identity> (component DOM names always default to their lowercase component names).

<template>
  <div class="content">
    <card>
      <identity></identity>
    </card>
  </div>
</template>
<script>
import { Card } from "@/components/index";

export default {
  components: {
    Card,
    Identity: () => import("@/components/Cards/Identity"),
  },
};
</script>
<style></style>

Basic identity card

Not too shabby!

Address Input

To be able to look up addresses, we need an input field on the page we’re looking at. We also need to make sure the page sends the information from that input field to the Identity component instance.

In Profile.vue, above the <card> that holds our <identity> component, let’s add a new card which will contain the header and description of this page, along with the input field we want.

    <card>
      <template slot="header">
        <h5 class="title">Profile</h5>
        <p class="category">A user's on-chain profile - enter an address!</p>
      </template>
      <div class="row">
        <div class="col-lg-6 col-md-6 col-sm-12 col-xs-12 col-xs-12">
          <input
            class="form-control"
            type="text"
            placeholder="PhngluimglwnafhCthulhuRlyehwgahnaglfhtagn"
            id="idlookup"
            v-model="newaddress"
            v-on:keyup.enter="lookup"
          />
        </div>
      </div>
    </card>

We gave the input field it a model of newaddress and we call the lookup function when the user hits Enter. Technically, we could just use address for v-model, but this would make our component perform a lookup on every character we write.

So if we write a it would lookup(a), then on ab it would lookup(ab), and so on. By the time we’ve typed out our full address, some JS would have triggered 30 times. Even though most people will paste addresses in and not type them, it’s better to avoid such wastefulness.

Now we need to define these properties and methods in our component’s <script> section.

  data() {
    return {
      address: "",
      newaddress: "",
    };
  },
  methods: {
    lookup: function() {
      this.address = this.newaddress;
    },
  },

Inputting “Foo” into the address input field and hitting enter will now set both of these values to “Foo”. You can inspect this through the VueJS Chrome Dev Tools extension:

VueJS tools exposing properties of a component: both address and newaddress contain the value "Foo"

Let’s pass address into the <identity> instance. Change the <identity></identity> line to:

<identity :address="address"></identity>

We also need to configure our Identity.vue component to accept this property. We’ll remove the address: "Some address" line, and add it under props like so:

export default {
  components: {
    Card
  },
  data() {
    return {
      user: {
          fullName: "Test",
          email: "test@test.com"
      } // <-- we removed address from here
    };
  },
  props: { // <-- this part is new
      address: String
  }
};
</script>

props are are what we use when we want to pass external data into a component. data is what we use when we want local data, data in and for that component.

Changing the address in the input field will now also change the text inside our component.

Validation

Before we act on the address, we’ll need to do a sanity check: is the provided address valid, and does it belong to the chain we’re currently connected to?

Remember, every Substrate-based chain has its own address prefix which produces a different address format - this is something we’ve demonstrated and coded in the Address Translation Filter tutorial. A Kusama address is different from a Polkadot address. As such it makes sense to, for example, block Polkadot address lookups when connected to Kusama.

Each Substrate chain has its prefix information available in the node’s metadata. You can query this metadata through rpc.system.properties() as shown in the Polkadot JS Apps Toolbox:

RPC call in the UI toolbox reveals system properties

The image above shows that the current chain’s SS58Format is 2 - that’s the prefix for Kusama.

We’ll put the address validation check into Profile.vue to keep the Identity component isolated and atomic, as per the single responsibility principle. The component’s only responsibility is finding and displaying identity data. It assumes it already got a sanitized address.

First, let’s find out the current chain’s prefix as soon as the Profile page is loaded.

  created: function() {
    api.then(async (api) => {
      this.currentPrefix = (
        await api.rpc.system.properties()
      ).toJSON().ss58Format;
    });
  },

As soon as the component (in this case Profile.vue) is instantiated, it will set the currentPrefix data property to the ss58Format property of the JSON version of the result of api.rpc.system.properties.

Naturally, we need to import the api connection and define this data property.


// ...

import api from "@/connection.js"; // <-- this is new

// ...

  data() {
    return {
      address: "",
      newaddress: "",
      currentPrefix: "",
    };
  },

// ..

Refreshing the app should already reveal the chain’s prefix in the data properties:

The current chain's prefix is successfully loaded on page load

Now it’s time to validate our address. For this, we’ll need a new dependency: util-crypto. This package contains convenience methods for checking validity, encoding and decoding, and more.

yarn add @polkadot/util-crypto

We’ll need to import the checkAddress function from this package:

import { checkAddress } from "@polkadot/util-crypto";

Finally, let’s rewrite our lookup function to validate the address.

    lookup: function() {
      if (this.validateAddress(this.newaddress)) {
        this.address = this.newaddress;
      }
    },
    validateAddress: function(address) {
      let check = checkAddress(address, this.currentPrefix);
      console.log(check[1]);
      this.error = check[1];
      return check[0];
    },

We make the lookup function check if the address is valid through the validateAddress method, and only if it is do we change the address property. Without this change, the <identity> instance in our template never reacts, which is what we want.

The validateAddress function uses the imported checkAddress function to check for validity of the address in the current prefix’ context. This function returns an array of two values: a boolean (indicating true for success and false for failure) and a string if the validation failed. We log this string and return the boolean.

We’ll need to use this error property we just populated, and we’ll need to add it in our data function.

  data() {
    return {
      address: "",
      error: "",
      newaddress: "",
      currentPrefix: "",
    };
  },

In the template, let’s put it immediately below the input field.

      <div class="row">
        <div class="col-lg-6 col-md-6 col-sm-12 col-xs-12 col-xs-12">
          <input
            class="form-control"
            type="text"
            placeholder="PhngluimglwnafhCthulhuRlyehwgahnaglfhtagn"
            id="idlookup"
            v-model="newaddress"
            v-on:keyup.enter="lookup"
          />
          <p>
            {{ error }}
          </p>
        </div>

Inputting an invalid address will now output a (rather unhelpful) error message.

The error message on wrong address input is displayed

Lookup

At long last, we can build our Identity component’s logic. We can now safely assume the component will be given a sanitized address and focus on the meat of it.

The code that follows all goes into Identity.vue.

As usual, we’ll need our API instance.

import api from "@/connection.js";

Then, we’ll set up a watcher on our address property. Our aim is to trigger a function call whenever the address changes.

watch: {
    address: function(val) {
      this.readIdentity(val);
    },
  },

Now we can create the method which will read the on-chain data:

  methods: {
    readIdentity(address) {
      api.then(async (api) => {
        let id = await api.query.identity.identityOf(address);
        console.log(id.toJSON());
      });
    },
  },

You can verify that this call results in the data you seek by using the Polkadot Apps UI, this time in the Chain State section.

Identity information for the address CpjsLDC1JFyrhm3ftC9Gs4QoyrkHKhZKtK7YqGTRFtTafgp

The JSON we get from this call looks something like this:

JSON identity payload for address CpjsLDC1JFyrhm3ftC9Gs4QoyrkHKhZKtK7YqGTRFtTafgp

An address with no data registered simply returns null. This is a good start, but what do these values mean?

Decoding Data

There are three main properties in an identity’s return payload: deposit (how many tokens the user locked up to reserve an identity record on-chain), info (the data we need), and judgements (how certain registrars judged this person’s data - will be an empty array if the identity has not been judged).

The info property contains data as encoding->value pairs. Raw means that this is a direct encoding from letters to bytes. Indeed, if we take the raw display value of 0x4272756e6f207c20573346 from the image above and use a bytes to UTF-8 converter, we get “Bruno | W3F”.

Image description

The other encoding types are: BlakeTwo256, Sha256, Keccak256 and ShaThree256. Note that these are hashing algorithms, which means it is not possible to unhash the value stored in such a format. Hashed information is generally stored off-chain, and the on-chain hash entry is only used to prove that the content matches by hashing the off-chain content and comparing it with the on chain one. The official UI currently only allows raw types, so we won't deal with the hash edge cases in this guide.

Ok, it’s time to land this airplane. Let’s decode everything and show it in our user card.

To make decoding easy, another convenience package is available: @polkadot/util:

yarn add @polkadot/util

Let’s import the function we need:

import { hexToString } from "@polkadot/util";

Now let’s apply it:

    readIdentity(address) {
      api.then(async (api) => {
        let id = (await api.query.identity.identityOf(address)).toJSON();
        this.user.fullName = (id.info.legal.Raw) ? hexToString(id.info.legal.Raw) : "";
        this.user.email = (id.info.email.Raw) ? hexToString(id.info.email.Raw) : "";      
      });
    },

Gravatar

For some extra coolness, let’s add in a Gravatar for the email.

Gravatar is a global service for attaching avatars to your email address. All you need to call that avatar in any app you use is the MD5 hashed email. Let’s install the md5 package first:

yarn add blueimp-md5

Let’s import it.

const hash = require("blueimp-md5");

Now let’s change the avatar source:

<img class="avatar" :src="avatar" alt="..." />

And define the component’s avatar property. We’ll be dynamically generating the image based on the email if the email is provided, or returning a default avatar if not. This is best done with a computed property - a type of VueJS property which can change value dynamically based on other values already in the component.

  computed: {
      avatar: function() {
          if (this.user.email) {
              return "https://www.gravatar.com/avatar/" + hash(this.user.email) + "?s=200"
          } else {
              return "../../assets/img/anime6.png";
          }
      }
  },

Our Gravatar is now displayed if an identity has an email address set (and, of course, a Gravatar avatar defined).

A user's avatar is displayed based on their email address

The full and final code for this tutorial can be found in the tutorial-identity-2 branch.

Homework

If you’d like to take this further, consider doing any of the following:

  • render all the other info as well, don’t stop at email and name.
  • make the error messages on invalid address input more verbose, explain what’s wrong. If the address is too short, say it’s too short. If it’s of the wrong prefix, maybe consider even encoding and decoding it into the current chain’s prefix. After all, addresses can be easily converted from format to format!
  • Add a green checkmark to a profile if the profile has been verified (look at the judgements property in the identity payload)
  • Add an identicon to generate a crypto-looking unique avatar from an address which doesn’t have email info attached to it.
  • Cover the edge case of hash-encoded values. You can’t display their true values, but you can let the user know they’re hashed and need an off-chain reference. Bonus points: include a hashing function on-page to check provided data against the on-chain hashes.
  • An identity can have custom fields too. In those cases, both the name of the field will be encoded, and the value. Try to cover for that edge case. If you have trouble finding such identities on-chain, power up your own development chain and play around with identities!

Remember to subscribe to our newsletter to be kept up to date on new posts and to be kept in the loop about Web 3.0 developments.

Related posts