Codec & schema

Schemas are used in various places in the Lisk SDK to encode and decode the data that is retrieved or pushed to the database.

Schemas are specifically used in the following:

  • modules to define the accountSchema, which consists of module-specific properties that are added to each account by the module.

  • assets, for the schema, which defines the data structure and formats of the transaction asset.

  • modules and/or assets, to decode/encode other specific data from the database, such as blocks, transactions, account state, and chain state data.

Schema Format

Schemas must be defined as shown below, which is a modified JSON (JavaScript Object Notation), schema (see the JSON schema reference).

It is required to use camelCase for the key naming.
If the data type of a property is either an object or an array, the type property must be used instead of dataType.
Example of an account schema of a module
export const lnsAccountPropsSchema = {
	$id: 'lisk/lns/lnsAccount',
	type: 'object',
	required: ['ownNodes', 'reverseLookup'],
	properties: {
		reverseLookup: {
			dataType: 'bytes',
			fieldNumber: 1,
		},
		ownNodes: {
			type: 'array',
			fieldNumber: 2,
			items: {
				dataType: 'bytes',
			},
		},
	},
	default: {
		ownNodes: [],
		reverseLookup: EMPTY_BUFFER,
	},
};

$id

Unique identifier of the schema throughout the system.

The $id property is directly inherited from the JSON-schema. You can read more about the id property on https://json-schema.org/understanding-json-schema/structuring.html#id.

In general, adhere to the following criteria:

  • Use unique IDs across the system.

  • Use path like format for easy readability, (it is not an actual requirement).

  • in order to ensure no mixing of any schema with other registered schemas occurs, use one fixed identifier for your app in each ID.

  • `

type

Root type of the schema must be type object.

required

Required schema properties.

Regarding account and asset schemas:

If the schema is used for serialization it is recommended to put all properties as required to guarantee the uniqueness of encoding.

properties

Properties of the schema.

default

Default property values.

Do not define any default properties for asset schemas.

Data types

The application data is stored in specific data types and structures in the database.

A schema always defines the data types that will be used in the database to store specific data. When the data is retrieved from the database by a module or plugin of the blockchain application, it is returned as a JavaScript object or JSON, depending on the context:

JavaScript object

These data types are used internally in the blockchain application to handle data from the database.

JSON

Data that is provided by actions and events is always returned in JSON format.

Table 1. Table: Data types of the different data structures
Data type JavaScript object JSON

string

string

string

uint32

number

number

sint32

number

number

uint64

BigInt

string

sint64

BigInt

string

bytes

Buffer

string in hex format

boolean

boolean

boolean

Decoding and encoding data

With the API client

Please refer to the dedicated guide Decoding & encoding data.

With lisk-codec

To conveniently decode and encode the data structures stored on the blockchain, such as blocks, transactions, account state, and chain state, use the codec library. The library can be imported from the following NPM packages:

  • lisk-sdk

  • @liskhq/lisk-client

  • @liskhq/lisk-codec

Example: Importing the codec library from the lisk-sdk package
const {
    codec,
} = require('lisk-sdk');

const CHAIN_STATE_KEY = "myContext:moreContext";

const schema = {
    $id: "lisk/myContext/moreContext",
    type: "object",
    required: ["myCounter"],
    properties: {
        myCounter: {
            dataType: "uint32",
            fieldNumber: 1,
        },
    },
    default: {
      myCounter: 0
    }
};

// Get data from the database
let counterBuffer = await stateStore.chain.get(
    CHAIN_STATE_KEY
);

// Decode the retrieved data with the schema
let counter = codec.decode(
    schema,
    counterBuffer
);

// Mutate the retrieved data
counter.myCounter++;

// Post the data back to the database
await stateStore.chain.set(
    CHAIN_STATE_KEY,
    // Encode the data again before sending it to the DB
    codec.encode(schema, counter)
);

Converting between formats

Account addresses are stored as binary data in the database. However, in other parts of the application, the data is expected as a hexadecimal string, which is more human-readable.

On the other hand, the node API always returns the data such as blocks and transactions in hex string. However, please be aware that to decode transactions or blocks, they need to be in Buffer format.

In these cases, it is necessary to convert between Buffer and String as described below:

String to Buffer

Use Buffer.from(data, 'hex') to convert a hex string to a Buffer.

this._channel.subscribe('app:block:new', async (data) => {
  const { block } = data;
  const { payload } = codec.decode(
    this.schemas.block,
    Buffer.from(block, 'hex'),
  );
  // ...
});

Buffer to String

Use .toString('hex') to convert a Buffer to a hex string.

this._channel.publish('srs:configCreated', {
  address: transaction._senderAddress.toString('hex'),
  // ...
});