Creating a new asset
How to create a new asset for a module of a blockchain application.
As defined in the Hello World application overview, the asset shall provide the following functionality:
-
Anyone can update the hello message in their account.
Sample code
View the complete sample code of this guide on GitHub in the Lisk SDK examples repository. |
Prerequisites
To follow this guide, the following criteria below is assumed:
|
Generating the asset skeleton
In the root folder of the blockchain application, generate a skeleton for the new asset with Lisk Commander.
The command generate:asset
expects 3 arguments:
-
Module name: The name of the module the asset belongs to.
-
Asset name: The name of the new asset. It needs to be a string that only consists of lower case and upper case letters [a-z, A-Z]. No numbers, hyphens, etc., are allowed.
-
Asset ID: The ID of the asset. It needs to be unique within the module. The minimum value is 0.
For a complete overview of all available options of the generate:asset
command, see the Lisk Commander command reference.
As an example, we will add a new asset createHello
to the hello
module:
lisk generate:asset hello createHello 0
This will generate the following files:
Creating asset skeleton with asset name "createHello" and asset ID "0" for module "hello" Using template "lisk-ts" Generating asset skeleton. Registering asset... identical .liskrc.json create src/app/modules/hello/assets/create_hello_asset.ts create test/unit/modules/hello/assets/create_hello_asset.spec.ts No change to package.json was detected. No package manager install will be executed. Your asset is created and ready to use.
The file create_hello_asset.ts
contains the asset skeleton and the file create_hello_asset.spec.ts
contains the related unit test skeletons for the new asset.
Open the asset skeleton in create_hello_asset.ts
:
import { BaseAsset, ApplyAssetContext, ValidateAssetContext } from 'lisk-sdk';
export class CreateHelloAsset extends BaseAsset {
public name = 'createHello';
public id = 0;
// Define schema for asset
public schema = {
$id: 'hello/createHello-asset',
title: 'CreateHelloAsset transaction asset for hello module',
type: 'object',
required: [],
properties: {},
};
public validate({ asset }: ValidateAssetContext<{}>): void {
// Validate your asset
}
// eslint-disable-next-line @typescript-eslint/require-await
public async apply({ asset, transaction, stateStore }: ApplyAssetContext<{}>): Promise<void> {
throw new Error('Asset "createHello" apply hook is not implemented.');
}
}
As can be seen above, the command generate:asset
already created the asset class CreateHelloAsset
which contains skeletons of all important components of an asset.
The only properties which are set at this point are the asset ID and the asset name, which were defined when generating the asset.
If the application is started at this point, it is already possible to create a basic createHello
transaction with an empty transaction asset, sign it, and send it to the node.
The node will then throw the following error:
Asset "createHello" apply hook is not implemented.
This is due to the fact that the apply()
function of the asset is not implemented yet.
To give the asset a purpose, it is necessary to implement certain logic inside of the apply()
function of an asset.
The following sections describe an example implementation of each component of a module asset in detail.
The asset class
The asset class always extends from the BaseAsset
, which is imported from the lisk-sdk
package.
The properties name
and id
are prefilled by the values used when generating the asset skeleton was completed in the previous step.
import { BaseAsset, ApplyAssetContext, ValidateAssetContext } from 'lisk-sdk';
export class CreateHelloAsset extends BaseAsset {
public name = 'createHello';
public id = 0;
// ...
}
The asset schema
The asset schema defines in which format data is sent in the transaction asset.
For more information about schemas and how they are used in the Lisk SDK, check out the Codec & schema. |
We expect the following data in a transaction to be able to create a new hello message:
-
helloString
: The string which will be saved underhelloMessage
in the senders user account.
Therefore, the asset schema is adjusted accordingly as shown below:
public schema = {
$id: 'lisk/hello/asset',
type: 'object',
required: ["helloString"], (1)
properties: {
helloString: {
dataType: 'string', (2)
fieldNumber: 1, (3)
minLength: 3, (4)
maxLength: 64, (5)
},
}
};
1 | The property helloString is required to create a hello message. |
2 | string is defined as a data type for helloString . |
3 | The fieldNumber increments by +1 for each property in the transaction asset. |
4 | The minimum length of helloString is set to 3 characters. |
5 | The maximum length of helloString is set to 64 characters. |
Validating the asset data
The optional function validate()
validates the data of a transaction asset, before it is passed to the apply()
function.
If one of these conditions is not fulfilled, then the transaction will not be processed, and an error should be thrown.
The minimum and maximum values for the different properties which are defined in The asset schema do not need to be validated again in the validate() function.
|
In this example, we want to validate that it is not possible to create a hello message with some illegal statement.
If any account sends a createHello
transaction, with asset.helloString
equal to Some illegal statement
, it will throw the error Illegal hello message: Some illegal statement
.
public validate({ asset }: ValidateAssetContext<{}>): void {
if (asset.helloString == "Some illegal statement") {
throw new Error(
'Illegal hello message: Some illegal statement'
);
}
}
If the validation does not throw any errors, it means the validation has been successful, and the apply()
function will be executed as the next step.
Defining the asset logic
The most important part of the module asset is the apply()
function.
It contains the logic of how the data in the transaction asset should be applied on the blockchain.
In this example, we use the transaction data to create a new hello message, which is added to the senders account.
Additionally, the hello counter is incremented by +1 for each applied hello transaction.
To get and set the blockchain state, the stateStore is used again, which is already known from the lifecycle hooks of the module guide.
public async apply({ asset, transaction, stateStore }: ApplyAssetContext<{}>): Promise<void> {
// 1. Get account data of the sender of the hello transaction
const senderAddress = transaction.senderAddress;
const senderAccount = await stateStore.account.get(senderAddress);
// 2. Update hello message in the senders account with thehelloString of the transaction asset
senderAccount.hello.helloMessage = asset.helloString;
stateStore.account.set(senderAccount.address, senderAccount);
// 3. Get the hello counter from the database
let counterBuffer = await stateStore.chain.get(
CHAIN_STATE_HELLO_COUNTER
);
// 4. Decode the hello counter
let counter = codec.decode(
helloCounterSchema,
counterBuffer
);
// 5. Increment the hello counter +1
counter.helloCounter++;
// 6. Encode the hello counter and save it back to the database
await stateStore.chain.set(
CHAIN_STATE_HELLO_COUNTER,
codec.encode(helloCounterSchema, counter)
);
}