Starting with Polkadot Development (Part II)

Michael Michailidis
Published in
8 min readMay 17, 2021

--

In the previous part of this tutorial, we learned how to set up a development node in our local environment using Docker. We also launched the Polkadot.js open source UI project and connected with this node, allowing us to do transactions without broadcasting them to the entire network. Now we are going to learn by reading the code.

Creating a Node.js Project and Connecting

Starting our node in development mode gave us a few accounts filled with DOTs (Polkadot’s currency). We could (and will) be using those, but in general, creating an account is paramount to any development effort, so we better learn how it’s done. Our task is to read through the code from the Polkadot.js project and implement it on Node.js. But since the codebase of this project is highly optimized and not as readable, we will first jump right in a working example using Node. Go into a new folder and:

npm init --yes # easy way to create Node.js project

This will create a package.json file without asking us too many questions. By default that is expecting an index.js file as an entry point to the application, so let’s add it to our project.

touch index.js

For now, we are going to run it simply through

node index.js

Then we need to install the Polkadot module through which we are going to communicate with the network.

npm i @polkadot/api --save

Within our index.js file, we can now initialize the client in two modes, HTTP and WS (Web Sockets). Remember how in the first part of this tutorial, we started our server w --ws-external? This was meant to enable WS connection, which we are not going to use.

// index.js
const { ApiPromise, WsProvider } = require('@polkadot/api');

const connect = async () => {
const wsProvider = new WsProvider('ws://127.0.0.1:9944');
const api = new ApiPromise({ provider: wsProvider });
return api.isReady;
};

connect().then((api) => {
console.log(`Our client is connected: ${api.isConnected}`);
}).catch((err) => {
console.error(err)
}).finally(() => process.exit());

We initialize the client via a WSProvide, the alternative being an HttpProvider. api.isReady returns a promise which resolves in the client instance, which we can then use to check the connection above is true.

Creating an Account

Next, we need to create an account like so. First, we initialize a Keyring, which you can think of as an internal store that saves a key-pair that makes up an account. More specifically, and although we are simplifying greatly, an account is made up of a public and private key. An address such as the ones generated for free upon initialization through the --aliceflag, is essentially a hash of the public key, while the private key is what signs the transactions and must be kept a secret. The Keyring is a nice way to keep the private key from being exposed to the rest of the code while maintaining the capability of signing a transaction. Add this to our file

const { mnemonicGenerate } = require('@polkadot/util-crypto');
const { Keyring } = require('@polkadot/keyring');
const keyring = new Keyring({type: 'sr25519'});
const mnemonic = mnemonicGenerate();
const account = keyring.addFromMnemonic(mnemonic);
console.log(`Address: ${account.address}`);
console.log(`Mnemonic: "${mnemonic}"`);

A private/public key-pair is usually created via an algorithm that takes a single input value called a seed. The insight here is that the algorithm is deterministic, which means that without input it would generate the same keys every time it ran, which is not what we expect now, is it? The seed differentiated the initial variable of this algorithm and so it’s usually random. That is what mnemonicGenerate() does, it creates a random seed. The value it returns however is not a number but a list of words. This method was first proposed by Bitcoin and later incorporated by most crypto-currencies. The words that you see are essentially an offset in a dictionary. Concatenate the offsets and you get the seed. As the name implies, it’s meant as an easier way to store the initialization secrete, even in your own head!

Why save it you may ask. Remember how the key-derivation algorithm was described as deterministic. That means that if you generate a key-pair using the same mnemonic, you get the same key, which is indispensable. Let’s include this functionality as well.

const { mnemonicValidate } = require('@polkadot/util-crypto');const createAccount = (mnemonic) => {
mnemonic = mnemonic && mnemonicValidate(mnemonic)
? mnemonic
: mnemonicGenerate();
const account = keyring.addFromMnemonic(mnemonic);
return { account, mnemonic };
}

Here, we can optionally pass an existing mnemonic and use that to generate our keys. The object returned from the .addFromMnemonic(...)function is of a special type called a KeyringPair. If we were to program in Typescript, we would get an explicit type back, but since we are not, we cannot see it in our code, and yet, it’s there. This object has an address field that holds the address as a string, so let’s check that what we did was valid.

const { account: acc1, mnemonic } = createAccount();
const { account: acc2 } = createAccount(mnemonic);

console.log(`Mnemonic: "${mnemonic}"`);
console.log(`- Address 1: ${acc1.address}`);
console.log(`- Address 2: ${acc2.address}`);

In my case, the output is like so:

Mnemonic: "cruel leader remember night skill clump question focus nurse neck battle federal"
- Address 1: 5HW9P7pCSMK3wUYy5gxcC6MZaJVUwNriY9z5EAQjSrqfRjLw
- Address 2: 5HW9P7pCSMK3wUYy5gxcC6MZaJVUwNriY9z5EAQjSrqfRjLw

The addresses are identical because we reused the same mnemonic. But are these addresses (or rather, address) “on the network?”

We can refresh our Polkadot.js UI from the 1rst part of this tutorial, but we won’t see our newly created address. Where is it? Do we need to broadcast it to the network? In short, the answer is no. An address does not really exist apart from a reference in transactions. As long as there is some transaction that includes the address, it’s visible to the network and can be queried with various tools, otherwise, it might as well not have existed.

Let’s do just that. Copy the mnemonic phrase that was printed on your terminal, and go to the Polkadot UI that is running locally, press “Add Account” and paste it in the box below:

Check “I have saved my mnemonic seed safely” and click “Next.” Add a name, let’s call it “MEDIUM_1,” and a password. You should see the address that you created on your Node.js application at the bottom of that list. This still does not make the address somehow “public” on the network, but we can move funds into it from one of the other accounts that were created for us when we started this Polkadot node locally.

Find the account on the drop-down list, set an amount, and click on Make Transfer. Since the account ALICE was created automatically it has no password set.

You may have noticed a delay until the new account gets credited with the number of funds that you chose to send from ALICE. That is because of a transaction to be finalized, it needs to be “mined” inside of a new block. In the world of crypto, mining is the process whereby new blocks are added to the blockchain that includes all the latest transactions that were propagated on the Polkadot network. Depending on the network you are in, the time for a new block will carry, but if you’ve set up your Polkadot node in the way I showed in the first part of this tutorial, this interval should be 6 seconds. You can see blocks get generated by navigating to the Network > Explorer tab and checking the timer at the top left.

Reading our balance off the Network

How do we actually check for these funds? Since the addresses are deterministic, we can recreate them at our heart’s content as long as we have saved the mnemonic. The actual transaction of transferring the funds was not saved in any local application but on the blockchain itself, which by nature is immutable. So all we actually need is to recreate the address using the mnemonic we just pasted in the UI, and call a function on the API to get the funds.

The complete code for the Node.js application looks like this:

const balance = await api.derive.balances?.all(account);
const available = balance.availableBalance;

Notice howavailableBalance returned not a number, but an object. That object is a “big number,” that is meant to store numerical values with increased accuracy by representing them as a byte array. To get a readable value we just call .toNumber() .

Before that, however, we need to understand what currency denomination this amount is in. The Polkadot coin is called a DOT, but just like with Bitcoin, amounts are usually represented in smaller fractions, making them easily manageable on the software layer where floating-point arithmetic can be lossy. And since Polkadot is somewhat “network agnostic,” in that it’s a “network of networks,” we need to get this fraction directly from the network we are connected with and divide our amount with it.

const dots = available / (10 ** api.registry.chainDecimals);

The complete code for the Node.js application looks like this:

// index.jsconst { 
mnemonicGenerate,
mnemonicValidate
} = require('@polkadot/util-crypto');
const {
ApiPromise,
WsProvider
} = require('@polkadot/api');
const {
Keyring
} = require('@polkadot/keyring');
const keyring = new Keyring({type: 'sr25519'});const connect = async () => {
const wsProvider = new WsProvider('ws://127.0.0.1:9944');
const api = new ApiPromise({ provider: wsProvider });
return api.isReady;
};

const createAccount = (mnemonic) => {
mnemonic = mnemonic && mnemonicValidate(mnemonic)
? mnemonic
: mnemonicGenerate();
const account = keyring.addFromMnemonic(mnemonic);
return { account, mnemonic };
}
const main = async (api) => {
console.log(`Our client is connected: ${api.isConnected}`);

const mnemonic = 'cruel leader remember night skill clump question focus nurse neck battle federal';
const { account: medium1 } = createAccount(mnemonic); const balance = await api.derive.balances.all(medium1.address);
const available = balance.availableBalance.toNumber();
const dots = available / (10 ** api.registry.chainDecimals);
const print = dots.toFixed(4);
console.log(`Address ${medium1.address} has ${print} DOT`);
};
connect().then(main).catch((err) => {
console.error(err)
}).finally(() => process.exit());

note: it’s recommended to work not with plain JS numerical values as we did here but with BN (big number) instances. We’ll do that in the next tutorial.

Finilising the Transaction

You may have noticed that

Let’s see how to do all that programmatically in part 3 of this tutorial.

--

--

Michael Michailidis
Coinmonks

I am a software engineer working in Node.js + iOS with an eye for blockchain applications!