Skip to content

Toncenter Streaming API v2 clients - #188

Open
ArkadiyStena wants to merge 7 commits into
ton-org:mainfrom
ArkadiyStena:dev/viqex/streaming-clients
Open

Toncenter Streaming API v2 clients#188
ArkadiyStena wants to merge 7 commits into
ton-org:mainfrom
ArkadiyStena:dev/viqex/streaming-clients

Conversation

@ArkadiyStena

@ArkadiyStena ArkadiyStena commented Apr 15, 2026

Copy link
Copy Markdown

Summary

This PR adds streaming clients for @ton/ton with support for Toncenter Streaming API v2 over both SSE and WebSocket.

The implementation covers:

  • address-based subscriptions
  • trace-based subscriptions
  • explicit finality handling (pending / confirmed / finalized)
  • streaming of transactions, actions, trace, account_state_change, jettons_change, and trace_invalidated
  • connection lifecycle management, graceful shutdown, and transport-specific error handling

What’s included

New streaming clients

  • TonSseClient
  • TonWsClient

Transport support

  • SSE transport with a single subscription per stream
  • WebSocket transport with dynamic subscribe / unsubscribe / ping operations

Subscription handling

  • validation and normalization of subscription params
  • support for address subscriptions
  • support for trace subscriptions via trace_external_hash_norms
  • support for min_finality, include_address_book, include_metadata, action_types, and supported_action_types

Event parsing and typing

  • typed streaming event surface for all supported notification kinds
  • protocol-level parsing and validation of incoming messages
  • handling of both control messages and data notifications
  • dedicated error types for transport / protocol / timeout / close scenarios

Lifecycle and reliability

  • graceful close() behavior
  • request/response correlation for WebSocket operations
  • ping/pong support for WebSocket health checks
  • protection against stale connection state during reconnect / shutdown flows

Design notes

The implementation follows the semantics of Streaming API v2:

  • SSE uses a single POST request with an immutable subscription for the lifetime of the stream.
  • WebSocket subscribe replaces the current subscription snapshot for the connection.
  • WebSocket unsubscribe removes specific addresses or trace hashes.
  • Trace-based events may be delivered multiple times as finality increases.
  • trace_invalidated is handled as a first-class event.

The parser and event model were also checked against real SSE output from live wallet e2e runs to make sure the client matches real payload shapes rather than only the nominal protocol description.

Compatibility

Supported provider presets cover:

  • Toncenter mainnet / testnet
  • TonAPI mainnet / testnet

The implementation also supports custom endpoints and auth parameter configuration where needed.

Summary by CodeRabbit

  • New Features

    • Added real-time streaming client supporting WebSocket and SSE transports.
    • Enabled subscriptions to blockchain events: transactions, actions, account state changes, jetton changes, and trace events.
  • Tests

    • Added live integration tests for streaming transactions and wallet end-to-end scenarios.
  • Chores

    • Added npm script for running live streaming tests.

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces complete streaming client functionality for the TON blockchain SDK, enabling real-time event subscriptions via WebSocket and SSE transports. It includes abstract lifecycle management with reconciliation, protocol-level message parsing, two transport implementations, comprehensive type definitions, and live tests validating functionality against mainnet and testnet.

Changes

Streaming Client Infrastructure

Layer / File(s) Summary
Error & Type Contracts
src/client/streaming/errors.ts, src/client/streaming/types.ts
Streaming error hierarchy with context tracking and complete event/subscription/parameter type surface including transactions, actions, traces, account state, and jetton events.
Utilities & Event Infrastructure
src/client/streaming/utils.ts, src/client/streaming/TypedEventEmitter.ts
Deferred promises, type-safe event emitter, finality/service constants, URL builders, validation helpers, and abort-error detection.
Subscription State Management
src/client/streaming/subscriptionState.ts
Resolves, normalizes, compares, and serializes streaming subscription requests with validation of addresses, event types, finality levels, and conditional dependencies.
Protocol Parsing & Validation
src/client/streaming/protocol.ts
Runtime parsing of untyped JSON into strongly-typed streaming events with field validation and error handling for all six event types.
Abstract Client Lifecycle
src/client/streaming/AbstractStreamingClient.ts
Orchestrates subscribe/close lifecycle with private reconciliation loop, supersession handling, transport abstraction, and error context.
SSE Incremental Parser
src/client/streaming/SseParser.ts
Buffers and parses Server-Sent Events from chunked streams, handles CRLF/CR normalization, enforces 4 MB buffer limit, and dispatches parsed events via callback.
SSE Transport Implementation
src/client/streaming/TonSseClient.ts
SSE-based streaming client posting subscription snapshots, reading streamed events, differentiating normal vs. premature closure, and emitting lifecycle errors.
WebSocket Transport Implementation
src/client/streaming/TonWsClient.ts
WebSocket streaming with request/response correlation via request IDs, heartbeat ping loop, timeout-driven cleanup, binary/string message parsing, and connection state machine.
Public API & Exports
src/client/streaming/index.ts, src/index.ts
Re-exports streaming clients, error types, and type definitions making them part of the library's public surface.
Tests & Configuration
package.json, src/client/streaming/Streaming.transactions.live.spec.ts, src/client/streaming/Streaming.wallet.e2e.live.spec.ts
Adds npm test script and live tests validating transaction watching across sources and end-to-end wallet operations including TON and jetton transfers.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Hop along the streaming chain,
Events flow like gentle rain—
WebSocket winds and SSE breeze,
Reconcile each subscription with ease!
Live tests hop from blockchain to screen,
The finest streaming seen!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the primary change: adding streaming API v2 clients (TonSseClient and TonWsClient) for the Toncenter/TON ecosystem.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ArkadiyStena
ArkadiyStena marked this pull request as ready for review May 8, 2026 14:15

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/index.ts (1)

31-38: 💤 Low value

Consider exporting StreamingSupersededError for completeness.

StreamingSupersededError is exported from ./client/streaming/index.ts but not re-exported here. If consumers might need to distinguish superseded subscriptions from other errors (e.g., for retry logic), consider adding it to the public API.

Suggested addition
 export {
     TonWsClient,
     TonSseClient,
     StreamingClosedError,
     StreamingError,
     StreamingHandshakeError,
     StreamingRequestTimeoutError,
+    StreamingSupersededError,
 } from "./client/streaming";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/index.ts` around lines 31 - 38, The public re-exports omit
StreamingSupersededError; add it to the exported symbols alongside TonWsClient,
TonSseClient, StreamingClosedError, StreamingError, StreamingHandshakeError, and
StreamingRequestTimeoutError so consumers can detect superseded-subscription
errors; update the export list that currently references "./client/streaming" to
include StreamingSupersededError.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/client/streaming/protocol.ts`:
- Around line 100-107: The optional-field checks treat only undefined as
"absent" and throw when JSON uses null; update castOptionalJsonObjectRecord<T>
to treat null the same as undefined (e.g., check value == null or value === null
|| value === undefined) and likewise change the guards in parseTraceEvent for
optional fields like actions (and any similar checks for address_book/metadata)
to consider null as absent so those fields are ignored instead of passed to
expectRecord/expectArray.

In `@src/client/streaming/Streaming.transactions.live.spec.ts`:
- Around line 143-149: The failing formatter is caused by the formatting of the
process.stdout.write template literal inside the target.on("transactions")
handler; update the formatting of that call (the process.stdout.write(...) line)
so it matches the project's style (or simply run the project's formatter, e.g.,
npm run format) and ensure the surrounding handler (target.on("transactions",
...), the first flag usage, and events.push({ source }) remain unchanged.

In `@src/client/streaming/Streaming.wallet.e2e.live.spec.ts`:
- Around line 287-290: The Promise.all call formatting causes CI failure;
reformat the array containing addressStream and traceStream to match project
style (likely collapse into a single-line Promise.all([addressStream.close(),
traceStream.close()]) or run the project's formatter). Locate the
Promise.all(...) in the test file where addressStream and traceStream are closed
and either manually adjust to the project's preferred single-line style or run
the project's formatter (e.g., npm run format) to apply the correct formatting.

---

Nitpick comments:
In `@src/index.ts`:
- Around line 31-38: The public re-exports omit StreamingSupersededError; add it
to the exported symbols alongside TonWsClient, TonSseClient,
StreamingClosedError, StreamingError, StreamingHandshakeError, and
StreamingRequestTimeoutError so consumers can detect superseded-subscription
errors; update the export list that currently references "./client/streaming" to
include StreamingSupersededError.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4585cce9-807f-4c50-97e7-098051821a60

📥 Commits

Reviewing files that changed from the base of the PR and between 4e60c0b and 491c7b3.

📒 Files selected for processing (15)
  • package.json
  • src/client/streaming/AbstractStreamingClient.ts
  • src/client/streaming/SseParser.ts
  • src/client/streaming/Streaming.transactions.live.spec.ts
  • src/client/streaming/Streaming.wallet.e2e.live.spec.ts
  • src/client/streaming/TonSseClient.ts
  • src/client/streaming/TonWsClient.ts
  • src/client/streaming/TypedEventEmitter.ts
  • src/client/streaming/errors.ts
  • src/client/streaming/index.ts
  • src/client/streaming/protocol.ts
  • src/client/streaming/subscriptionState.ts
  • src/client/streaming/types.ts
  • src/client/streaming/utils.ts
  • src/index.ts

Comment on lines +100 to +107
function castOptionalJsonObjectRecord<T extends JsonObject>(
value: unknown,
fieldName: string,
): Record<string, T> | undefined {
return value === undefined
? undefined
: castJsonObjectRecord<T>(value, fieldName);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

null values for optional payload fields throw instead of being treated as absent

castOptionalJsonObjectRecord guards with === undefined (Line 104), and parseTraceEvent guards actions with === undefined (Line 185). JSON APIs routinely send "field": null for absent optional fields. Receiving null for address_book, metadata, or actions falls through to expectRecord/expectArray, which correctly rejects null as a non-array/non-object and throws a parse error, surfacing a confusing protocol-level failure to callers instead of silently treating the field as missing.

🐛 Proposed fix — treat `null` identically to absent (`undefined`)
 function castOptionalJsonObjectRecord<T extends JsonObject>(
     value: unknown,
     fieldName: string,
 ): Record<string, T> | undefined {
-    return value === undefined
+    return value == null
         ? undefined
         : castJsonObjectRecord<T>(value, fieldName);
 }
         actions:
-            payload.actions === undefined
+            payload.actions == null
                 ? undefined
                 : castJsonObjectArray<StreamingAction>(
                       payload.actions,
                       "trace.actions",
                   ),

Also applies to: 184-190

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/streaming/protocol.ts` around lines 100 - 107, The optional-field
checks treat only undefined as "absent" and throw when JSON uses null; update
castOptionalJsonObjectRecord<T> to treat null the same as undefined (e.g., check
value == null or value === null || value === undefined) and likewise change the
guards in parseTraceEvent for optional fields like actions (and any similar
checks for address_book/metadata) to consider null as absent so those fields are
ignored instead of passed to expectRecord/expectArray.

Comment on lines +143 to +149
target.on("transactions", () => {
if (first) {
first = false;
process.stdout.write(` ✓ ${source}: first transaction received\n`);
}
events.push({ source });
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix formatting to resolve CI failure.

The pipeline indicates a formatting check failure on lines 146-148. The process.stdout.write call with the template literal needs reformatting to satisfy the project's formatter.

Suggested fix

Run the project's formatter (e.g., npm run format) to automatically fix the line formatting for the process.stdout.write call.

🧰 Tools
🪛 GitHub Actions: Check formatting / 0_test.txt

[error] 146-148: Formatter (format:check) failed for this file. Process.stdout.write template call would be reformatted (would split into multi-line call).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/streaming/Streaming.transactions.live.spec.ts` around lines 143 -
149, The failing formatter is caused by the formatting of the
process.stdout.write template literal inside the target.on("transactions")
handler; update the formatting of that call (the process.stdout.write(...) line)
so it matches the project's style (or simply run the project's formatter, e.g.,
npm run format) and ensure the surrounding handler (target.on("transactions",
...), the first flag usage, and events.push({ source }) remain unchanged.

Comment on lines +287 to +290
await Promise.all([
addressStream.close(),
traceStream.close(),
]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix formatting to resolve CI failure.

The pipeline indicates a formatting check failure. The Promise.all array should be formatted according to the project's style (likely single-line).

Suggested fix

Run the project's formatter (e.g., npm run format) to automatically fix the Promise.all array formatting.

🧰 Tools
🪛 GitHub Actions: Check formatting / 0_test.txt

[error] 287-290: Formatter (format:check) failed for this file. Promise.all array formatting would be changed from multi-line to single-line.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/streaming/Streaming.wallet.e2e.live.spec.ts` around lines 287 -
290, The Promise.all call formatting causes CI failure; reformat the array
containing addressStream and traceStream to match project style (likely collapse
into a single-line Promise.all([addressStream.close(), traceStream.close()]) or
run the project's formatter). Locate the Promise.all(...) in the test file where
addressStream and traceStream are closed and either manually adjust to the
project's preferred single-line style or run the project's formatter (e.g., npm
run format) to apply the correct formatting.

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