Monitoring a transaction status
Make sure a transaction gets included in the blockchain after being announced.
Background Information
After calling an API method that changes the database state, you usually will receive a response if the change has been applied or failed due to some constraint. As the application spends precious time waiting for the response, other actions can be processed in the meantime.
When working with blockchain technology, it is interesting to “fire” the transaction, let the node process it, and receive a notification if it succeeded or failed. Differently, from a traditional database, the average confirmation time of modification is higher, passing from milliseconds to seconds - or minutes in the worst case.
Prerequisites
- Text editor or IDE.
- XPX-Chain-SDK or XPX-Chain-CLI.
- Finish the getting started section.
Getting into some code
Sirius Chain enables asynchronous transactions announcement. After you publish a transaction, the API node will always accept it if it is well-formed.
At this time, the server does not ensure that the transaction is valid. For example, you don’t have the amount of asset units you want to send, hence, it is not certain it will be added in a block.
To ensure the transaction is added in a block, you must track the transaction status using Listeners.
Listeners enable receiving notifications when a change in the blockchain occurs. The notification is received in real time without having to poll the API waiting for a reply.
- Define the transaction you want to announce. In this case, we are going to send the message
Test
toVD5DT3-CH4BLA-BL5HIM-EKP2TA-PUKF4N-Y3L5HR-IR54
.
conf, err := sdk.NewConfig(context.Background(), []string{"http://bctestnet1.brimstone.xpxsirius.io:3000"})
if err != nil {
panic(err)
}
// Use the default http client
client := sdk.NewClient(nil, conf)
address, err := sdk.NewAddressFromRaw("VD5DT3-CH4BLA-BL5HIM-EKP2TA-PUKF4N-Y3L5HR-IR54")
if err != nil {
panic(err)
}
transferTransaction, err := client.NewTransferTransaction(sdk.NewDeadline(time.Hour), address, []*sdk.Mosaic{}, sdk.NewPlainMessage("Test"))
if err != nil {
panic(err)
}
const recipientAddress = Address.createFromRawAddress("VD5DT3-CH4BLA-BL5HIM-EKP2TA-PUKF4N-Y3L5HR-IR54");
const transferTransaction = TransferTransaction.create(
Deadline.create(),
recipientAddress,
[],
PlainMessage.create('Test'),
NetworkType.TEST_NET);
- Sign the transaction.
signer, err := client.NewAccountFromPrivateKey(os.Getenv("PRIVATE_KEY"))
if err != nil {
panic(err)
}
signedTransaction, err := signer.Sign(transferTransaction)
if err != nil {
panic(err)
}
const privateKey = process.env.PRIVATE_KEY as string;
const signer = Account.createFromPrivateKey(privateKey, NetworkType.TEST_NET);
const signedTransaction = signer.sign(transferTransaction, generationHash);
- Open a new Listener. This communicates with the API WebSocket, who will communicate with you asynchronously the status of the transaction.
wsClient, err := websocket.NewClient(context.Background(), conf)
if err != nil {
panic(err)
}
const url = 'http://bctestnet1.brimstone.xpxsirius.io:3000';
const listener = new Listener(url);
const transactionHttp = new TransactionHttp(url);
const amountOfConfirmationsToSkip = 5;
listener.open().then(() => {
- Start monitoring if the WebSocket connection is alive. Blocks are generated every
15
seconds on average, so a timeout can be raised if there is no response after approximately 30 seconds.
err = wsClient.AddBlockHandlers(func (info *sdk.BlockInfo) bool {
fmt.Printf("Block received with height: %v \n", info.Height)
return true
})
if err != nil {
panic(err)
}
const newBlockSubscription = listener
.newBlock()
.pipe(timeout(30000)) // time in milliseconds when to timeout.
.subscribe(block => {
console.log("New block created:" + block.height.compact());
},
error => {
console.error(error);
listener.terminate();
});
xpx2-cli monitor block
- Monitor if there is some validation error with the transaction issued. When you receive a message from status WebSocket channel, it always means the transaction did not meet the requirements. You need to handle the error accordingly, by reviewing the error status list.
err = wsClient.AddStatusHandlers(signer.Address, func (info *sdk.StatusInfo) bool {
fmt.Printf("Status: %v \n", info.Status)
fmt.Printf("Hash: %v \n", info.Hash)
return true
})
if err != nil {
panic(err)
}
listener
.status(signer.address)
.pipe(filter(error => error.hash === signedTransaction.hash))
.subscribe(error => {
console.log("❌:" + error.status);
newBlockSubscription.unsubscribe();
listener.close();
},
error => console.error(error));
xpx2-cli monitor status --address VD5DT3-CH4BLA-BL5HIM-EKP2TA-PUKF4N-Y3L5HR-IR54
- Monitor if the transaction reaches the network as well. When you receive a message from an unconfirmed WebSocket channel, the transaction is valid and is waiting to be included in a block. This does not necessarily mean that the transaction will be included, as a second validation happens before being finally confirmed.
err = wsClient.AddUnconfirmedAddedHandlers(signer.Address, func (info sdk.Transaction) bool {
fmt.Printf("UnconfirmedAdded Tx Hash: %v \n", info.GetAbstractTransaction().TransactionHash)
return true
})
if err != nil {
panic(err)
}
listener
.unconfirmedAdded(signer.address)
.pipe(filter(transaction => (transaction.transactionInfo !== undefined
&& transaction.transactionInfo.hash === signedTransaction.hash)))
.subscribe(ignored => console.log("⏳: Transaction status changed to unconfirmed"),
error => console.error(error));
xpx2-cli monitor unconfirmed --address VD5DT3-CH4BLA-BL5HIM-EKP2TA-PUKF4N-Y3L5HR-IR54
- Monitor when the transaction gets included in a block. When included, the transaction can still be rolled-back because of forks. You can decide for yourself that after e.g. 6 blocks the transaction is secured.
err = wsClient.AddConfirmedAddedHandlers(signer.Address, func (info sdk.Transaction) bool {
fmt.Printf("ConfirmedAdded Tx Hash: %v \n", info.GetAbstractTransaction().TransactionHash)
return true
})
if err != nil {
panic(err)
}
listener
.confirmed(signer.address)
.pipe(
filter(transaction =>(transaction.transactionInfo !== undefined
&& transaction.transactionInfo.hash === signedTransaction.hash)),
mergeMap(transaction => {
return listener.newBlock()
.pipe(
skip(amountOfConfirmationsToSkip),
first(),
map( ignored => transaction))
})
)
.subscribe(ignored => {
console.log("✅: Transaction confirmed");
newBlockSubscription.unsubscribe();
listener.close();
}, error => console.error(error));
xpx2-cli monitor confirmed --address VD5DT3-CH4BLA-BL5HIM-EKP2TA-PUKF4N-Y3L5HR-IR54
- Finally, announce the transaction to the network.
_, err = client.Transaction.Announce(ctx, signedTransaction)
if err != nil {
panic(err)
}
transactionHttp
.announce(signedTransaction)
.subscribe(x => console.log(x),
error => console.error(error));
});
xpx2-cli transaction transfer --recipient VD5DT3-CH4BLA-BL5HIM-EKP2TA-PUKF4N-Y3L5HR-IR54 --message "Test"
If you missed the WebSocket response, check the transaction status after by calling the transaction status endpoint. The status of failed transactions is not persistent, which means that eventually it will be pruned.
What’s next?
Run your application and try to send a transfer transaction to the selected account. If all goes well, you will see the transaction information in your terminal.