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.
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
const transactions = require('@liskhq/lisk-transactions');
const tx = new transactions.TransferTransaction({
asset: {
amount: '1',
recipientId: '1L',
},
fee: '10000000',
nonce: '0',
});
console.log(tx.stringify());
{
"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.
const HelloTransaction = require('../transactions/hello_transaction');
const tx = new HelloTransaction({
asset: {
hello: 'world',
},
fee: "10000000",
nonce: "0",
});
console.log(tx.stringify());
{
"asset": {
"hello": "world"
},
"fee": "10000000",
"nonce": "0",
"senderId": "",
"senderPublicKey": "",
"signatures": [],
"type": 20
}
2. Acquire the nonce
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. |
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());
{
"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.
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());
{
"asset": {
"hello": "world"
},
"fee": "10000000",
"id": "9283551789433227429",
"nonce": "0",
"senderId": "16313739661670634666L",
"senderPublicKey": "c094ebee7ec0c50ebee32918655e089f6e1a604b83bcaa760293c61e0f18ab6f",
"signatures": [
"ebb1c78e412b7cd83984e7633180ec1649bdb1e16db43759011b8567d78a47162d5209501a9462570dabeab6605412e7fc1f50db982b1f502c35f6a7daa69604"
],
"type": 20
}
4. Validate the fee
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
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. |
//++++++++++++++++ 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.
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));
});
//++++++++++++++++ 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:
-
GET
api/node/status
to receive the node status data. It should contain a propertychainMaxHeightFinalized
which describes the highest block height of the network, that is already finalized. -
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 propertyheight
, which indicates the block height at the time, the transaction was included into the blockchain. -
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. |