How to register a sidechain
On this page, you’ll learn how to:
-
Register a blockchain as a sidechain on the mainchain
-
Register the mainchain on a sidechain
Registration overview
To complete the sidechain registration process, perform the following steps:
Please read Sidechain Registration & Recovery to get more information about the lifecycle of a sidechain. |
1. How to register a sidechain on the mainchain
A sidechain is registered on the mainchain by posting a Sidechain Registration transaction.
A Sidechain Registration transaction can be sent by any user account on the Klayr Mainchain that has adequate funds to pay the required fee.
The transaction requires specific parameters. It is recommended to prepare the transaction and all required parameters in a dedicated script.
The process of registering a sidechain on the mainchain is explained by following the existing example sidechain_registration.ts, which can be found in the interoperability example in the Klayr SDK repository. |
1.1. Sidechain registration fee
When the "Sidechain Registration" command is processed, it creates a sidechain account in the mainchain state which is associated with a unique chain identifier and a name. Hence, every new sidechain occupies a certain namespace within the ecosystem. Additionally, every newly registered sidechain can increase the size of every cross-chain update command posted on the mainchain, because of the longer Merkle proofs.
For these two reasons, the minimum fee for this command has an added constant similar to the extra fee in a Register Validator transaction.
The extra registration fee for a sidechain registration is 10 KLY tokens. |
1.2. Connect to a mainchain and a sidechain node
import { apiClient, codec, sidechainRegParams, cryptography, Transaction } from 'klayr-sdk';
// Replace this with the path to a file storing the public and private keys of a mainchain account that will send the sidechain registration transaction.
// (Can be any account with enough tokens).
import { keys } from '../default/dev-validators.json';
(async () => {
const { address } = cryptography;
// Replace this with the alias of the sidechain node(s)
const SIDECHAIN_ARRAY = ['pos-sidechain-example-one', 'pos-sidechain-example-two'];
// Replace this with the alias of the mainchain node(s), e.g. klayr-core
// Note: The number of mainchain nodes should be equal to sidechain nodes, for this script to work properly.
const MAINCHAIN_ARRAY = ['mainchain-node-one', 'mainchain-node-two'];
let i = 0;
for (const nodeAlias of SIDECHAIN_ARRAY) {
// Connect to the sidechain node
const sidechainClient = await apiClient.createIPCClient(`~/.klayr/${nodeAlias}`);
// Connect to the mainchain node
const mainchainClient = await apiClient.createIPCClient(`~/.klayr/${MAINCHAIN_ARRAY[i]}`);
// ...
}
process.exit(0);
})();
Next, get the basic node information from the mainchain and sidechain nodes.
The node information is requested to receive the following values:
-
CHAIN_ID
(Mainchain): The chain ID of the mainchain is required to sign the transaction. -
CHAIN_ID
(Sidechain): The chain ID of the sidechain is required as a parameter of the transaction. -
height
(Sidechain): The latest height of the sidechain is used to get the data about the currently active sidechain validators.
// Get node info data from sidechain and mainchain
const mainchainNodeInfo = await mainchainClient.invoke('system_getNodeInfo');
const sidechainNodeInfo = await sidechainClient.invoke('system_getNodeInfo');
1.3. Get info about the active sidechain validators
To retrieve the BFT parameters of all sidechain validators, invoke the consensus_getBFTParameters endpoint on the sidechain node.
Set the height
to the latest block height on the sidechain in the request parameters.
The validator list needs to be lexicographically sorted after the |
// Get info about the active sidechain validators and the certificate threshold
const { validators: sidechainActiveValidators, certificateThreshold } =
await sidechainClient.invoke('consensus_getBFTParameters', {
height: sidechainNodeInfo.height,
});
// Sort the validators lexicographically after their BLS keys
(sidechainActiveValidators as { blsKey: string; bftWeight: string }[]).sort((a, b) =>
Buffer.from(a.blsKey, 'hex').compare(Buffer.from(b.blsKey, 'hex')),
);
1.4. Prepare transaction with parameters
To create a Sidechain Registration transaction, the following information is required:
All these parameters can be prepared as a JSON object.
Create the Register Sidechain transaction using the prepared parameters, and sign it with a mainchain account, that has enough funds to send the transaction and pay the required transaction fees.
// Define parameters for the sidechain registration
const params = {
sidechainCertificateThreshold: certificateThreshold,
sidechainValidators: sidechainActiveValidators,
chainID: sidechainNodeInfo.chainID,
name: nodeAlias.replace(/-/g, '_'),
};
// Get public key and nonce of the sender account
const relayerKeyInfo = keys[2];
const { nonce } = await mainchainClient.invoke<{ nonce: string }>('auth_getAuthAccount', {
address: address.getKlayr32AddressFromPublicKey(Buffer.from(relayerKeyInfo.publicKey, 'hex')),
});
// Create registerSidechain transaction
const tx = new Transaction({
module: 'interoperability',
command: 'registerSidechain',
fee: BigInt(2000000000),
params: codec.encodeJSON(sidechainRegParams, params),
nonce: BigInt(nonce),
senderPublicKey: Buffer.from(relayerKeyInfo.publicKey, 'hex'),
signatures: [],
});
// Sign the transaction
tx.sign(
Buffer.from(mainchainNodeInfo.chainID as string, 'hex'),
Buffer.from(relayerKeyInfo.privateKey, 'hex'),
);
1.4.1. chainID
The chainID of the sidechain. Has to be unique within the Klayr ecosystem.
If the given value is already taken by another sidechain, the "Sidechain Registration" transaction will fail. In this case, the sidechain has to be restarted completely, or it needs to perform a hardfork to change the chain ID, and resubmit the "Sidechain Registration" transaction with a new value for the chain ID. |
To check if a certain chain ID is still available in the network, call RPC endpoint interoperability_isChainIDAvailable with the chain ID as a parameter.
If the chain ID is still available, it should return true
.
1.4.2. Name
The name of the sidechain is a string that has to be unique within the Klayr ecosystem and should only contain characters from the set [a-z0-9!@$&_.]
.
If the given value is already taken by another sidechain, the "Sidechain Registration" transaction will fail. In this case, the sidechain has to be restarted completely, or it needs to perform a hardfork to change the name, and resubmit the "Sidechain Registration" transaction with a new value for the name. |
To check if a certain name is still available in the network, call RPC endpoint interoperability_isNameAvailable with the name as parameter.
If the name is still available, it should return true
.
1.4.3. sidechainValidators
Defines the set of sidechain validators expected to sign the first certificate from the sidechain. Each item contains the following properties:
-
blsKey
: The public BLS key of the validator. -
bftWeight
: The BFT weight is the weight attributed to the pre-votes and pre-commits cast by a validator, and therefore determines to what extent the validator contributes to finalizing blocks.
The length of the sidechainValidators is equal to the amount of active validators on the sidechain (101 by default).
|
1.4.4. sidechainCertificateThreshold
An integer defining the minimum BFT weight threshold required for the first sidechain certificate to be valid.
A valid value for the threshold can be obtained by invoking the consensus_getBFTParameters endpoint. |
Minimum and maximum values for the certificate threshold are calculated using the Aggregated BFT weight.
const { apiClient } = require('@klayr/client');
(async () => {
// Update the path to point to your sidechain client data folder
const sidechainClient = await apiClient.createIPCClient('~/.klayr/hello_client');
const sidechainNodeInfo = await sidechainClient.invoke('system_getNodeInfo');
// Get active validators from sidechain
const bftParams = await sidechainClient.invoke('consensus_getBFTParameters', { height: sidechainNodeInfo.height });
// Calculate the aggregated BFT weight
let aggregateBFTWeight = BigInt(0);
for (const validator of bftParams.validators) {
aggregateBFTWeight += BigInt(validator.bftWeight);
}
console.log("certificateThreshold:");
console.log("min:");
console.log(aggregateBFTWeight/BigInt(3) + BigInt(1));
console.log("max:");
console.log(aggregateBFTWeight);
process.exit(0);
})();
Aggregated BFT weight
The aggregated BFT weight is the sum of BFT weights of all active validators at a specific block height.
The aggregated BFT weight is used to calculate the minimum and maximum values of :
Minimum threshold:
min = floor( 1/3 * Aggregated BFT weight ) + 1
Maximum threshold:
max = Aggregated BFT weight
1.5. Send the transaction to the mainchain
Send the registerSidechain
transaction to a node that is connected to the mainchain.
// Post the transaction to a mainchain node
const result = await mainchainClient.invoke<{
transactionId: string;
}>('txpool_postTransaction', {
transaction: tx.getBytes().toString('hex'),
});
console.log(
`Sent sidechain registration transaction on mainchain node ${MAINCHAIN_ARRAY[1]}. Result from transaction pool is: `,
result,
);
The node will respond with the transaction ID if it accepts the transaction.
2. How to verify the sidechain registration
2.1. Check the sidechain account
Once the Sidechain Registration command is processed, the sidechain account status
is set to registered
.
To verify that the account was created successfully, request the interoperability_getChainData
endpoint from a mainchain node.
Parameters:
-
chainID
: The chain ID of the registered sidechain.
klayr-core endpoint:invoke interoperability_getChainData '{"chainID": "04000001"}'
curl --location --request POST 'http://localhost:7887/rpc' \
--header 'Content-Type: application/json' \
--data-raw '{
"jsonrpc": "2.0",
"id": "1",
"method": "interoperability_getChainData",
"params": {
"chainID": "04000001"
}
}'
This will return the respective sidechain account stored in the Chain substore of the mainchain.
{
"id": "1",
"jsonrpc": "2.0",
"result": {
"lastCertificate": {
"height": 0,
"timestamp": 0,
"stateRoot": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"validatorsHash": "58fa1be3fca7aef9952a7640397124229837079b14a144907a7e3373685daceb"
},
"name": "pos-sidechain-example-one",
"status": 0
}
}
2.2. Check the sidechain channel
To verify that the channel to the sidechain was created successfully, request the interoperability_getChannelData
endpoint from a mainchain node.
Parameters:
-
chainID
: The chain ID of the registered sidechain.
klayr-core endpoint:invoke interoperability_getChannelData '{"chainID": "04000001"}'
curl --location --request POST 'http://localhost:7887/rpc' \
--header 'Content-Type: application/json' \
--data-raw '{
"jsonrpc": "2.0",
"id": "1",
"method": "interoperability_getChannelData",
"params": {
"chainID": "04000001"
}
}'
This will return the respective sidechain account stored in the Channel substore of the mainchain.
{
"id": "1",
"jsonrpc": "2.0",
"result": {
"messageFeeTokenID": "0000000000000000",
"outbox": {
"appendPath": [
"16dfaad8458dd4ae56ba2787593c2c5a55b14ba734c0431f177212226cf8328b"
],
"root": "16dfaad8458dd4ae56ba2787593c2c5a55b14ba734c0431f177212226cf8328b",
"size": 1
},
"inbox": {
"appendPath": [],
"root": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"size": 0
},
"partnerChainOutboxRoot": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
}
2.3. Check the sidechain validators
To verify that the sidechain validators list was created successfully, request the interoperability_getChainValidators
endpoint from a mainchain node.
Parameters:
-
chainID
: The chain ID of the registered sidechain.
klayr-core endpoint:invoke interoperability_getChainValidators '{"chainID": "04000001"}'
curl --location --request POST 'http://localhost:7887/rpc' \
--header 'Content-Type: application/json' \
--data-raw '{
"jsonrpc": "2.0",
"id": "1",
"method": "interoperability_getChainValidators",
"params": {
"chainID": "04000001"
}
}'
This will return the respective sidechain account stored in the Chain Validators substore of the mainchain.
{
"id": "1",
"jsonrpc": "2.0",
"result": {
"activeValidators": [
{
"blsKey": "8012798d2ac6b93df3bfa931192d9b2d496c3c947958a7408232e08872895557c06c1b94f5cc2555e28addbaadf3e0bd",
"bftWeight": "1"
},
// ...
],
"certificateThreshold": "65"
}
}
3. How to register the mainchain on the sidechain
The mainchain is registered on a sidechain by posting a Register Mainchain transaction. A "Register Mainchain" transaction can be sent by any user account in the sidechain that has adequate funds to pay the required fee.
The process of registering the mainchain on a sidechain is explained by following the existing example mainchain_registration.ts, which can be found in the Klayr SDK repository. |
|
To create a Register Mainchain transaction, the following information is required:
3.1. Preparing the Register Mainchain parameters
The parameters for the Register Mainchain transaction are created in an analog way as the Sidechain Registration parameters have been created in the previous step.
import {
codec,
cryptography,
apiClient,
Transaction,
registrationSignatureMessageSchema,
mainchainRegParams as mainchainRegParamsSchema,
MESSAGE_TAG_CHAIN_REG,
} from 'klayr-sdk';
/**
* Registers the mainchain to a specific sidechain node.
*
* @example
* ```js
* // Update path to point to the dev-validators.json file of the sidechain which shall be registered on the mainchain
import { keys as sidechainDevValidators } from '../default/dev-validators.json';
* (async () => {
* await registerMainchain("klayr-core","my-klayr-app",sidechainDevValidators);
*})();
* ```
*
* @param mc mainchain alias of the mainchain to be registered.
* @param sc sidechain alias of the sidechain, where the mainchain shall be registered.
* @param sidechainDevValidators the `key` property of the `dev-validators.json` file.
* Includes all keys of the sidechain validators to create the aggregated signature.
*/
export const registerMainchain = async (mc: string, sc: string, sidechainDevValidators: any[]) => {
const { bls, address } = cryptography;
// Connect to the mainchain node
const mainchainClient = await apiClient.createIPCClient(`~/.klayr/${mc}`);
// Connect to the sidechain node
const sidechainClient = await apiClient.createIPCClient(`~/.klayr/${sc}`);
// Get node info from sidechain and mainchain
const mainchainNodeInfo = await mainchainClient.invoke('system_getNodeInfo');
const sidechainNodeInfo = await sidechainClient.invoke('system_getNodeInfo');
// Get active validators from mainchain
const {
validators: mainchainActiveValidators,
certificateThreshold: mainchainCertificateThreshold,
} = await mainchainClient.invoke('consensus_getBFTParameters', {
height: mainchainNodeInfo.height,
});
// Sort validator list lexicographically after their BLS key
const paramsJSON = {
ownChainID: sidechainNodeInfo.chainID,
ownName: sc.replace(/-/g, '_'),
mainchainValidators: (mainchainActiveValidators as { blsKey: string; bftWeight: string }[])
.map(v => ({ blsKey: v.blsKey, bftWeight: v.bftWeight }))
.sort((a, b) => Buffer.from(a.blsKey, 'hex').compare(Buffer.from(b.blsKey, 'hex'))),
mainchainCertificateThreshold,
};
// Define parameters for the mainchain registration
const params = {
ownChainID: Buffer.from(paramsJSON.ownChainID as string, 'hex'),
ownName: paramsJSON.ownName,
mainchainValidators: paramsJSON.mainchainValidators.map(v => ({
blsKey: Buffer.from(v.blsKey, 'hex'),
bftWeight: BigInt(v.bftWeight),
})),
mainchainCertificateThreshold: paramsJSON.mainchainCertificateThreshold,
};
// Encode parameters
const message = codec.encode(registrationSignatureMessageSchema, params);
}
The next step is to collect and aggregate the corresponding signatures from the sidechain validators, see Preparing the aggregated signature.
3.1.2. ownName
Sets the name of the sidechain in its own state according to the name given in the mainchain.
Should be identical to the Name.
3.1.3. mainchainValidators
Defines the set of mainchain validators expected to sign the first certificate from the mainchain. Each item contains the following properties:
-
blsKey
: The public BLS key of the validator. -
bftWeight
: The BFT weight of the validator.
To retrieve the BFT parameters of all mainchain validators, invoke the consensus_getBFTParameters endpoint on the mainchain node. |
3.1.4. mainchainCertificateThreshold
mainchainCertificateThreshold
is an integer that defines the minimum BFT weight threshold required for the first mainchain certificate to be valid.
Minimum and maximum values for the certificate threshold are calculated with the Aggregated BFT weight.
3.2. Preparing the aggregated signature
To create a valid signature, enough sidechain validators need to individually sign the mainchain registration message, so that the total weight is equal or greater to the sidechainCertificateThreshold.
By signing the mainchain registration message, they verify the correctness of the following values: ownChainID
, ownChainName
, mainchainCertificateThreshold
, and mainchainValidators
.
All individual signatures are then aggregated into one signature
and the corresponding aggregationBits
, which are appended to the Register Mainchain parameters.
The individual signing of the mainchain registration by the sidechain validators is executed off-chain, similar to the process of creating multi-signature transactions.
3.2.1. Collecting the signatures of the sidechain validators
The process of signing the registration parameters needs to be coordinated off-chain by the sidechain validators.
The most important parts of the script are the following:
// Get active validators from sidechain
const { validators: sidechainActiveValidators } = await sidechainClient.invoke(
'consensus_getBFTParameters',
{ height: sidechainNodeInfo.height },
);
// Add validator private keys to the sidechain validator list
const activeValidatorsBKLYeys: { blsPublicKey: Buffer; blsPrivateKey: Buffer }[] = [];
for (const activeValidator of sidechainActiveValidators as {
blsKey: string;
bftWeight: string;
}[]) {
const sidechainDevValidator = sidechainDevValidators.find(
devValidator => devValidator.plain.blsKey === activeValidator.blsKey,
);
if (sidechainDevValidator) {
activeValidatorsBKLYeys.push({
blsPublicKey: Buffer.from(activeValidator.blsKey, 'hex'),
blsPrivateKey: Buffer.from(sidechainDevValidator.plain.blsPrivateKey, 'hex'),
});
}
}
console.log('Total activeValidatorsBKLYeys:', activeValidatorsBKLYeys.length);
// Sort active validators from the sidechain lexicographically after their BLS public key
activeValidatorsBKLYeys.sort((a, b) => a.blsPublicKey.compare(b.blsPublicKey));
const sidechainValidatorsSignatures: { publicKey: Buffer; signature: Buffer }[] = [];
// Sign parameters with each active sidechain validator
for (const validator of activeValidatorsBKLYeys) {
const signature = bls.signData(
MESSAGE_TAG_CHAIN_REG,
params.ownChainID,
message,
validator.blsPrivateKey,
);
sidechainValidatorsSignatures.push({ publicKey: validator.blsPublicKey, signature });
}
const publicKeysList = activeValidatorsBKLYeys.map(v => v.blsPublicKey);
console.log('Total active sidechain validators:', sidechainValidatorsSignatures.length);
The validator signatures need to be collected by the person intending to send the Register Mainchain transaction.
Once the aggregated BFT weight of the validators who signed is equal to or above the sidechainCertificateThreshold, enough validators have signed the registration parameters.
3.2.2. Aggregating the validator signatures
Once enough validators add their signatures, the list of signatures is aggregated into one single signature, which is then appended to the registration params that we created in step Preparing the Register Mainchain parameters.
- signature
-
The
signature
property is an aggregate signature of the sidechain validators. It ensures that the sidechain validators agree on registering the mainchain in the sidechain. - aggregationBits
-
The
aggregationBits
property is a bit vector used to validate the aggregate signature.
// Create an aggregated signature
const { aggregationBits, signature } = bls.createAggSig(
publicKeysList,
sidechainValidatorsSignatures,
);
// Get public key and nonce of the sender account
const relayerKeyInfo = sidechainDevValidators[0];
const { nonce } = await sidechainClient.invoke<{ nonce: string }>('auth_getAuthAccount', {
address: address.getKlayr32AddressFromPublicKey(Buffer.from(relayerKeyInfo.publicKey, 'hex')),
});
// Add aggregated signature to the parameters of the mainchain registration
const mainchainRegParams = {
...paramsJSON,
signature: signature.toString('hex'),
aggregationBits: aggregationBits.toString('hex'),
};
// Create registerMainchain transaction
const tx = new Transaction({
module: 'interoperability',
command: 'registerMainchain',
fee: BigInt(2000000000),
params: codec.encodeJSON(mainchainRegParamsSchema, mainchainRegParams),
nonce: BigInt(nonce),
senderPublicKey: Buffer.from(relayerKeyInfo.publicKey, 'hex'),
signatures: [],
});
// Sign the transaction
tx.sign(
Buffer.from(sidechainNodeInfo.chainID as string, 'hex'),
Buffer.from(relayerKeyInfo.privateKey, 'hex'),
);
3.3. Posting a Register Mainchain transaction
Send the registerMainchain
transaction to a node that is connected to the mainchain.
// Post the transaction to a sidechain node
const result = await sidechainClient.invoke<{
transactionId: string;
}>('txpool_postTransaction', {
transaction: tx.getBytes().toString('hex'),
});
console.log('Sent mainchain registration transaction. Result from transaction pool is: ', result);
The node will respond with the transaction ID if it accepts the transaction.
4. How to verify the mainchain registration
4.1. Check the mainchain account
Once the "Register Mainchain" command is processed, the mainchain account is initialized and its status
is set to registered
.
To verify that the account was created successfully, request the interoperability_getChainData
endpoint from a sidechain node.
Parameters:
-
chainID
: The chain ID of the registered mainchain.
If you maintain your own instance of a sidechain node, it is possible to invoke endpoints directly via the node CLI:
./bin/run endpoint:invoke interoperability_getChainData '{"chainID": "04000000"}'
Replace localhost:7887
with the IP and port to a node that is connected to the sidechain.
curl --location --request POST 'http://localhost:7887/rpc' \
--header 'Content-Type: application/json' \
--data-raw '{
"jsonrpc": "2.0",
"id": "1",
"method": "interoperability_getChainData",
"params": {
"chainID": "04000000"
}
}'
This should return the mainchain account stored in the Chain substore of the sidechain.
{
"lastCertificate": {
"height": 0,
"timestamp": 0,
"stateRoot": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"validatorsHash": "36d3f20ea724a9708ead8b00e52c68d9188f1655e057bb523b19db35271d9073"
},
"name": "klayr_mainchain",
"status": 0
}
4.2. Check the mainchain channel
If you maintain your own instance of a sidechain node, it is possible to invoke endpoints directly via the node CLI:
./bin/run endpoint:invoke interoperability_getChannelData '{"chainID": "04000000"}' --pretty
Replace localhost:7887
with the IP and port to a node that is connected to the sidechain.
curl --location --request POST 'http://localhost:7887/rpc' \
--header 'Content-Type: application/json' \
--data-raw '{
"jsonrpc": "2.0",
"id": "1",
"method": "interoperability_getChannelData",
"params": {
"chainID": "04000000"
}
}'
This should return the mainchain channel data stored in the Channel substore of the sidechain.
{
"messageFeeTokenID": "0400000000000000",
"outbox": {
"appendPath": [
"4d48ae83b249d1b409d2d7f1ae18792e7aeb15f647bd8a607c6639723a76a487"
],
"root": "4d48ae83b249d1b409d2d7f1ae18792e7aeb15f647bd8a607c6639723a76a487",
"size": 1
},
"inbox": {
"appendPath": [],
"root": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"size": 0
},
"partnerChainOutboxRoot": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"minReturnFeePerByte": "1000"
}
4.3. Check the mainchain validators
If you maintain your own instance of a sidechain node, it is possible to invoke endpoints directly via the node CLI:
./bin/run endpoint:invoke interoperability_getChainValidators '{"chainID": "04000000"}' --pretty
Replace localhost:7887
with the IP and port to a node that is connected to the sidechain.
curl --location --request POST 'http://localhost:7887/rpc' \
--header 'Content-Type: application/json' \
--data-raw '{
"jsonrpc": "2.0",
"id": "1",
"method": "interoperability_getChainValidators",
"params": {
"chainID": "04000000"
}
}'
This should return the mainchain validators data stored in the Chain Validators substore of the sidechain.
Example response
{
"activeValidators": [
{
"blsKey": "817cdb74e2136473f579ec7c5ff341e98a36a7ebfd1baadded8fbb315fcda6e77fd73f2132be39a018fcc3ae10b4a0e6",
"bftWeight": "1"
},
{
"blsKey": "830b156d85886dc0ea00cf76643030b82b4b80660a99e6b38386cd3774b8f1252f243a5ae2b38df915b1f71d5f5dfda3",
"bftWeight": "1"
},
{
"blsKey": "832d2cdd741b0df3f80311ed90deec394c13ed05da115d3c1fc135c96556911c6b00592451de0ab37296417559852080",
"bftWeight": "1"
},
{
"blsKey": "8394e9cfa7c81ff453ea93e50aca110dc1bb823b2681d8b7900787abd207a6268b34ff7d95e0f60530e05e45607a924e",
"bftWeight": "1"
},
{
"blsKey": "851d06def623bafccf24911d7baac97dbbdfdd3d9ebef92a912bbb335223e9f73e111ed181fb46bd5fd31044820246de",
"bftWeight": "1"
},
{
"blsKey": "85657021983a5019c8bd7461bbe04b830291d1094e26534131770b14dd3a88768a6ee57a6324bb8fa726f7c24d3f4507",
"bftWeight": "1"
},
{
"blsKey": "859a15723bc6fbef835069e843cc6f8a8221ffded4e9d2455b5520f63538775a5de66501e514a1c37d85df1c5092c418",
"bftWeight": "1"
},
{
"blsKey": "86896756d01d6c5b03f23647d7bce1cdab601ae8adb20d86614d13aaaa0e40a6bb8e524a4637d47cfc4bb1cece538862",
"bftWeight": "1"
},
{
"blsKey": "877a80b466c2090b917726b305c6f3861aea2c7e8ae9d9653fe5b871b2873d8a045643e202c007c927f9c90d65946d68",
"bftWeight": "1"
},
{
"blsKey": "87e74f2352400144080e849248c668e16da593c1d661a11ee20d5c31f149ec546729f93950b673b66dea6a33c9260d78",
"bftWeight": "1"
},
{
"blsKey": "8824e9c41ead5ee18e5fe71709f100351a9724320b7c444b0f14fa5e8f877470872edb58533442c933ee97fae8dba785",
"bftWeight": "1"
},
{
"blsKey": "897597d4dc645d4aad51246ed1d9d6b0c8f81bd1809fa9106ce367a791f0b98151bfa4b49b1251d6b307240ba1dad106",
"bftWeight": "1"
},
{
"blsKey": "89ffe19409b06f1e7854a9a5512b0d045a4ec696dc74b7d7d33117c9a8e1cfd68e185b09ae276d1a1bc3942395eb4dce",
"bftWeight": "1"
},
{
"blsKey": "8ad40d292e82ffaf9f75517b03df7b5693adba8b0f1654791de0a92a3ab0bfb26bf7d90b4cfed334d5480c329ad81614",
"bftWeight": "1"
},
{
"blsKey": "8adb937e0050d9fcf66321e2b55bc89f76592f359a07559e76c2e0cebc4c1611feb852d9f9172b71dc970a4766f9c935",
"bftWeight": "1"
},
{
"blsKey": "8aeba1cc038ad2cf1ba6ae1479f293f1e3c074369c3afe623e6921ac4cd6c959647ca85fe197228c38dda1df18812d32",
"bftWeight": "1"
},
{
"blsKey": "8b517672ecb3b38a766baedd0cedd7a0f2f2a568c067ac7b75087452c7f879c68951661e82cc2e2d1224f57b24fd0874",
"bftWeight": "1"
},
{
"blsKey": "8c43fcdeed68cefc70e02af16b5d18938f12d6a994d0ceb1519a0bfdba07ebaa3b92cd64204f411f501defaa5bce0a98",
"bftWeight": "1"
},
{
"blsKey": "8c6a9cc624212b23d5e4afc935db990b99b2f6a46390b9819ec4c34e4da73ceb49ce247eadacddb39a874b5404ca8982",
"bftWeight": "1"
},
{
"blsKey": "8d4298f04a38dd65f2b8ba59276d8da350aaf08f2d5f8101f1d23001394f74389a8c7339b1ba4a9167ca663de4b36c28",
"bftWeight": "1"
},
{
"blsKey": "8e6b900d97becb167b6182f815a4c50797538d221b5fef9d0eaeba0771560f8ec5b78e1bd9f466a823c02cead6b00006",
"bftWeight": "1"
},
{
"blsKey": "8ed44b95d761d3534ea609c7a015f465f9829a0d68c5efb04d7a2043b6be5ebc175c52277b4bb064c727018164b3e41e",
"bftWeight": "1"
},
{
"blsKey": "8edcba4c5f1c12f2d4834146cc3685d63d0ddceac3eae8aa55bfe72fc3adfae9aad0fffdc244a6d84125929add786206",
"bftWeight": "1"
},
{
"blsKey": "8f1861579e9344ed88748206a2fce1a040f974c40733fafd2a98bef813250c602bc2d3e4dd97836c638f1e8522d9304f",
"bftWeight": "1"
},
{
"blsKey": "8f3d6b429550ba9e9cab338cccb803b8553f1ab8c393089a772f8d40580683bbdef82b6972f8cafb1a6e3703f186f731",
"bftWeight": "1"
},
{
"blsKey": "8f4c1f0421fc4e84a9aaff71b15ea5387f2c2023de5764018c6260ac756ea4bcc1fa1e1b2adca51c41f33788f977202e",
"bftWeight": "1"
},
{
"blsKey": "8f558a92ea2ddb97aaf1950b2ad8cacf3bbb6df0ba6f3bb2af7e7dd2bb8c8f695af1d4cf89148f36bc7cc1b6c3c9b3e6",
"bftWeight": "1"
},
{
"blsKey": "91de8f0b3e01dcf01973a8a7ba0c787fad98e9e485b71b78ea5f4d6d684f3f8f74f25120818729bdcf19dec78beac82b",
"bftWeight": "1"
},
{
"blsKey": "921fab12f32b83338acd846c4f2e22e78d5657a9ad6e26a1b953235b837c97c1994b410fb98ed9d60043046bc49cf04b",
"bftWeight": "1"
},
{
"blsKey": "945159215901b8094983b7c457d47350031a6894ca2168bd9902de0248e11f7a7b3849820e7bbf8d12c9111df5dd0c59",
"bftWeight": "1"
},
{
"blsKey": "946f53073e217c65c31f135950827706bffc53a1714ef6476751f88e499cb2405fdbc68bffb715b94774a37c6c49b695",
"bftWeight": "1"
},
{
"blsKey": "94db4e95e0bdf2a2f2dad6fe1758d598cf39e5f5013d30812d89983560fd630e730d7f8443f8f8f7dedb2817d7199dd3",
"bftWeight": "1"
},
{
"blsKey": "94eab1c0dd071eb08efffdfc9fa4a1a084f18b155c265cea14b897f9ef8da838f061d072aec8b1fddf64d982db5383f2",
"bftWeight": "1"
},
{
"blsKey": "94f3abffbb40e97d2b024c614377a49bc02ccb96f5c39d5f5240cabed3abf71a79dfd1415819c945bd7bfbd6ce974e29",
"bftWeight": "1"
},
{
"blsKey": "962ad95f9eb8de66f40ae8650e4a1b9715db8b93082f94a999c5513b59b34ae765bbdf52525348eb2c3bfae9b74be5d0",
"bftWeight": "1"
},
{
"blsKey": "9649a2666c4397e558ac70522551fa2282bc12cb9f0efaaaeb723916050115eb6a06944047e27b2765c48a8fdc54723b",
"bftWeight": "1"
},
{
"blsKey": "964bb701671ac2ff8c74479ac55edd73425552873f62b0f0c7e806d31b97b04b3c3fce13746b9758c577fabc35569484",
"bftWeight": "1"
},
{
"blsKey": "974e9ad5b6dc8589d13f6ea588e13e2f911d274bdca25a0eaf95e78070ada24ebf055e7e96f4bc5d54d1e1ecbb88891b",
"bftWeight": "1"
},
{
"blsKey": "97ec41664e973d6c47a6eb42a71cdab2b32cab143ebbff4987ca1f21b70d2a4a31865fa63e498172ddfcdc66c375df3c",
"bftWeight": "1"
},
{
"blsKey": "981ed9e0ecefede2baabe2190da98bcda364e9cd2cdc84294104f76baa96357b208b8a46b35815ff70a2782321c7a997",
"bftWeight": "1"
},
{
"blsKey": "988b6193525235d5c772e65b777d699b5a6a2c78b9588aba6ba791cc90f8ad64d811b60db840d6335af8c1b56e64a6a8",
"bftWeight": "1"
},
{
"blsKey": "98c1650f3954150884b93926f0b9bb2895a86b7f53548b55c54a8baaf6298011dda1d4c59b0de58ff0b7049c84d4bb91",
"bftWeight": "1"
},
{
"blsKey": "a07a39359e00f2dc70586643b0192b72bf5cac12664c9b3e86196b9ff8ab42d2de0c99feaabe150a2f158bc5655af28a",
"bftWeight": "1"
},
{
"blsKey": "a13d891ddf147982e0c9a5f131e06e9839fbfbc1c2e45a0e36047420cf096c7befdb2368999f9127ae1c1351cd006360",
"bftWeight": "1"
},
{
"blsKey": "a227caf7eaccffa90c894e8338f1bf792936d3e70392970f87246c88ba8e37ac48242c6b9d94f0f34f45cca988d723a4",
"bftWeight": "1"
},
{
"blsKey": "a377f60b451ee1824e4bcef98ee59ed2d9e9e79fda80e9f9cb4d5142ec6e34c3cb2ba0b58fa9cda79ccfecf9be1944e1",
"bftWeight": "1"
},
{
"blsKey": "a3d818681e97be169c51414f692f743ffeb5fbf60478710a8dd4ab883044d2bf8b318bbf931316a30755061338cbf857",
"bftWeight": "1"
},
{
"blsKey": "a4ae34b8c356805c5a71cc7b9344eb190004733dc6ec97f2cb19249595ac2ccfd11a25ddbf455be0b17573e306c71301",
"bftWeight": "1"
},
{
"blsKey": "a4f8a7ab03605906f89a6b3e2a6091c53feae76f3a9e48593732fc027babbbfd6cf6fa5c98251f8453cb6b57fda99ced",
"bftWeight": "1"
},
{
"blsKey": "a5966288b2fcc72df1a9ec5434dd3a4120bfad14b96939591ddc3312f5d4645ac9715fc9f261ea969a96122c94e71c8e",
"bftWeight": "1"
},
{
"blsKey": "a5c327e70f2d173b3bbb1dc69e57e03bd4b32f91a8874309656cf9a364e0ab6f1b2fe76e3690981c53e32223530f9009",
"bftWeight": "1"
},
{
"blsKey": "a67a75058d23b7b9ef52c7de10f1faddb7ffdd53dd9daa0d77455bd6d202b6e5056c37b5fd09a336baaaa3e31fd73875",
"bftWeight": "1"
},
{
"blsKey": "a6f7e55f71ae7fdcaa2bcbda040c091e76862d4cadcea2d483c0af7af337e2bfd53d120ffa1c0b445f04ac3a74a1f369",
"bftWeight": "1"
},
{
"blsKey": "a7252bc36c9fdaf4310c65727558545882683a08d4c46e3f278de2ecc3bd76e66e9aa2917f8707d11893a0911b39f3c0",
"bftWeight": "1"
},
{
"blsKey": "a74d911e73ccf66c0d60b622fe507608aeb5af06b5a4258a4d1e83168688217eaf05ef350342c186c593cdc834fb1593",
"bftWeight": "1"
},
{
"blsKey": "abbc6a25a487fb9de634713c7c5ab755dacb33a39c84d711c7b2934ddc529c8f953044fe85e97cbebf9ab517ce0c36d6",
"bftWeight": "1"
},
{
"blsKey": "acc6b671c8e4843ac5c7db7625a5f4c4d6823dd93bf06e2eb16c7263543a58cee5243715efb41b982a02f0bdcca5e412",
"bftWeight": "1"
},
{
"blsKey": "af3f9f14bf7db60f3283f21f20516d1d82caece0ef1d4ee88ab6f8f0be46af594105114ae2eff847ace826b1a163cadf",
"bftWeight": "1"
},
{
"blsKey": "b0508dc694713f5fcd4a60d134d8254d18d70559981864f594f74150aaf49724e32ee6bad8933107609d807d9ea7942b",
"bftWeight": "1"
},
{
"blsKey": "b092e4bb3eb9c291d94cfac62f44f889b0727981e81115d69573a97766845e1b17f553bdd9208462605cfda44c058411",
"bftWeight": "1"
},
{
"blsKey": "b0dd52e91344ee8d961b01fda84f6bfd2c71bde2317e0bb01a6821b9dec36e10cfa01153fa5f427f598c04ab50681518",
"bftWeight": "1"
},
{
"blsKey": "b178489edeb8b41d987b5a898d9dc084e23f6793f6318d5aa2073e021830d74f1b49fb663cebcb2d549e354f0c6fe320",
"bftWeight": "1"
},
{
"blsKey": "b2be3c23c2ee5a80ee46ee6685cc759baec4d6e5fcb553ed09160ca0c2dc4e6e0ebc361ab48faec07acb08f1f9f43e8a",
"bftWeight": "1"
},
{
"blsKey": "b31f1cdafe5ae69ea79cb01e38b377d489f7ea6374ad92a6eeb1c599dc791f91711901c53dd19f584873f2eb95a4a0c0",
"bftWeight": "1"
},
{
"blsKey": "b346b18b84af84321cd00979443560b8d50615940fa6430f3f187db5971324761aaa50fb08b86480b1b175f696125ad6",
"bftWeight": "1"
},
{
"blsKey": "b462fb1caa2f877f2e5a23742a2e4aa52b285fcf8413191232b4c78f3a1f545d8531b103867cdff0e7a65ceb05ddbdae",
"bftWeight": "1"
},
{
"blsKey": "b47b918f6488ec7e77233e01fcf469ac4c3322add127fe5bf4598a8c8e8d2dfe27fa94b1bd46201883a0e578c32454d4",
"bftWeight": "1"
},
{
"blsKey": "b48052c2a26c2853e048b0d3781b4401e57e4628e2557ac4845d7d980ef03f997c4c71687370c63c044aa51244af1875",
"bftWeight": "1"
},
{
"blsKey": "b48b57d9ae6b0658430050bc0336005eba5a4f318589d8cb2641a6e813c6ee4e6a3158a503b18e06702591337c4adfc7",
"bftWeight": "1"
},
{
"blsKey": "b57ded217776a9bbd06939f6465300e922284e12e3bf584565fcc0526297a4234fc1f47829f9242491bb42c20b46f9e3",
"bftWeight": "1"
},
{
"blsKey": "b59d42bc9c72ec01ca4cb668953d25b150f936b39556fea44b59ab58a7696dda1ee48fa71947f8b6937b642d844cf7a2",
"bftWeight": "1"
},
{
"blsKey": "b5d6cc8adeff2f0f40d15dd9c8854810951915a2c5b02b562385e4f67ee979c17c4f1888d8c878a4346f42b963a5cfc6",
"bftWeight": "1"
},
{
"blsKey": "b6056ba058859f1f0e8985550ae76214301802a3149727e233edf7a63d9dac9317f116adc8fde387adc207a81cd57d9a",
"bftWeight": "1"
},
{
"blsKey": "b6fed2994e0796ddcad19dec93bcaabf2bdb458a57847e23cff3e5bd70183cd89946bc4f6289494da369cc3e64e7726d",
"bftWeight": "1"
},
{
"blsKey": "b79172fc4333d255ee3a1884b78ffde5901638e60ed674ccaadd57921448c4dcbc2cd5d9faf1cf71c5a48d27b89c482b",
"bftWeight": "1"
},
{
"blsKey": "b7ca4bc931e95a39d9b04d4f03ffd50c2bf1de9261901e5b3e9783b953b3171f877733d8db2189317ed2bf865199fcf6",
"bftWeight": "1"
},
{
"blsKey": "b809509d27fc57d0bcb96eec7aa0ae0a5fc44f31bf22c288ccd7745969148e6a559ee374c183154b6f3fa0a5269e1206",
"bftWeight": "1"
},
{
"blsKey": "b87c1c8c3f07f5bcf067eea76ca9328da862bbfffd9af24ec63271307b3e2490aec28d8c8ffb00ed6873357a3859f22c",
"bftWeight": "1"
},
{
"blsKey": "b8f904f1e724e1dfc6075fa763eb60987a2e51e9863a45cf7691deaeb698e66769919d19d3be85daed169bd3266ffc20",
"bftWeight": "1"
},
{
"blsKey": "b9a15fb236e20f5857e062579e8deb7384153e4b0655014964a5501f25fc43d7268f97c45343b71bf7ac3423c45b401b",
"bftWeight": "1"
},
{
"blsKey": "b9b285aa8e0f5811f19250ab6893d7a9a502d1b0430302487a226eb905e2e42413c15945f2cda5cc472298856b9f9fa8",
"bftWeight": "1"
}
],
"certificateThreshold": "65"
}
5. Relayer nodes and app-registry
Relayer nodes are required to send cross-chain messages from the sidechain to the mainchain and vice-versa.
To facilitate cross-chain communication, at least one relayer node is required on the mainchain and the sidechain.
5.1. Setup relayer nodes on mainchain and sidechain
Please check the guide Setting up a relayer node for a step-by-step explanation of how to turn a node into a relayer.
5.2. Register off-chain data in the app-registry
As a last step, it is necessary to register the sidechain related off-chain metadata to the app registry.
The data can be provided as a pull request to the repository, which adds a new folder under the respective network with the following structure:
. ├── app-registry/betanet/<APP>/ │ └── app.json │ ├── nativetokens.json │ └── images/ │ │ ├── application │ │ │ ├── app.png │ │ │ └── app.svg │ │ └── tokens │ │ │ ├── token.png │ │ │ └── token.svg tsconfig.json
where <APP>
is the name of the sidechain app.
Please refer to the README.md for all important details about the app registration process, like the required image formats and sizes. |
app.json
-
Off-chain metadata of a sidechain, to be used by client applications such as Klayr Desktop and other explorers.
Example: app.json
{
"title": "Klayr - Betanet",
"description": "Metadata configuration for the Klayr blockchain (mainchain) in betanet",
"chainName": "Klayr",
"chainID": "02000000",
"networkType": "betanet",
"genesisURL": "https://downloads.klayr.xyz/klayr/betanet/genesis_block.json.tar.gz",
"projectPage": "https://klayr.xyz",
"logo": {
"png": "https://raw.githubusercontent.com/KlayrHQ/app-registry/main/betanet/Klayr/images/application/klayr.png",
"svg": "https://raw.githubusercontent.com/KlayrHQ/app-registry/main/betanet/Klayr/images/application/klayr.svg"
},
"backgroundColor": "#f7f9fb",
"serviceURLs": [
{
"http": "https://betanet-service.klayr.xyz",
"ws": "wss://betanet-service.klayr.xyz"
}
],
"explorers": [
{
"url": "https://explorer.klayr.xyz",
"txnPage": "https://explorer.klayr.xyz/transactions"
}
],
"appNodes": [
{
"url": "https://betanet.klayr.xyz",
"maintainer": "Lightcurve GmbH"
},
{
"url": "wss://betanet.klayr.xyz",
"maintainer": "Lightcurve GmbH"
}
]
}
nativetokens.json
-
Off-chain metadata about the native tokens of a sidechain.
Example: nativetokens.json
{
"title": "Klayr - Betanet - Native tokens",
"tokens": [
{
"tokenID": "0200000000000000",
"tokenName": "Klayr",
"description": "Default token for the entire Klayr ecosystem",
"denomUnits": [
{
"denom": "beddows",
"decimals": 0,
"aliases": [
"Beddows"
]
},
{
"denom": "kly",
"decimals": 8,
"aliases": [
"Klayr"
]
}
],
"baseDenom": "beddows",
"displayDenom": "kly",
"symbol": "KLY",
"logo": {
"png": "https://raw.githubusercontent.com/KlayrHQ/app-registry/main/betanet/Klayr/images/tokens/klayr.png",
"svg": "https://raw.githubusercontent.com/KlayrHQ/app-registry/main/betanet/Klayr/images/tokens/klayr.svg"
}
}
]
}
images/application/
-
Icons to be used for the sidechain application.
images/tokens/
-
Icons to be used for the native tokens of the sidechain.
5.3. Move KLY to sidechain
This step is not necessary, if the default token for the CCM fees (KLY) is changed to a native sidechain token. |
To be able to pay the CCM transaction fees, it is necessary to move a sufficient amount of KLY tokens from the mainchain to the sidechain via a cross-chain transfer transaction.
Sending CCMs from the mainchain to the sidechain does not Activate the sidechain, yet. Only a CCM from the sidechain to the mainchain will enable the liveness condition. |
Create the following transaction with your mainchain node:
$ klayr-core transaction:create token transferCrossChain 10000000
? Please enter passphrase: [hidden]
? Please enter: tokenID: 0100000000000000
? Please enter: amount: 1000000000
? Please enter: receivingChainID: 01000001
? Please enter: recipientAddress: kly24cd35u4jdq8szo3pnsqe5dsxwrnazyqqqg5eu
? Please enter: data: cross-chain transfer
? Please enter: messageFee: 1
? Please enter: messageFeeTokenID: 0100000000000000
-
tokenID
: The token ID of the KLY token. -
receivingChainID
: Enter the chain ID of the respective sidechain where you intend to transfer your KLY tokens. -
recipientAddress
: Enter the recipient address for the account on the sidechain. -
messageFee
: Enter the fee for the CCM. -
messageFeeTokenID
: Enter the token ID of the token that should be used to pay the fee. Enter here the tokenID of the KLY token.
To verify that the cross-chain transfer was executed successfully, request the token_getBalance endpoint with the recipient address as a parameter.
Alternatively, the following script can be used to perform the cross-chain transfer: transfer_kly_sidechain_one.ts To make it work for your sidechain, please adjust the script as explained in the code comments of the script. The script is executed like so:
|
6. Activate the sidechain
After registering the sidechain and setting up the required relayer nodes, the sidechain is ready to be activated.
To activate the sidechain, send a first CCU from sidechain to mainchain.
|
Once the first CCU is sent successfully from the sidechain to the mainchain, the sidechain is activated and is interoperable with the mainchain and other sidechains within the Klayr ecosystem.