Hello World

In the Hello World tutorial, you learn how to create a simple blockchain application, including a basic frontend. It describes all important steps that are required to build a blockchain application with the Lisk SDK. The Hello World app is also a good entry point to start from and extend the app further for your use case.

For the full code example please see Hello World App on Github.

Blockchain application

Showcases a minimal setup of a blockchain application which includes the following:

lisk-sdk-examples/tutorials/hello-world/blockchain_app
├── blockchain_app
│   ├── genesis-block.json
│   ├── hello_module
│   │   ├── hello_module.js (1)
│   │   ├── hello_asset.js (2)
│   │   ├── index.js
│   │   └── schemas.js
│   ├── hello_plugin (3)
│   │   └── index.js
│   ├── index.js
│   └── package.json
1 A custom module, which is containing the on-chain logic related to the hello property .
2 A custom asset, which is handling the logic for the hello transaction inside the hello_module.
3 A plugin, which is providing additional off-chain API endpoints related to the hello_module.

Client application

A simple react frontend to complement the Hello World blockchain application. This allows the user to create and fund new accounts, transfer tokens, send hello transactions, and show account details.

lisk-sdk-examples/tutorials/hello-world/react-client
└── react-client
    ├── README.md
    ├── package.json
    ├── src
    │   ├── accounts.json
    │   ├── api.js (1)
    │   ├── components (2)
    │   │   ├── Account.js
    │   │   ├── App.js
    │   │   ├── Faucet.js
    │   │   ├── Hello.js
    │   │   ├── NewAccount.js
    │   │   ├── Transfer.js
    │   │   └── home.js
    │   ├── index.html
    │   ├── index.js
    │   └── utils.js
    └── webpack.config.js
1 Exports various functions to get data from the API.
2 Contains the different react components for the different pages of the Hello World application.

Setup

Setup of the blockchain application

To set up the Hello World app, clone the repository and navigate into the app as shown below:

In the terminal
git clone https://github.com/LiskHQ/lisk-sdk-examples.git
cd lisk-sdk-examples/tutorials/hello-world/blockchain_app

Now install all required node modules for the node application by executing the following command below:

lisk-sdk-examples/tutorials/hello-world/blockchain_app
npm install

The next step is to spin up the devnet node by executing the following command:

node index.js

If the setup was successful, it will be possible to view the logs of the node in the terminal.

Setup of the client application

Now, navigate into the react-client folder and start the frontend application:

cd ../react-client
npm install

Now start the client:

npm start

This will launch the react client in a new browser window.

With the blockchain application and the react client both running on your local machine, it is possible to utilize the frontend to verify that the Hello World application was installed successfully and works as expected.

The Hello World app

Below you find a summary of all pages of the client application.

You can also explore it yourself in the browser before moving on with the tutorial.

Home page

The general landing page of the Hello World app.

It displays a welcome message, the hello counter, and the latest hello message and its sender.

home

Creating a new account

Creates new account credentials. Refresh the page to get new ones.

create account

The faucet

A faucet that sends funds from the genesis account to the specified recipient.

faucet

Sending a Hello transaction

A page to create and send a hello transaction.

send hello

The counter and "latest hello message" values will update on the home page after sending the hello transaction:

Updated home page

Get account details

Shows the information about an account.

Account details

Transferring tokens

To test the token transfer, simply create another account, and use the new account as recipient.

transfer

Building the blockchain application

The file blockchain_app/index.js is the entry point to the blockchain application. Here, we import the Application from the lisk-sdk package, which is used to create a new application instance. The Application expects a genesis block and a configuration object as arguments.

After creating the application instance, it is possible to register custom modules and plugins with the application. The implementation of the HelloModule and the HelloAPIPlugin is explained below.

As last step, the application instance is started.

blockchain_app/index.js
const { Application, configDevnet, utils } = require('lisk-sdk');
const genesisBlockDevnet = require('./genesis-block');
const { HelloModule } = require('./hello_module');
const { HelloAPIPlugin } = require('./hello_plugin');

// Update genesis block accounts to include the hello attribute
genesisBlockDevnet.header.asset.accounts = genesisBlockDevnet.header.asset.accounts.map(
    (a) =>
        utils.objects.mergeDeep({}, a, {
            hello: {
                helloMessage: ''
            },
        }),
);

// Create a custom config based on the configDevnet
const appConfig = utils.objects.mergeDeep({}, configDevnet, {
    label: 'hello-app',
    genesisConfig: { communityIdentifier: 'hello' },
    rpc: {
        enable: true,
        mode: 'ws',
        port: 8888,
    },
    network: {
        port: 8887,
    },
    logger: {
        consoleLogLevel: 'info',
    },
});

// Create the application instance
const app = Application.defaultApplication(genesisBlockDevnet, appConfig);

// Register Modules
app.registerModule(HelloModule);

// Register Plugins
app.registerPlugin(HelloAPIPlugin);

// Starts the application
app
	.run()
	.then(() => app.logger.info('App started...'))
	.catch(error => {
		console.error('Faced error in application', error);
		process.exit(1);
	});

The hello module

The hello module extends like every other module from the BaseModule. Inside of the module, we define the different properties, which are described below.

blockchain_app/hello_module/hello_module.js
const { BaseModule, codec } = require('lisk-sdk');
const { HelloAsset, HelloAssetID } = require('./hello_asset');
const {
    helloCounterSchema,
    helloAssetSchema,
    CHAIN_STATE_HELLO_COUNTER
} = require('./schemas');

class HelloModule extends BaseModule { (1)
    name = 'hello'; (2)
    id = 1000; (3)
    accountSchema = { (4)
        type: 'object',
        properties: {
            helloMessage: {
                fieldNumber: 1,
                dataType: 'string',
            },
        },
        default: {
            helloMessage: '',
        },
    };
    transactionAssets = [ new HelloAsset() ]; (5)
    actions = { (6)
        amountOfHellos: async () => {
            const res = await this._dataAccess.getChainState(CHAIN_STATE_HELLO_COUNTER);
            const count = codec.decode(
                helloCounterSchema,
                res
            );
            return count;
        },
    };
    events = ['newHello']; (7)
    async afterTransactionApply({transaction, stateStore, reducerHandler}) { (8)
      // If the transaction is a hello transaction
      if (transaction.moduleID === this.id && transaction.assetID === HelloAssetID) {
        // Decode the transaction asset
        const helloAsset = codec.decode(
          helloAssetSchema,
          transaction.asset
        );

        // And publish a new hello:newHello event,
        // including the latest hello message and the sender.
        this._channel.publish('hello:newHello', {
          sender: transaction._senderAddress.toString('hex'),
          hello: helloAsset.helloString
        });
      }
    };
    async afterGenesisBlockApply({genesisBlock, stateStore, reducerHandler}) { (9)
      // Set the hello counter to zero after the genesis block is applied
      await stateStore.chain.set(
        CHAIN_STATE_HELLO_COUNTER,
        codec.encode(helloCounterSchema, { helloCounter: 0 })
      );
    };
}

module.exports = HelloModule;
1 The HelloModule extends like every other module from the BaseModule.
2 The name for the module. Must be unique within the application.
3 The module ID. The lowest possible module ID is 1024. Must be unique within the application.
4 accountSchema defines additional data structures, that are added to the accounts by this module.
5 transactionAssets contains all custom assets that are included in the module. Here, we include only one asset, the hello asset.
6 actions defines all available actions of the module. Here, we define one action amountOfHellos, which returns the total amount of sent hello transaction from the db.
7 events defines all available events of the module. Here, we include one event newHello
8 afterTransactionApply: Code in here is applied after each transaction is applied. Here, we look for transactions with the module ID of the hello module, and the asset ID of the hello asset. If we the criteria are met, the hello module publishes a new event hello:newHello.
9 afterGenesisBlockApply: Code in here is applied after the genesis block is applied. Here, we initially set the hello counter to zero, after the genesis block is applied.

The schemas are saved in a separate file and can be imported to other files where needed.

blockchain_app/hello_module/schemas.js
const CHAIN_STATE_HELLO_COUNTER = "hello:helloCounter";

const helloCounterSchema = {
    $id: "lisk/hello/counter",
    type: "object",
    required: ["helloCounter"],
    properties: {
        helloCounter: {
            dataType: "uint32",
            fieldNumber: 1,
        },
    },
};

const helloAssetSchema = {
  $id: "lisk/hello/asset",
  type: "object",
  required: ["helloString"],
  properties: {
    helloString: {
      dataType: "string",
      fieldNumber: 1,
    },
  },
};

module.exports = {
    CHAIN_STATE_HELLO_COUNTER,
    helloCounterSchema,
    helloAssetSchema
};

The hello asset

Next, it is needed to write the code for the custom asset which we defined in the hello module above. Inside of the asset, we define the different properties, which are described below.

blockchain_app/hello_module/hello_asset.js
const {
    BaseAsset,
    codec,
} = require('lisk-sdk');
const {
    helloCounterSchema,
    CHAIN_STATE_HELLO_COUNTER
} = require('./schemas');

const HelloAssetID = 0;

class HelloAsset extends BaseAsset { (1)
    name = 'helloAsset'; (2)
    id = HelloAssetID; (3)
    schema = { (4)
        $id: 'lisk/hello/asset',
        type: 'object',
        required: ["helloString"],
        properties: {
            helloString: {
                dataType: 'string',
                fieldNumber: 1,
            },
        }
    };

    validate({asset}) { (5)
        if (!asset.helloString || typeof asset.helloString !== 'string' || asset.helloString.length > 64) {
          throw new Error(
                'Invalid "asset.hello" defined on transaction: A string value no longer than 64 characters is expected'
            );
        }
    };

    async apply({ asset, stateStore, reducerHandler, transaction }) { (6)
        // Get sender account details
        const senderAddress = transaction.senderAddress;
        const senderAccount = await stateStore.account.get(senderAddress);
        // Add the hello string to the sender account
        senderAccount.hello.helloMessage = asset.helloString;
        stateStore.account.set(senderAccount.address, senderAccount);
        // Get the hello counter and decode it
        let counterBuffer = await stateStore.chain.get(
            CHAIN_STATE_HELLO_COUNTER
        );
        let counter = codec.decode(
            helloCounterSchema,
            counterBuffer
        );
        // Increment the hello counter by +1
        counter.helloCounter++;
        // Save the updated counter on the chain
        await stateStore.chain.set(
            CHAIN_STATE_HELLO_COUNTER,
            codec.encode(helloCounterSchema, counter)
        );
    }
}

module.exports = { HelloAsset, HelloAssetID };
1 The HelloAsset extends like every other module from the BaseModule.
2 The name for the asset. Must be unique within the module.
3 The asset ID. Must be unique within the module.
4 schema defines structure for the data in the transaction asset.
5 validate validates the data in the transaction asset, before it is applied.
6 apply applies the desired changes in the database, based on the data in the transaction asset.

The Hello API plugin

In addition to the hello module, a HelloAPIPlugin is added which provides additional API endpoints. These API endpoints allow the react client to get information about the newly created hello properties of the application via API calls.

blockchain_app/hello_plugin/index.js
const { BasePlugin } = require("lisk-sdk");
const pJSON = require("../package.json");

class HelloAPIPlugin extends BasePlugin { (1)
  _server = undefined;
  _app = undefined;
  _hello = undefined;

  static get alias() { (2)
    return "HelloAPI";
  }

  static get info() { (3)
    return {
      author: pJSON.author,
      version: pJSON.version,
      name: pJSON.name,
    };
  }

  get defaults() {
    return {};
  }

  get events() {
    return [];
  }

  get actions() { (4)
    return {
      latestHello: () => this._hello,
    };
  }

  async load(channel) { (5)
    channel.subscribe('hello:newHello', (info) => {
      this._hello = info;
    });
  }

  async unload() { (6)
  }
}

module.exports = { HelloAPIPlugin };
1 The HelloAPIPlugin extends like every other module from the BasePlugin.
2 Returns the name of the plugin. Must be unique within the application.
3 Returns general information about the plugin.
4 Contains the actions which the plugin provides.
5 load will be invoked by the controller to load the plugin.
6 unload will be invoked by the controller to unload the plugin.

Building the client application

Finally, we build a simple frontend application, to be able to conveniently test the blockchain application in the browser.

The development of the client application is absolutely flexible, and you can use any technology stack that you feel comfortable with.

In this example, we use React.js to build the client application.

Components

The below example shows the implementation of a React component that allows the user to send a hello transaction. We receive the required values for the transaction from the form, and use the API client to create a transaction object based on the form data. Finally, the transaction is posted to the network.

react-client/src/components/Hello.js
import { cryptography, transactions } from '@liskhq/lisk-client';
import React, { Component, useState } from 'react';
import * as api from '../api.js';

const Hello = () => {
    const [state, updateState] = useState({
        hello: '',
        fee: '',
        passphrase: '',
        transaction: {},
        response: {}
    });

    const handleChange = (event) => {
        const { name, value } = event.target;
        updateState({
            ...state,
            [name]: value,
        });
    };

    const handleSubmit = async (event) => {
        event.preventDefault();

        const client = await api.getClient();
        const tx = await client.transaction.create({
            moduleID: 1000,
            assetID: 0,
            fee: BigInt(transactions.convertLSKToBeddows(state.fee)),
            asset: {
                helloString: state.hello,
            },
        }, state.passphrase);

        let res = '';
        try {
          res = await client.transaction.send(tx);
        } catch (error) {
          res = error;
        }
        updateState({
            transaction: client.transaction.toJSON(tx),
            response: err,
            hello: '',
            fee: '',
            passphrase: '',
        });
    };

    return (
        <div>
            <h2>Hello</h2>
            <p>Send a Hello transaction.</p>
            <form onSubmit={handleSubmit}>
                <label>
                    Hello message:
                        <input type="text" id="hello" name="hello" onChange={handleChange} value={state.hello} />
                </label>
                <label>
                    Fee:
                        <input type="text" id="fee" name="fee" onChange={handleChange} value={state.fee} />
                </label>
                <label>
                    Passphrase:
                        <input type="text" id="passphrase" name="passphrase" onChange={handleChange} value={state.passphrase} />
                </label>
                <input type="submit" value="Submit" />
            </form>
            <div>
                <pre>Transaction: {JSON.stringify(state.transaction, null, 2)}</pre>
                <pre>Response: {JSON.stringify(state.response, null, 2)}</pre>
            </div>
        </div>
    );
}
export default Hello;

API

The API allows the client to communicate with the blockchain application. We define different helper functions, that can be reused by the different React components.

react-client/src/api.js
const { apiClient, cryptography } = require('@liskhq/lisk-client');
const RPC_ENDPOINT = 'ws://localhost:8888/ws';

let clientCache;

export const getClient = async () => {
    if (!clientCache) {
        clientCache = await apiClient.createWSClient(RPC_ENDPOINT);
    }
    return clientCache;
};

export const sendTransactions = async (tx) => {
    return fetch(LISK_API + "/api/transactions", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify(tx),
    });
};

export const fetchAccountInfo = async (address) => {
    const client = await getClient();
    return client.account.get(cryptography.getAddressFromBase32Address(address));
}

export const fetchHelloCounter = async () => {
    const client = await getClient();
    return client.invoke('hello:amountOfHellos');
}

export const fetchLatestHello = async () => {
    const client = await getClient();
    return client.invoke('HelloAPI:latestHello');
};

These are the most important parts of the Hello World client application. For more information, try out and explore the complete code example in the lisk-sdk-examples GitHub repository.