Skip to content

[mkdocs] docs: add listen tx flow tutorial#916

Open
zero4862 wants to merge 3 commits into
NemProject:new-docsfrom
zero4862:docs-listen-transaction-flow
Open

[mkdocs] docs: add listen tx flow tutorial#916
zero4862 wants to merge 3 commits into
NemProject:new-docsfrom
zero4862:docs-listen-transaction-flow

Conversation

@zero4862

@zero4862 zero4862 commented Jun 29, 2026

Copy link
Copy Markdown

Adapts Listen tx flow tutorial to NEM and documents missing websocket channels and requests.

Comment thread mkdocs/config/mkdocs.base.yml
: Notifies subscribed clients every time a new block is added to the chain.

=== "Request body"
=== "Frame"

@zero4862 zero4862 Jun 29, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed from "Request body" to "Frame" so that's not confused with the new "Requests" concept.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. But I'd call the tabs "Subscription frame" and "Notification body", so it's extra clear what they are.
I would even go as far as adding :material-arrow-up-bold: and :material-arrow-down-bold: (for example) at the beginning of the tab title so the direction of each message is explicit.


[TransactionMetaDataPair](../rest/nem.md#model/TransactionMetaDataPair)

#### `/account/mosaic/owned/{address}`

@zero4862 zero4862 Jun 29, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discovered some more websocket channels while working on this tutorial.

}
```

## Requests

@zero4862 zero4862 Jun 29, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a new section to document requests properly. Originally I thought the registration only existed for account channels to work, but it turns out there are a few more that push snapshots of current data to channels.

More details in https://github.com/QuantumMechanics/nem-lightwallet/tree/master/lightwallet#nis-websocket-channels-and-data

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice finding! And good idea to give it its own section.

Again, I would mention in the intro to this section (after the definition) that all requests are made via a SEND frame,
and the destination always begins with /w/api/ (so you can remove this detail from the definition).

It's also worth mentioning that some of these requests do not receive any answer in return, and some others get an answer through any of the channels above.

@zero4862 zero4862 force-pushed the docs-listen-transaction-flow branch from 209c9cd to 50f1500 Compare June 29, 2026 11:15
@zero4862 zero4862 force-pushed the docs-listen-transaction-flow branch from 50f1500 to b936694 Compare June 29, 2026 11:48
port.
The SockJS endpoint is `/w/messages`, for example: `http://localhost:7778/w/messages`.

## Session

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before it only covered connection + subscription, now the full cycle with "requests" taken into account.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what a "session" is.
Is it a STOMPS or SockJS concept? Or are you just describing a typical usage session?
You need to clarify this. Maybe rename the section "Typical Usage Session" ?

@zero4862 zero4862 marked this pull request as ready for review June 29, 2026 12:45
@zero4862 zero4862 requested a review from segfaultxavi June 29, 2026 12:46

@segfaultxavi segfaultxavi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have only reviewed the WebSocket reference page.
Apologies for spending so much time reviewing such a obscure corner of the docs, but I don't like the shape it has on Symbol and decided to fix it in this review.

I'll continue now to review the rest of the PR, but wanted to submit this early.

Comment on lines 3 to 9
To get **live updates** when an event occurs on the blockchain, NEM publishes
[WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API).

Client applications can open a WebSocket connection and subscribe to any of the available channels instead of needing
to constantly poll the [REST API](../rest/nem.md) for updates.

When an event occurs in a channel, the <node:> sends a notification to every subscribed client in real-time.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
NEM publishes blockchain events over
[WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API), so applications can receive live updates
without constantly polling the [REST API](../rest/nem.md).
Client applications open a WebSocket connection to any <node:> in the network and subscribe to the [channels](#channels)
they want to monitor.
When an event occurs on a channel, the node notifies every subscribed client in real time.
Some channels also accept [requests](#requests) for immediate data, similar to the REST API.
This can simplify applications that use WebSockets as their only API for both live notifications and on-demand updates.

In this way:

  • We establish the terminology we are going to use: clients SUBSCRIBE and REQUEST, nodes NOTIFY. I don't like "push" or "send" because they work in both directions so they can be ambiguous.
  • We introduce requests early on so they are not a surprise later, and we state their purpose.
  • We introduce the node upfront, otherwise, you mention "the node" and it's not clear which node is that.

port.
The SockJS endpoint is `/w/messages`, for example: `http://localhost:7778/w/messages`.

## Session

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what a "session" is.
Is it a STOMPS or SockJS concept? Or are you just describing a typical usage session?
You need to clarify this. Maybe rename the section "Typical Usage Session" ?

## Session

The SockJS endpoint is `/w/messages`, for example `http://localhost:7778/w/messages`.
A client that does not use SockJS can open a plain WebSocket to `/w/messages/websocket` directly.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't just throw the non-SockJS API here without any more information and never mention it again :D
Also, putting it in the section intro, makes it look like it is an important part of the process.

I would move all mention of the non-SockJS SPI to an info box at the end of this section AND provide more information.
So if SockJS is not used, what is the protocol? What changes? Is there anywhere the user can go to know more if they are interested in this topic?


After opening the SockJS connection, the client drives the session with STOMP frames.
A _frame_ is a plain-text message made of a command, optional `header:value` lines, and an optional body.
Frame

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This glossary term is global, so we risk collisions if we use too generic words.
Since this is a pretty niche usage, I would call it "STOMP Frame" instead.

The section title should probably be called "STOMP Frames" too.

Same problem with "Channel" and "Request" below, although those are probably related to WebSocket or SockJS, not STOMP.

Comment on lines +25 to +31
!!! note "Some channels require registration"

Some channels, like [account channels](#account-channels), deliver nothing until they receive a registration
request.
A client must send this request before subscribing to the channels it enables.

See [registration requests](#registration-requests) for the full list.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of making this an info box, which interrupts the flow of the session, I would make it an optional step:

Suggested change
!!! note "Some channels require registration"
Some channels, like [account channels](#account-channels), deliver nothing until they receive a registration
request.
A client must send this request before subscribing to the channels it enables.
See [registration requests](#registration-requests) for the full list.
3. Send an optional [registration request](#registration-requests) to receive notifications for channels which
do not send them automatically.

Also, we avoid "nothing", which is too strong a word.

Comment on lines +369 to +372
Request
: A message the client sends to a `/w/api/...` destination to make the node act once.
It either **registers** (a prerequisite some channels require before they deliver) or pushes a **snapshot** of
current data to a channel.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Request
: A message the client sends to a `/w/api/...` destination to make the node act once.
It either **registers** (a prerequisite some channels require before they deliver) or pushes a **snapshot** of
current data to a channel.
Request
: A message sent by the client to make the node deliver:
either notifications on channels that require **registration**,
or an immediate response containing a **snapshot** of the state of the blockchain.

I think it reads more clearly, but we can iterate.

Comment on lines +384 to +386
req:w&#47;api&#47;account&#47;subscribe
: Registers the address so that [account channels](#account-channels) begin delivering events.
It pushes nothing back to any channel.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
req:w&#47;api&#47;account&#47;subscribe
: Registers the address so that [account channels](#account-channels) begin delivering events.
It pushes nothing back to any channel.
req:w&#47;api&#47;account&#47;subscribe
: Makes the node start sending notifications about the selected [account channel](#account-channels).

Every request is **read-only**.
It never changes the chain, only registers interest in receiving events or replays existing data.

### Registration requests

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add a short intro explaining why these are needed.

Instead of "SEND frame" I would title the code blocks ":material-arrow-up-bold: Registration frame".
Maybe even use the same table layout as above (if we decide to use it), for consistency, even when it would only have one column here.

#### `/w/api/account/get`

req:w&#47;api&#47;account&#47;get
: Registers the address and pushes its current state to <ws:account&#47;{address}>.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this exactly the same as the previous one, but triggering an immediate notification?
If so, I would explain it in this way.
Always make it explicit who is sending each message and avoid using "push" on its own.

{ "account": "{address}" }
```

### Snapshot requests

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, short intro explaining why these are needed.

The definitions of all requests are a bit confusing. I would begin them with something like:
"This request forces the node to send an immediate notification containing..."

Instead of "SEND frame" I would title the code blocks ":material-arrow-up-bold: Request frame".
Maybe even use the same table layout as above (if we decide to use it), for consistency, even when it would only have one column here.

@segfaultxavi segfaultxavi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments for the tutorial proper now.


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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand this change simplifies creating URLs for the WebSocket tutorials, but I think this forces us to change ALL other tutorials to follow the same pattern.
Otherwise, different tutorials use different environment variables and it's a bit messy.

Do you want to apply this pattern to the rest of tutorials, including Symbol? :)

Comment on lines +16 to +17
NODE_URL = f'http://{NODE_HOST}:7890'
WS_URL = f'http://{NODE_HOST}:7778'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
NODE_URL = f'http://{NODE_HOST}:7890'
WS_URL = f'http://{NODE_HOST}:7778'
NODE_REST_URL = f'http://{NODE_HOST}:7890'
NODE_WS_URL = f'http://{NODE_HOST}:7778'

For consistency.

print(f'Connected to {WS_URL}')
# [<step-2]
# Register the account and confirm it is active [>step-3]
account = f'/account/{MONITOR_ADDRESS}'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
account = f'/account/{MONITOR_ADDRESS}'
destination = f'/account/{MONITOR_ADDRESS}'

Because this is the meaning of this field for the SUBSCRIBE frame
(and this is how you called it below).

await stomp_subscribe(websocket, account, 'id-0')
await stomp_send(websocket, '/w/api/account/get',
json.dumps({'account': MONITOR_ADDRESS}))
async for raw in websocket:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe raw_frame?

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

The code registers the address by sending a <req:w&#47;api&#47;account&#47;get> request, which both registers it and
replies with the account's current state on the <ws:account&#47;{address}> channel.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
replies with the account's current state on the <ws:account&#47;{address}> channel.
triggers a reply containing the account's current state on the <ws:account&#47;{address}> channel.

print(f'{name}: hash={message_hash[:16]}...')
is_match = message_hash.upper() == expected_hash
if name == 'confirmed' and is_match:
short = transaction_hash[:16]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

short_hash for clarity and consistency with JS.

Comment on lines +99 to +100
To catch that reply, the code subscribes to the channel using `id-0`, then waits.
Once the reply arrives, the registration is active and the temporary subscription is dropped.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
To catch that reply, the code subscribes to the channel using `id-0`, then waits.
Once the reply arrives, the registration is active and the temporary subscription is dropped.
To verify that the subscription is successful, the code first temporarily subscribes to the
<ws:account&#47;{address}> channel, sends the registration request, and waits for that reply.
Once the reply arrives, the registration is active and the temporary subscription to the
general account messages channel is dropped, using the same `id-0` used during registration.


{{ tutorial.code_snippet_tagged('step-4') }}

With the account registered, the code subscribes to two address-scoped channels:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
With the account registered, the code subscribes to two address-scoped channels:
With the account's address registered, the code subscribes to two address-scoped channels:

So the connection between the account and the channels below is extra clear.


{{ tutorial.code_snippet_tagged('step-5') }}

This tutorial builds a minimal [Transfer Transaction](../transactions/transfer-xem.md) to the monitored address, with a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would make this link point to the Textbook, and then, below, instead of "as usual", I would say "following the same procedure as in the Transfer Transaction Tutorial", and include the tutorial link there.

Each message follows the [TransactionMetaDataPair](../reference/rest/nem.md#model/TransactionMetaDataPair) schema, whose
`meta.hash.data` field holds the transaction hash.

When a message from the `/transactions/{address}` channel arrives whose hash matches the announced transaction, the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
When a message from the `/transactions/{address}` channel arrives whose hash matches the announced transaction, the
When a message from the <ws:transactions&#47;{address}> channel arrives whose hash matches the announced transaction, the

Same in another place below.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants