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 the Lisk Elements packages in order 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 posts it to the network.

See the full script 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 tx = new transactions.TransferTransaction({
    asset: {
        amount: '1',
        recipientId: '1L',
    },
    fee: '10000000',
    nonce: '0',
});

console.log(tx.stringify());
Example output
{
    "asset": {
        "amount": "1",
        "recipientId": "1L"
    },
    "fee": "10000000",
    "nonce": "0",
    "senderId": "",
    "senderPublicKey": "",
    "signatures": [],
    "type": 8
}

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('../transactions/hello_transaction');

const tx = new HelloTransaction({
    asset: {
        hello: 'world',
    },
    fee: "10000000",
    nonce: "0",
});

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

2. Acquire the nonce

How to acquire the current nonce of an account via API call
const HelloTransaction = require('../transactions/hello_transaction');
const { APIClient } = require('@liskhq/lisk-api-client');
const API_BASEURL = 'http://localhost:4000';
const api = new APIClient([API_BASEURL]);

api.accounts.get({address: '5059876081639179984L'}).then(response => {

    const nonce = parseInt(response.data[0].nonce);
    const tx = new HelloTransaction({
        asset: {
            hello: 'world',
        },
        fee: "10000000",
        nonce: nonce,
    });

    console.log(tx.stringify());
});

3. Sign a transaction

Before any transaction can be sent to the network, it is required for the sender 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(
    "19074b69c97e6f6b86969bb62d4f15b888898b499777bda56a3a2ee642a7f20a",
    "Lisk",
);

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

tx.sign(
    networkIdentifier,
    'peanut hundred pen hawk invite exclude brain chunk gadget wait wrong ready'
);

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

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('../transactions/hello_transaction');
const {getNetworkIdentifier} = require('@liskhq/lisk-cryptography');

const networkIdentifier = getNetworkIdentifier(
    "19074b69c97e6f6b86969bb62d4f15b888898b499777bda56a3a2ee642a7f20a",
    "Lisk",
);

const tx = new HelloTransaction({
    asset: {
        hello: 'world',
    },
    fee: "10000000",
    nonce: "0",
});

tx.sign(
    networkIdentifier,
    'peanut hundred pen hawk invite exclude brain chunk gadget wait wrong ready'
);
console.log(tx.stringify());
Example output
{
    "asset": {
        "hello": "world"
    },
    "fee": "10000000",
    "id": "9283551789433227429",
    "nonce": "0",
    "senderId": "16313739661670634666L",
    "senderPublicKey": "c094ebee7ec0c50ebee32918655e089f6e1a604b83bcaa760293c61e0f18ab6f",
    "signatures": [
        "ebb1c78e412b7cd83984e7633180ec1649bdb1e16db43759011b8567d78a47162d5209501a9462570dabeab6605412e7fc1f50db982b1f502c35f6a7daa69604"
    ],
    "type": 20
}

4. Validate the fee

How to validate the fee for a transaction before sending it to a node
const HelloTransaction = require('../hello_world/transactions/hello_transaction');
const { getNetworkIdentifier } = require("@liskhq/lisk-cryptography");
const networkIdentifier = getNetworkIdentifier(
    "19074b69c97e6f6b86969bb62d4f15b888898b499777bda56a3a2ee642a7f20a",
    "Lisk"
);

const tx = new HelloTransaction({
    asset: {
        hello: 'world',
    },
    fee: "10",
    nonce: "0",
});

tx.sign(
    networkIdentifier,
    "peanut hundred pen hawk invite exclude brain chunk gadget wait wrong ready"
);

// Validate that the fee for the transaction is equal to, or higher than the minimum fee.
if ( tx.minFee > tx.fee) {
    console.log("Please provide a higher fee. Minimum fee for the current transaction: " + tx.minFee);
    console.dir(tx);
} else {
    console.log(tx.stringify());
}

5. 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(
    "19074b69c97e6f6b86969bb62d4f15b888898b499777bda56a3a2ee642a7f20a",
    "Lisk",
);

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

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

tx.sign(
    networkIdentifier,
    'peanut hundred pen hawk invite exclude brain chunk gadget wait wrong ready'
);

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, where you wish to broadcast the transaction.
Example output
//++++++++++++++++ API Response +++++++++++++++++
{ message: 'Transaction(s) accepted' }
//++++++++++++++++ Transaction Payload +++++++++++++++++
{
   "id":"17387110868403092024",
   "type":8,
   "senderPublicKey":"0fe9a3f1a21b5530f27f87a414b549e79a940bf24fdf2b2f05e7f22aeeecc86a",
   "senderId":"5059876081639179984L",
   "nonce":"105",
   "fee":"10000000",
   "signatures":[
      "ee65a1b47c536463fa7b46d366246ae5aad4e1ecf05344bba92fcd2d1dd028bab36d98aefe35be4831e048cef258b20e785853f8f3f612d25cd41491f247030b"
   ],
   "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('./transactions/hello_transaction.js');
const { getNetworkIdentifier } = require('@liskhq/lisk-cryptography');
const { APIClient } = require('@liskhq/lisk-api-client');

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

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

const tx = new HelloTransaction({
    asset: {
        hello: 'world',
    },
    fee: "10000000",
    nonce: "0",});

tx.sign(
    networkIdentifier,
    'peanut hundred pen hawk invite exclude brain chunk gadget wait wrong ready'
);

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":"14010199306117184554",
   "type":20,
   "senderPublicKey":"0fe9a3f1a21b5530f27f87a414b549e79a940bf24fdf2b2f05e7f22aeeecc86a",
   "senderId":"5059876081639179984L",
   "nonce":"104",
   "fee":"10000000",
   "signatures":[
      "56a17864d905fc96d2755f16d9c75088d23e88050e6958c21faf99c4cc3d09fb6889cee7551866f6568ae8fc730f4fd7175b17f143dda2ea842afe4ad051f004"
   ],
   "asset":{
      "hello":"world"
   }
}
//++++++++++++++++ End Script +++++++++++++++++

6. What happens to a transaction after a node received 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 want 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. All that is required now, is to compare the two values: 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.