Skip to content

Listening to Transaction Flow⚓︎

BEGINNER

NEM provides WebSocket channels that send real-time notifications as a transaction moves through the confirmation process for a specific account. Compared to polling the /transaction/get GET endpoint, WebSockets push updates as they happen without the overhead of repeated API calls.

This tutorial shows how to subscribe to transaction channels, announce a minimal Transfer Transaction, and wait for its confirmation using WebSockets.

Alternative: Polling

For a polling-based approach, see the Monitoring Transaction Status tutorial.

Prerequisites⚓︎

Before you start, make sure to:

Additionally, NEM serves WebSockets using the STOMP messaging protocol over SockJS, so a STOMP client and a WebSocket transport are required:

Install the stomper and websockets libraries:

pip install stomper websockets

Install the @stomp/stompjs and sockjs-client libraries:

npm install @stomp/stompjs sockjs-client

See the WebSocket reference for details on the connection protocol.

Full Code⚓︎

import asyncio
import json
import os
import random
import urllib.request
import uuid

import stomper
from symbolchain.CryptoTypes import PrivateKey
from symbolchain.facade.NemFacade import NemFacade
from symbolchain.nc import Amount
from symbolchain.nem.FeeCalculator import calculate_transaction_fee
from symbolchain.nem.Network import NetworkTimestamp
from websockets import connect

NODE_URL = os.getenv('NODE_URL', 'http://libertalia.nemtest.net:7890')
WS_URL = NODE_URL.replace(':7890', ':7778')
print(f'Using node {NODE_URL}')


# SockJS has no Python client library.
# These helpers wrap the raw WebSocket transport to mirror a STOMP client.
def sockjs_url(endpoint_url):
    # SockJS raw WebSocket transport adds a random server and session id
    server = random.randint(100, 999)
    session = uuid.uuid4().hex
    ws_base = endpoint_url.replace('http', 'ws', 1)
    return f'{ws_base}/{server}/{session}/websocket'


async def send_frame(websocket, frame):
    # SockJS wraps each client payload as a JSON array of frame strings
    await websocket.send(json.dumps([frame]))


async def stomp_connect(websocket):
    await websocket.recv()  # consume the SockJS open frame
    await send_frame(
        websocket, stomper.connect('', '', NODE_URL, heartbeats=(0, 0)))


async def stomp_subscribe(websocket, destination, sub_id):
    await send_frame(websocket, stomper.subscribe(destination, sub_id))


async def stomp_send(websocket, destination, body):
    await send_frame(websocket, stomper.send(destination, body))


async def stomp_unsubscribe(websocket, sub_id):
    await send_frame(websocket, stomper.unsubscribe(sub_id))


async def stomp_disconnect(websocket):
    await send_frame(websocket, stomper.disconnect())


def stomp_messages(raw_frame):
    # Yield each STOMP MESSAGE frame in a SockJS data frame
    if 'a' != raw_frame[0]:  # skip 'o' open, 'h' heartbeat, 'c' close
        return
    for payload in json.loads(raw_frame[1:]):
        frame = stomper.unpack_frame(payload)
        if 'MESSAGE' == frame['cmd']:
            yield frame


async def stomp_frames(websocket):
    # Yield each STOMP MESSAGE frame as it arrives
    async for raw_frame in websocket:
        for frame in stomp_messages(raw_frame):
            yield frame


# Set up the monitored address and signer
MONITOR_ADDRESS = os.getenv(
    'MONITOR_ADDRESS',
    'TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4'
)
print(f'Monitoring address: {MONITOR_ADDRESS}')

SIGNER_PRIVATE_KEY = os.getenv(
    'SIGNER_PRIVATE_KEY',
    '0000000000000000000000000000000000000000000000000000000000000000'
)
facade = NemFacade('testnet')
signer_key_pair = NemFacade.KeyPair(PrivateKey(SIGNER_PRIVATE_KEY))


async def main():
    # Build and sign a transfer to the monitored address
    with urllib.request.urlopen(
        f'{NODE_URL}/time-sync/network-time'
    ) as resp:
        network_time = json.loads(
            resp.read().decode())['receiveTimeStamp'] // 1000
    timestamp = NetworkTimestamp(network_time)
    deadline = timestamp.add_hours(2)
    transaction = facade.transaction_factory.create({
        'type': 'transfer_transaction_v2',
        'signer_public_key': signer_key_pair.public_key,
        'timestamp': timestamp.timestamp,
        'deadline': deadline.timestamp,
        'recipient_address': MONITOR_ADDRESS,
        'amount': 0,
    })
    transaction.fee = Amount(calculate_transaction_fee(transaction))
    signature = facade.sign_transaction(signer_key_pair, transaction)
    json_payload = facade.transaction_factory.attach_signature(
        transaction, signature)
    transaction_hash = str(
        facade.hash_transaction(transaction)).upper()

    # Connect to the WebSocket
    endpoint = f'{WS_URL}/w/messages'
    async with connect(sockjs_url(endpoint)) as websocket:
        await stomp_connect(websocket)
        print(f'Connected to {WS_URL}')

        # Subscribe to the account and transaction channels
        account_channel = f'/account/{MONITOR_ADDRESS}'
        channels = {
            account_channel: 'id-0',
            f'/unconfirmed/{MONITOR_ADDRESS}': 'id-1',
            f'/transactions/{MONITOR_ADDRESS}': 'id-2',
        }
        for channel, sub_id in channels.items():
            await stomp_subscribe(websocket, channel, sub_id)
            print(f'Subscribed to {channel} channel')

        # Register the account and confirm it is active
        await stomp_send(websocket, '/w/api/account/get',
            json.dumps({'account': MONITOR_ADDRESS}))
        async for frame in stomp_frames(websocket):
            if account_channel == frame['headers']['destination']:
                balance = json.loads(
                    frame['body'])['account']['balance']
                print(f'Account update: balance={balance}')
                break
        print('Account registered')

        # Announce the transaction and wait for it to confirm
        print(f'Announcing transaction {transaction_hash[:16]}...')
        announce_request = urllib.request.Request(
            f'{NODE_URL}/transaction/announce',
            data=json_payload.encode(),
            headers={'Content-Type': 'application/json'},
            method='POST'
        )
        with urllib.request.urlopen(announce_request) as resp:
            result = json.loads(resp.read().decode())

        if 'SUCCESS' == result['message']:
            confirmed = False
            async for frame in stomp_frames(websocket):
                destination = frame['headers']['destination']
                body = json.loads(frame['body'])
                if account_channel == destination:
                    balance = body['account']['balance']
                    print(f'Account update: balance={balance}')
                    if confirmed:
                        break
                elif '/transactions/' in destination:
                    message_hash = body['meta']['hash']['data']
                    print(f'confirmed: hash={message_hash[:16]}...')
                    if message_hash.upper() == transaction_hash:
                        short_hash = transaction_hash[:16]
                        print(f'Transaction {short_hash}... confirmed')
                        confirmed = True
                else:
                    message_hash = body['meta']['hash']['data']
                    if message_hash.upper() == transaction_hash:
                        print(f'unconfirmed: hash={message_hash[:16]}...')
        else:
            print(f'Transaction rejected: {result["message"]}')

        # Unsubscribe before closing
        for sub_id in channels.values():
            await stomp_unsubscribe(websocket, sub_id)
        print('Unsubscribed from all channels')
        await stomp_disconnect(websocket)


try:
    asyncio.run(main())
except Exception as error:
    print(error)

Download source

import { Client } from '@stomp/stompjs';
import SockJS from 'sockjs-client';
import { PrivateKey } from 'symbol-sdk';
import {
    NemFacade, NetworkTimestamp, calculateTransactionFee, models
} from 'symbol-sdk/nem';

const NODE_URL = process.env.NODE_URL ||
    'http://libertalia.nemtest.net:7890';
const WS_URL = NODE_URL.replace(':7890', ':7778');
console.log(`Using node ${NODE_URL}`);
// Set up the monitored address and signer
const MONITOR_ADDRESS = process.env.MONITOR_ADDRESS ||
    'TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4';
console.log(`Monitoring address: ${MONITOR_ADDRESS}`);

const SIGNER_PRIVATE_KEY = process.env.SIGNER_PRIVATE_KEY ||
    '0000000000000000000000000000000000000000000000000000000000000000';
const facade = new NemFacade('testnet');
const signerKeyPair = new NemFacade.KeyPair(
    new PrivateKey(SIGNER_PRIVATE_KEY));

try {
    // Build and sign a transfer to the monitored address
    const timeResponse = await fetch(
        `${NODE_URL}/time-sync/network-time`);
    const networkTime = Math.floor(
        (await timeResponse.json()).receiveTimeStamp / 1000);
    const timestamp = new NetworkTimestamp(networkTime);
    const deadline = timestamp.addHours(2);
    const transaction = facade.transactionFactory.create({
        type: 'transfer_transaction_v2',
        signerPublicKey: signerKeyPair.publicKey.toString(),
        timestamp: timestamp.timestamp,
        deadline: deadline.timestamp,
        recipientAddress: MONITOR_ADDRESS,
        amount: 0n
    });
    transaction.fee = new models.Amount(
        calculateTransactionFee(transaction));
    const signature = facade.signTransaction(signerKeyPair, transaction);
    const jsonPayload = facade.transactionFactory.static.attachSignature(
        transaction, signature);
    const transactionHash =
        facade.hashTransaction(transaction).toString().toUpperCase();
    const shortHash = transactionHash.substring(0, 16);

    // Connect to the WebSocket
    const client = new Client({
        webSocketFactory: () => new SockJS(`${WS_URL}/w/messages`)
    });
    await new Promise(resolve => {
        client.onConnect = resolve;
        client.activate();
    });
    console.log(`Connected to ${WS_URL}`);

    // Subscribe to the account and transaction channels
    const accountChannel = `/account/${MONITOR_ADDRESS}`;
    const channels = {
        [accountChannel]: 'id-0',
        [`/unconfirmed/${MONITOR_ADDRESS}`]: 'id-1',
        [`/transactions/${MONITOR_ADDRESS}`]: 'id-2'
    };
    let confirmed = false;
    let resolveRegistered;
    let resolveDone;
    const registered = new Promise(resolve => {
        resolveRegistered = resolve;
    });
    const done = new Promise(resolve => {
        resolveDone = resolve;
    });
    const onMessage = message => {
        const body = JSON.parse(message.body);
        const { destination } = message.headers;
        if (accountChannel === destination) {
            const { balance } = body.account;
            console.log(`Account update: balance=${balance}`);
            resolveRegistered();
            if (confirmed)
                resolveDone();
        } else if (destination.includes('/transactions/')) {
            const messageHash = body.meta.hash.data;
            console.log(
                `confirmed: hash=${messageHash.substring(0, 16)}...`);
            if (messageHash.toUpperCase() === transactionHash) {
                console.log(`Transaction ${shortHash}... confirmed`);
                confirmed = true;
            }
        } else {
            const messageHash = body.meta.hash.data;
            if (messageHash.toUpperCase() === transactionHash) {
                console.log(
                    'unconfirmed: hash=' +
                    `${messageHash.substring(0, 16)}...`);
            }
        }
    };
    for (const [channel, id] of Object.entries(channels)) {
        client.subscribe(channel, onMessage, { id });
        console.log(`Subscribed to ${channel} channel`);
    }

    // Register the account and confirm it is active
    client.publish({
        destination: '/w/api/account/get',
        body: JSON.stringify({ account: MONITOR_ADDRESS })
    });
    await registered;
    console.log('Account registered');

    // Announce the transaction and wait for it to confirm
    console.log(`Announcing transaction ${shortHash}...`);
    const response = await fetch(`${NODE_URL}/transaction/announce`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: jsonPayload
    });
    const announceResult = await response.json();
    if ('SUCCESS' === announceResult.message)
        await done;
    else
        console.log(`Transaction rejected: ${announceResult.message}`);

    // Unsubscribe before closing
    for (const id of Object.values(channels))
        client.unsubscribe(id);
    console.log('Unsubscribed from all channels');
    client.deactivate();
} catch (error) {
    console.error(error);
}

Download source

Note

There is no SockJS client library for Python, so a few small helper methods are defined at the top of the file for convenience.

The snippet uses the NODE_URL environment variable to set the NEM node. If no value is provided, a default one is used.

WS_URL defines the WebSocket endpoint for the same node. It is derived from NODE_URL by replacing port 7890, the default HTTP API port, with 7778, the default NIS WebSocket port.

Code Explanation⚓︎

Setting Up the Monitored Address and Signer⚓︎

# Set up the monitored address and signer
MONITOR_ADDRESS = os.getenv(
    'MONITOR_ADDRESS',
    'TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4'
)
print(f'Monitoring address: {MONITOR_ADDRESS}')

SIGNER_PRIVATE_KEY = os.getenv(
    'SIGNER_PRIVATE_KEY',
    '0000000000000000000000000000000000000000000000000000000000000000'
)
facade = NemFacade('testnet')
signer_key_pair = NemFacade.KeyPair(PrivateKey(SIGNER_PRIVATE_KEY))
// Set up the monitored address and signer
const MONITOR_ADDRESS = process.env.MONITOR_ADDRESS ||
    'TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4';
console.log(`Monitoring address: ${MONITOR_ADDRESS}`);

const SIGNER_PRIVATE_KEY = process.env.SIGNER_PRIVATE_KEY ||
    '0000000000000000000000000000000000000000000000000000000000000000';
const facade = new NemFacade('testnet');
const signerKeyPair = new NemFacade.KeyPair(
    new PrivateKey(SIGNER_PRIVATE_KEY));

This step sets up the address to monitor and the account that sends a transfer to it.

MONITOR_ADDRESS is the address to watch. The channels this tutorial subscribes to are scoped to this address and notify whenever it is involved in a transaction, for example as the sender or recipient of a transfer. The WebSocket API expects the address uppercase and without hyphens.

SIGNER_PRIVATE_KEY is the private key of the account that sends the transfer, which triggers the notifications.

If any of these environment variables is not provided, the tutorial provides default values.

Building and Signing a Transfer Transaction⚓︎

    # Build and sign a transfer to the monitored address
    with urllib.request.urlopen(
        f'{NODE_URL}/time-sync/network-time'
    ) as resp:
        network_time = json.loads(
            resp.read().decode())['receiveTimeStamp'] // 1000
    timestamp = NetworkTimestamp(network_time)
    deadline = timestamp.add_hours(2)
    transaction = facade.transaction_factory.create({
        'type': 'transfer_transaction_v2',
        'signer_public_key': signer_key_pair.public_key,
        'timestamp': timestamp.timestamp,
        'deadline': deadline.timestamp,
        'recipient_address': MONITOR_ADDRESS,
        'amount': 0,
    })
    transaction.fee = Amount(calculate_transaction_fee(transaction))
    signature = facade.sign_transaction(signer_key_pair, transaction)
    json_payload = facade.transaction_factory.attach_signature(
        transaction, signature)
    transaction_hash = str(
        facade.hash_transaction(transaction)).upper()
    // Build and sign a transfer to the monitored address
    const timeResponse = await fetch(
        `${NODE_URL}/time-sync/network-time`);
    const networkTime = Math.floor(
        (await timeResponse.json()).receiveTimeStamp / 1000);
    const timestamp = new NetworkTimestamp(networkTime);
    const deadline = timestamp.addHours(2);
    const transaction = facade.transactionFactory.create({
        type: 'transfer_transaction_v2',
        signerPublicKey: signerKeyPair.publicKey.toString(),
        timestamp: timestamp.timestamp,
        deadline: deadline.timestamp,
        recipientAddress: MONITOR_ADDRESS,
        amount: 0n
    });
    transaction.fee = new models.Amount(
        calculateTransactionFee(transaction));
    const signature = facade.signTransaction(signerKeyPair, transaction);
    const jsonPayload = facade.transactionFactory.static.attachSignature(
        transaction, signature);
    const transactionHash =
        facade.hashTransaction(transaction).toString().toUpperCase();
    const shortHash = transactionHash.substring(0, 16);

This tutorial builds a minimal Transfer transaction to the monitored address, with a zero amount, no mosaics, and no message. A transfer is used for simplicity, but any transaction type triggers the same WebSocket notifications.

The transaction is built the same way as in the Transfer XEM tutorial: fetching the network time, creating the transaction, and signing it.

Signing the transaction produces its hash, which uniquely identifies it. The code stores this hash because transaction channel notifications include the transaction hash. The message handler, defined later, compares each received hash with the stored value to identify notifications for this transaction.

The transaction is prepared, but it is not announced yet. The announcement happens after the channel subscriptions are established, so the notifications it triggers are not missed.

Connecting to the WebSocket⚓︎

    # Connect to the WebSocket
    endpoint = f'{WS_URL}/w/messages'
    async with connect(sockjs_url(endpoint)) as websocket:
        await stomp_connect(websocket)
        print(f'Connected to {WS_URL}')
    // Connect to the WebSocket
    const client = new Client({
        webSocketFactory: () => new SockJS(`${WS_URL}/w/messages`)
    });
    await new Promise(resolve => {
        client.onConnect = resolve;
        client.activate();
    });
    console.log(`Connected to ${WS_URL}`);

The code opens a SockJS connection to the /w/messages endpoint on WS_URL and starts a STOMP session over it.

Subscribing to the Channels⚓︎

        # Subscribe to the account and transaction channels
        account_channel = f'/account/{MONITOR_ADDRESS}'
        channels = {
            account_channel: 'id-0',
            f'/unconfirmed/{MONITOR_ADDRESS}': 'id-1',
            f'/transactions/{MONITOR_ADDRESS}': 'id-2',
        }
        for channel, sub_id in channels.items():
            await stomp_subscribe(websocket, channel, sub_id)
            print(f'Subscribed to {channel} channel')
    // Subscribe to the account and transaction channels
    const accountChannel = `/account/${MONITOR_ADDRESS}`;
    const channels = {
        [accountChannel]: 'id-0',
        [`/unconfirmed/${MONITOR_ADDRESS}`]: 'id-1',
        [`/transactions/${MONITOR_ADDRESS}`]: 'id-2'
    };
    let confirmed = false;
    let resolveRegistered;
    let resolveDone;
    const registered = new Promise(resolve => {
        resolveRegistered = resolve;
    });
    const done = new Promise(resolve => {
        resolveDone = resolve;
    });
    const onMessage = message => {
        const body = JSON.parse(message.body);
        const { destination } = message.headers;
        if (accountChannel === destination) {
            const { balance } = body.account;
            console.log(`Account update: balance=${balance}`);
            resolveRegistered();
            if (confirmed)
                resolveDone();
        } else if (destination.includes('/transactions/')) {
            const messageHash = body.meta.hash.data;
            console.log(
                `confirmed: hash=${messageHash.substring(0, 16)}...`);
            if (messageHash.toUpperCase() === transactionHash) {
                console.log(`Transaction ${shortHash}... confirmed`);
                confirmed = true;
            }
        } else {
            const messageHash = body.meta.hash.data;
            if (messageHash.toUpperCase() === transactionHash) {
                console.log(
                    'unconfirmed: hash=' +
                    `${messageHash.substring(0, 16)}...`);
            }
        }
    };
    for (const [channel, id] of Object.entries(channels)) {
        client.subscribe(channel, onMessage, { id });
        console.log(`Subscribed to ${channel} channel`);
    }

The code subscribes to three address-scoped channels:

The subscriptions use the IDs id-0, id-1 and id-2, which identify them when the code unsubscribes at the end.

All three channels stay silent until the address is registered, which the next step performs.

Registering the Account⚓︎

        # Register the account and confirm it is active
        await stomp_send(websocket, '/w/api/account/get',
            json.dumps({'account': MONITOR_ADDRESS}))
        async for frame in stomp_frames(websocket):
            if account_channel == frame['headers']['destination']:
                balance = json.loads(
                    frame['body'])['account']['balance']
                print(f'Account update: balance={balance}')
                break
        print('Account registered')
    // Register the account and confirm it is active
    client.publish({
        destination: '/w/api/account/get',
        body: JSON.stringify({ account: MONITOR_ADDRESS })
    });
    await registered;
    console.log('Account registered');

To receive notifications on an account's channels, the address must first be registered with the node.

The code sends a w/api/account/get REQ request, which registers the address and also forces the node to send the account's current state on the account/{address} WS channel.

The code waits for this first account notification, which confirms that the registration is active. The notification follows the AccountMetaDataPair schema.

The subscription to the account channel stays open for the rest of the run, so the account notification triggered by the transaction confirmation also appears in the output.

Announcing and Waiting for Confirmation⚓︎

        # Announce the transaction and wait for it to confirm
        print(f'Announcing transaction {transaction_hash[:16]}...')
        announce_request = urllib.request.Request(
            f'{NODE_URL}/transaction/announce',
            data=json_payload.encode(),
            headers={'Content-Type': 'application/json'},
            method='POST'
        )
        with urllib.request.urlopen(announce_request) as resp:
            result = json.loads(resp.read().decode())

        if 'SUCCESS' == result['message']:
            confirmed = False
            async for frame in stomp_frames(websocket):
                destination = frame['headers']['destination']
                body = json.loads(frame['body'])
                if account_channel == destination:
                    balance = body['account']['balance']
                    print(f'Account update: balance={balance}')
                    if confirmed:
                        break
                elif '/transactions/' in destination:
                    message_hash = body['meta']['hash']['data']
                    print(f'confirmed: hash={message_hash[:16]}...')
                    if message_hash.upper() == transaction_hash:
                        short_hash = transaction_hash[:16]
                        print(f'Transaction {short_hash}... confirmed')
                        confirmed = True
                else:
                    message_hash = body['meta']['hash']['data']
                    if message_hash.upper() == transaction_hash:
                        print(f'unconfirmed: hash={message_hash[:16]}...')
        else:
            print(f'Transaction rejected: {result["message"]}')
    // Announce the transaction and wait for it to confirm
    console.log(`Announcing transaction ${shortHash}...`);
    const response = await fetch(`${NODE_URL}/transaction/announce`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: jsonPayload
    });
    const announceResult = await response.json();
    if ('SUCCESS' === announceResult.message)
        await done;
    else
        console.log(`Transaction rejected: ${announceResult.message}`);

Announce after subscribing to channels

Always announce the transaction after subscribing to the WebSocket channels to ensure the listener is ready. Otherwise, notifications could arrive before the WebSocket is listening.

The code announces the transaction to the /transaction/announce POST endpoint and checks the result. If the node rejects it, the code prints the rejection reason and stops.

Otherwise, the code waits for confirmation, printing each message from the subscribed channels. Messages from the transaction channels follow the TransactionMetaDataPair schema, whose meta.hash.data field holds the transaction hash. As each one arrives, the message handler compares that hash against the stored value to recognize this transaction among the channel notifications.

The expected sequence for a successful transaction is described in the Transaction Lifecycle section:

  1. unconfirmed: The transaction enters the unconfirmed pool.
  2. confirmed: The transaction is included in a block.

The block that includes the transaction also triggers a final notification on the account/{address} WS channel. Unlike the transaction channels, this notification contains the account's updated state rather than a transaction hash, so it cannot be matched to a specific transaction.

Once this final notification arrives, the program moves on to the cleanup step.

Unsubscribing from Channels⚓︎

        # Unsubscribe before closing
        for sub_id in channels.values():
            await stomp_unsubscribe(websocket, sub_id)
        print('Unsubscribed from all channels')
        await stomp_disconnect(websocket)
    // Unsubscribe before closing
    for (const id of Object.values(channels))
        client.unsubscribe(id);
    console.log('Unsubscribed from all channels');
    client.deactivate();

After confirmation, the code unsubscribes from the three channels and ends the STOMP session before the connection closes.

Output⚓︎

Using node http://libertalia.nemtest.net:7890
Monitoring address: TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4
Connected to http://libertalia.nemtest.net:7778
Subscribed to /account/TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4 channel
Subscribed to /unconfirmed/TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4 channel
Subscribed to /transactions/TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4 channel
Account update: balance=10035200000
Account registered
Announcing transaction 2928C2D9554AE127...
unconfirmed: hash=2928c2d9554ae127...
confirmed: hash=2928c2d9554ae127...
Transaction 2928C2D9554AE127... confirmed
Account update: balance=10035200000
Unsubscribed from all channels

The output shows:

  • Address (line 2): The monitored address.
  • Connection (line 3): The STOMP session is established over the node's WebSocket endpoint at port 7778.
  • Subscriptions (lines 4-6): The account channel and both transaction channels are subscribed.
  • Registration (lines 7-8): The account's current state arrives on the account channel, confirming the registration.
  • Announcement (line 9): The transaction is announced and its hash is printed.
  • Transaction flow (lines 10-11): The transaction moves from unconfirmed to confirmed, showing the confirmation lifecycle.
  • Confirmation (line 12): The hash from the transactions/{address} WS channel matches the announced transaction.
  • Account update (line 13): The block containing the transaction triggers a final account notification. The balance is unchanged, since the transfer amount is zero.
  • Unsubscribe (line 14): The code unsubscribes from the three channels.

Conclusion⚓︎

This tutorial showed how to:

Step Related documentation
Subscribe to the account channel account/{address} WS
Subscribe to the unconfirmed channel unconfirmed/{address} WS
Subscribe to the transactions channel transactions/{address} WS
Register the account w/api/account/get REQ
Handle transaction messages TransactionMetaDataPair