How to create a module
How to create a new module for a Klayr blockchain client.
Complete application code
View the complete sample code of the complete "Hello World" Klayr app on GitHub in the Klayr SDK examples repository. |
1. Generating the module file- & folder structure
Prerequisites
To use this guide, the following criteria is assumed:
|
While in the root folder of the client, generate a skeleton for the new module with Klayr Commander.
The command klayr generate:module
expects one argument:
-
The module name. It needs to be a string in camelCase, and always starts with a lower case letter. No numbers, hyphens, etc., are allowed. Needs to be unique within the client.
For a complete overview of all available options of the generate:module
command, type generate:module --help
.
As an example, we use the module name hello
:
klayr generate:module hello
The module name should contain only characters from the set [a-z0-9!@$&_.] .
|
This will generate the following file- & folder structure:
├── bin/ ├── config/ ├── src/ │ ├── app/ │ │ ├── app.ts │ │ ├── index.ts │ │ ├── modules/ │ │ │ └── hello/ (1) │ │ │ ├── endpoint.ts (2) │ │ │ ├── events/ (3) │ │ │ ├── method.ts (4) │ │ │ ├── module.ts (5) │ │ │ └── stores/ (6) │ │ ├── modules.ts │ │ ├── plugins/ │ │ └── plugins.ts │ └── commands/ └── test/ ├── integration/ ├── network/ └── unit/ ├── modules/ │ └── hello/ │ └── modules.spec.ts (7) └── plugins/
1 | The root folder hello/ for the module is created under src/app/modules/ . |
2 | endpoint.ts : Contains the module endpoints.
For more information on how to create endpoints for a module, check out the guide: How to create endpoints and methods. |
3 | events/ : Contains the blockchain events of the module.
This folder is empty at first.
For more information on how to create a blockchain event, check out the guide: How to create a blockchain event. |
4 | method.ts : Contains the module methods.
For more information on how to create module methods, check out the guide: How to create endpoints and methods. |
5 | module.ts : Contains the class of the Hello module. |
6 | stores/ : Contains on-chain and off-chain stores of the module.
This folder is empty at first.
For more information on how to create a module store, check out the guide: How to create stores. |
7 | modules.spec.ts : Will contain unit tests for the module.
For more information on how to write a unit test for a module, check out the Testing the blockchain application guide. |
Klayr Commander will also automatically register the module to the client, by adding it to the file src/app/modules.ts
:
import { Application } from 'klayr-sdk';
import { HelloModule } from "./modules/hello/module";
export const registerModules = (app: Application): void => {
app.registerModule(new HelloModule());
};
Now, let’s take a look at the module skeleton:
2. Module class & skeleton
The command generate:module
already created the class HelloModule
which contains skeletons for the most important components of the module.
The module class always extends from the BaseModule
, which is imported from the klayr-sdk
package.
However, this module is not performing any functions yet. To give the module a purpose, it is necessary to implement certain logic inside of the module.
The following guides explain how the different components of a module can be used to implement the desired logic for the module.
Module skeleton of the Hello module
import {
BaseModule,
ModuleInitArgs,
InsertAssetContext,
BlockVerifyContext,
TransactionVerifyContext,
VerificationResult,
TransactionExecuteContext,
GenesisBlockExecuteContext,
ModuleMetadata,
BlockExecuteContext,
BlockAfterExecuteContext,
} from 'klayr-sdk';
import { HelloEndpoint } from './endpoint';
import { HelloMethod } from './method';
export class HelloModule extends BaseModule {
public endpoint = new HelloEndpoint(this.stores, this.offchainStores);
public method = new HelloMethod(this.stores, this.events);
public commands = [];
public constructor() {
super();
// registration of stores and events
}
public metadata(): ModuleMetadata {
return {
name: '',
endpoints: [],
commands: this.commands.map(command => ({
name: command.name,
params: command.schema,
})),
events: this.events.values().map(v => ({
name: v.name,
data: v.schema,
})),
assets: [],
};
}
// Lifecycle hooks
public async init(_args: ModuleInitArgs): Promise<void> {
// initialize this module when starting a node
}
public async insertAssets(_context: InsertAssetContext) {
// initialize block generation, add asset
}
public async verifyAssets(_context: BlockVerifyContext): Promise<void> {
// verify block
}
// Lifecycle hooks
public async verifyTransaction(_context: TransactionVerifyContext): Promise<VerificationResult> {
// verify transaction will be called multiple times in the transaction pool
}
public async beforeCommandExecute(_context: TransactionExecuteContext): Promise<void> {
}
public async afterCommandExecute(_context: TransactionExecuteContext): Promise<void> {
}
public async initGenesisState(_context: GenesisBlockExecuteContext): Promise<void> {
}
public async finalizeGenesisState(_context: GenesisBlockExecuteContext): Promise<void> {
}
public async beforeTransactionsExecute(_context: BlockExecuteContext): Promise<void> {
}
public async afterTransactionsExecute(_context: BlockAfterExecuteContext): Promise<void> {
}
}