Broadcast a transaction

This guide explains how to create a transaction object of a specific transaction type, and how to broadcast it to the network.

To achieve this a small script in Javascript needs to be written, that utilizes Lisk Elements packages to create the transaction object, sign it, and send it to the network.

For the default transaction types, it is also possible to use Lisk Commander to create and broadcast transactions from the command-line or Lisk Desktop to send a transaction via a user-friendly UI.

Ensure that Node.js is the latest LTS (version 12 or higher), and create a Node.js project by creating a package.json file inside of your current folder.

Install dependencies
mkdir create-tx
cd create-tx
node --version
# v12.15.0
npm init --yes
npm i @liskhq/lisk-transactions
npm i @liskhq/lisk-cryptography
npm i @liskhq/lisk-api-client

Now create a new file index.js that will contain the script to generate a transaction, sign it and then post it to the network.

The full script can be viewed at 3. Broadcast a transaction.

1. Create a transaction object

  • Default transaction

  • Custom transaction

How to create a tx object with Lisk Elements
const transactions = require('@liskhq/lisk-transactions');
const {getNetworkIdentifier} = require('@liskhq/lisk-cryptography');

const networkIdentifier = getNetworkIdentifier(
    "23ce0366ef0a14a91e5fd4b1591fc880ffbef9d988ff8bebf8f3666b0c09597d",
    "Lisk",
);

const tx = new transactions.TransferTransaction({
    asset: {
        amount: '1',
        recipientId: '1L',
    },
    networkIdentifier: networkIdentifier,
});

console.log(tx.stringify());
Example output
{
   "type":8,
   "timestamp":0,
   "senderPublicKey":"",
   "senderId":"",
   "fee":"10000000",
   "signatures":[

   ],
   "asset":{
      "amount":"1",
      "recipientId":"1L"
   }
}

The HelloTransaction from the Hello World app is used as an example here.

How to create a custom tx object with Lisk Elements
const HelloTransaction = require('../hello_transaction');
const {getNetworkIdentifier} = require('@liskhq/lisk-cryptography');

const networkIdentifier = getNetworkIdentifier(
    "23ce0366ef0a14a91e5fd4b1591fc880ffbef9d988ff8bebf8f3666b0c09597d",
    "Lisk",
);

const tx = new HelloTransaction({
    asset: {
        hello: 'world',
    },
    networkIdentifier: networkIdentifier,
});

console.log(tx.stringify());
Example output
{
  "type": 20,
  "timestamp": 0,
  "senderPublicKey": "",
  "senderId": "",
  "fee": "0",
  "signatures": [],
  "asset": {
    "hello": "world"
  }
}

2. Sign a transaction

Before any transaction can be sent to the network, it is required for the sender’s account to sign the transaction object.

See the Lisk protocol for more information about the signature scheme.
  • Default transaction

  • Custom transaction

How to sign tx object with Lisk Elements
const transactions = require('@liskhq/lisk-transactions');
const {getNetworkIdentifier} = require('@liskhq/lisk-cryptography');

const networkIdentifier = getNetworkIdentifier(
    "23ce0366ef0a14a91e5fd4b1591fc880ffbef9d988ff8bebf8f3666b0c09597d",
    "Lisk",
);

const tx = new transactions.TransferTransaction({
    asset: {
        amount: '1',
        recipientId: '1L',
    },
    networkIdentifier: networkIdentifier,
});

tx.sign('creek own stem final gate scrub live shallow stage host concert they');

console.log(tx.stringify());
Example output
{
   "id":"14444765956109766257",
   "type":8,
   "timestamp":0,
   "senderPublicKey":"5c554d43301786aec29a09b13b485176e81d1532347a351aeafe018c199fd7ca",
   "senderId":"11237980039345381032L",
   "fee":"10000000",
   "signature":"49d5824b9008b2005a554d984dedf8986b8bb54328dc5bf4c6a61fcdca6115a5ac0e17b5ec4c24bdaaae4f3be2cf808f514d2b74c506c6df9fcfcfad1caaa702",
   "signatures":[],
   "asset":{
      "amount":"1",
      "recipientId":"1L"
   }
}

The HelloTransaction from the Hello World app is used as an example here.

How to sign a custom tx object with Lisk Elements
const HelloTransaction = require('../hello_transaction');
const {getNetworkIdentifier} = require('@liskhq/lisk-cryptography');

const networkIdentifier = getNetworkIdentifier(
    "23ce0366ef0a14a91e5fd4b1591fc880ffbef9d988ff8bebf8f3666b0c09597d",
    "Lisk",
);

const tx = new HelloTransaction({
    asset: {
        hello: 'world',
    },
    networkIdentifier: networkIdentifier,
});

tx.sign('wagon stock borrow episode laundry kitten salute link globe zero feed marble');

console.log(tx.stringify());
Example output
{
  "id": "11559016465370069697",
  "type": 20,
  "timestamp": 0,
  "senderPublicKey": "c094ebee7ec0c50ebee32918655e089f6e1a604b83bcaa760293c61e0f18ab6f",
  "senderId": "16313739661670634666L",
  "fee": "0",
  "signature": "7524e854fe7da042606e4893e61e2515ec1956f70231422973fa9369d345eded998e5a9ba90902e51cb0ac8fdce88fca645fb44e7085fe7ed7f1b29499ae570c",
  "signatures": [],
  "asset": {
    "hello": "world"
  }
}

3. Broadcast a transaction

  • Default transaction

  • Custom Transaction

How to create, sign and post a transaction
const transactions = require('@liskhq/lisk-transactions');
const {getNetworkIdentifier} = require('@liskhq/lisk-cryptography');
const { APIClient } = require('@liskhq/lisk-api-client');

// Constants
const API_BASEURL = 'http://localhost:4000'; (1)
const networkIdentifier = getNetworkIdentifier(
    "23ce0366ef0a14a91e5fd4b1591fc880ffbef9d988ff8bebf8f3666b0c09597d",
    "Lisk",
);

// Initialize
const api = new APIClient([API_BASEURL]);

const tx = new transactions.TransferTransaction({
    asset: {
        amount: '1',
        recipientId: '1L',
    },
    networkIdentifier: networkIdentifier,
});

tx.sign('creek own stem final gate scrub live shallow stage host concert they');

api.transactions.broadcast(tx.toJSON()).then(res => {
    console.log("++++++++++++++++ API Response +++++++++++++++++");
    console.log(res.data);
    console.log("++++++++++++++++ Transaction Payload +++++++++++++++++");
    console.log(tx.stringify());
    console.log("++++++++++++++++ End Script +++++++++++++++++");
}).catch(err => {
    console.log(JSON.stringify(err.errors, null, 2));
});
1 http://localhost:4000 will post the transaction to a node that runs locally (this is indicated by the url http://localhost), and is connected to the Devnet, (this is indicated by the port number 4000). Replace http://localhost:4000 with the url of the node you want to broadcast the transaction to.
Example output
++++++++++++++++ API Response +++++++++++++++++
{ message: 'Transaction(s) accepted' }
++++++++++++++++ Transaction Payload +++++++++++++++++
{
  "id": "14444765956109766257",
  "type": 8,
  "timestamp": 0,
  "senderPublicKey": "5c554d43301786aec29a09b13b485176e81d1532347a351aeafe018c199fd7ca",
  "senderId": "11237980039345381032L",
  "fee": "10000000",
  "signature": "49d5824b9008b2005a554d984dedf8986b8bb54328dc5bf4c6a61fcdca6115a5ac0e17b5ec4c24bdaaae4f3be2cf808f514d2b74c506c6df9fcfcfad1caaa702",
  "signatures": [],
  "asset": {
    "amount": "1",
    "recipientId": "1L"
  }
}
++++++++++++++++ End Script +++++++++++++++++

The HelloTransaction from the Hello World app is used as an example here.

How to create, sign and post a transaction
const HelloTransaction = require('./hello');
const {getNetworkIdentifier} = require('@liskhq/lisk-cryptography');
const { APIClient } = require('@liskhq/lisk-api-client');

// Constants
const API_BASEURL = 'http://localhost:4000';
const networkIdentifier = getNetworkIdentifier(
    "23ce0366ef0a14a91e5fd4b1591fc880ffbef9d988ff8bebf8f3666b0c09597d",
    "Lisk",
);

// Initialize
const api = new APIClient([API_BASEURL]);

const tx = new HelloTransaction({
    asset: {
        hello: 'world',
    },
    networkIdentifier: networkIdentifier,
});

tx.sign('wagon stock borrow episode laundry kitten salute link globe zero feed marble');

api.transactions.broadcast(tx.toJSON()).then(res => {
    console.log("++++++++++++++++ API Response +++++++++++++++++");
    console.log(res.data);
    console.log("++++++++++++++++ Transaction Payload +++++++++++++++++");
    console.log(tx.stringify());
    console.log("++++++++++++++++ End Script +++++++++++++++++");
}).catch(err => {
    console.log(JSON.stringify(err.errors, null, 2));
});
Example output
++++++++++++++++ API Response +++++++++++++++++
{ message: 'Transaction(s) accepted' }
++++++++++++++++ Transaction Payload +++++++++++++++++
{
  "id": "2039423469691006779",
  "type": 20,
  "timestamp": 0,
  "senderPublicKey": "5c554d43301786aec29a09b13b485176e81d1532347a351aeafe018c199fd7ca",
  "senderId": "11237980039345381032L",
  "fee": "0",
  "signature": "532c3297451bc7f14fe7b80b38d55b4cc9527b1d13a6f353fa7f13b8af973e69d47f87d4108e5768e0a9e0e6a426de6ae0751005dd126f04fa34f97882bfc509",
  "signatures": [],
  "asset": {
    "hello": "world"
  }
}
++++++++++++++++ End Script +++++++++++++++++

4. What happens to a transaction after a node receives it?

The transaction will be validated by the node, and added to the transaction pool if it is valid. To validate the transaction, it will execute the logic defined in the validateAsset() method.

The node will also inform its peer nodes about the new transaction, so in turn all of them will validate the transaction and add it to their transaction pool as well. If the transaction is added to the transaction pool of a forging node, the transaction will be included in one of the next new blocks, if it is not included already by another forger.

Once the transaction is included into a block, it becomes part of the blockchain.

By including a transaction into a block, the node executes the logic defined in the applyAsset() method of the transaction.

To ensure that the transaction is final, it is recommended to wait for at least 150 blocks.

It is possible to verify the finality of a particular transaction via the API:

  1. GET api/node/status to receive the node status data. It should contain a property chainMaxHeightFinalized which describes the highest block height of the network, that is already finalized.

  2. GET api/transactions?id=<TRANSACTION_ID> to receive the data of the transaction that you wish to check for finality. Replace <TRANSACTION_ID> with the ID of the transaction. The data should contain a property height, which indicates the block height at the time the transaction was included into the blockchain.

  3. The final step here is now to compare the two values. Hence the transaction is final, if chainMaxHeightFinalized > height.

If a transaction is finalized, it becomes a permanent part of the blockchain and cannot be removed anymore.