Monitoring a transaction status

Make sure a transaction gets included in the blockchain after being announced.

Background

After calling an API method that changes a database state, the application usually receives a response informing if the change has been applied or failed due to some constraints.

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 even minutes in the worst cases.

In this guide, you are going to learn how to verify that your transaction has been recorded permanently before executing any other critical action.

Prerequisites

Getting into some code

Catapult enables an asynchronous transaction announcement. After an application publishes 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 might not have the number of asset units you want to need to send and still, get a positive response from the server. For this reason, the “OK” response does not guarantee getting the transaction included in a block. To make sure the transaction is added in a block, you must track the transaction status using Listeners.

Listeners enable receiving notifications possible when a change in the blockchain occurs. The notification is received in real-time without having to poll the API waiting for a reply.

Let’s see how transaction monitorization works transferring a TransferTransaction.

  1. Define the transaction you want to announce. In this case, we are going to send the message Test to SD5DT3-CH4BLA-BL5HIM-EKP2TA-PUKF4N-Y3L5HR-IR54.
const recipientAddress = Address.createFromRawAddress("SD5DT3-CH4BLA-BL5HIM-EKP2TA-PUKF4N-Y3L5HR-IR54");
const transferTransaction = TransferTransaction.create(
    Deadline.create(),
    recipientAddress,
    [],
    PlainMessage.create('Test'),
    NetworkType.MIJIN_TEST);
  1. Sign the transaction with your account.

Note

To make the transaction only valid for your network, include the first block generation hash. Open http://localhost:3000/block/1 in a new tab and copy the meta.generationHash value.

const privateKey = process.env.PRIVATE_KEY as string;
const signer = Account.createFromPrivateKey(privateKey, NetworkType.MIJIN_TEST);
const networkGenerationHash = process.env.NETWORK_GENERATION_HASH as string;
const signedTransaction = signer.sign(transferTransaction, networkGenerationHash);
  1. Open a new Listener. The listener communicates with the API WebSocket, which forward you asynchronously the status of the transaction.
const url = 'http://localhost:3000';
const listener = new Listener(url);
const transactionHttp = new TransactionHttp(url);

const amountOfConfirmationsToSkip = 5;

listener.open().then(() => {
  1. Monitor if the WebSocket connection is alive. Blocks are generated every 15 seconds in average, so a timeout can be raised if there is no response after 30 seconds approximately.
    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();
            });
nem2-cli monitor block
  1. 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.
    listener
        .status(signer.address)
        .pipe(filter(error => error.hash === signedTransaction.hash))
        .subscribe(error => {
                console.log("❌:" + error.status);
                newBlockSubscription.unsubscribe();
                            listener.terminate();
            },
            error => console.error(error));
nem2-cli monitor status --address SD5DT3-CH4BLA-BL5HIM-EKP2TA-PUKF4N-Y3L5HR-IR54
  1. Monitor as well if the transaction reaches the network. When you receive a message from unconfirmed WebSocket channel, the transaction is valid and is waiting to be included in a block. This does not mean necessarily that the transaction will be included, as a second validation happens before being finally confirmed.
    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));
nem2-cli monitor unconfirmed --address SD5DT3-CH4BLA-BL5HIM-EKP2TA-PUKF4N-Y3L5HR-IR54
  1. Monitor when the transaction gets included in a block. When included, transaction can still be rolled-back because of forks. You can decide for yourself that after e.g. 6 blocks the transaction is secured.
    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.terminate();
        }, error => console.error(error));
nem2-cli monitor confirmed --address SD5DT3-CH4BLA-BL5HIM-EKP2TA-PUKF4N-Y3L5HR-IR54
  1. Finally, announce the transaction to the network.
    transactionHttp
        .announce(signedTransaction)
        .subscribe(x => console.log(x),
            error => console.error(error));
});
 nem2-cli transaction transfer --recipient SD5DT3-CH4BLA-BL5HIM-EKP2TA-PUKF4N-Y3L5HR-IR54 --mosaics @cat.currency::10000000 --message "Welcome to NEM"

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, meaning that eventually is pruned.

Note

If you are developing a small application, and monitoring asynchronous transactions adds too much overhead to your project, consider turning asynchronous transactions announcement into synchronous.

Troubleshooting: Monitoring transactions on the client side

The NEM2-SDK for typescript base Listener was designed to work on Node.js backend environments. To make the code work in the client side (e.g., Angular, React, Vue.), pass the browser implementation of the WebSocket to the Listener.

const listener = new Listener('ws://localhost:3000', WebSocket);
listener.open().then(() => ...