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> {
}
}