Skip to content

feat(client): replace RTDS with PolyBolt-only price streams - #351

Draft
kartojal wants to merge 5 commits into
mainfrom
feat/realtime-polybolt
Draft

feat(client): replace RTDS with PolyBolt-only price streams#351
kartojal wants to merge 5 commits into
mainfrom
feat/realtime-polybolt

Conversation

@kartojal

@kartojal kartojal commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Replace RTDS completely with authenticated PolyBolt price streams, including the production default. Remove the legacy transport, source-named aliases, comments/Chainlink spot streams, and rtds configuration; use secure clients, source-neutral topics, and realtime endpoints. Production deployment is still pending.

Validation: build, lint, typecheck, 497 unit/type tests, built-package consumer checks, and 10 staging integration tests pass. Broader routing checks remain blocked by staging market-discovery HTTP 500. Live 30-second TWAP/BBO coverage remains unverified.

@kartojal kartojal changed the title feat(client): add opt-in authenticated realtime price streams feat(client): add PolyBolt support, opt-in authenticated realtime price streams Sep 9, 2026
BREAKING CHANGE: remove legacy topics, manager aliases and rtds endpoints. All price subscriptions require authenticated clients and explicit filters.
@kartojal kartojal changed the title feat(client): add PolyBolt support, opt-in authenticated realtime price streams feat(client): replace RTDS with PolyBolt-only price streams Sep 9, 2026

@samsondav samsondav 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.

Upfront: my agent ran this review; flag anything that reads off and I'll take a personal look.

This pull request ships the customer subscribe path for authenticated price streams (prices.crypto, prices.crypto.twap, prices.equity, prices.polymarket). The production host flip is still deferred. I request changes for two hangs on the default price socket.

Requested changes

  1. One failed control frame ends every listener on the shared socket.
    #flush calls #fail on any SubscriptionRejectedError or TransportError, including the 10-second ack timeout. #fail rejects every key, ends every listener, then closes the connection. A bad_filter or a single timeout then kills sibling streams that the server kept. Scope the reject to the batch keys. Treat ack timeout and send failure as a restart.

  2. subscribe() never settles when the first connect fails.
    add() does void this.#connect().catch(() => this.#schedule(1006)). Retries continue while keys remain. state.ready is never rejected. Callers that await subscribe() hang and cannot close a handle they never received. Reject never-subscribed keys after the first failed connect, or after a deadline.

A related hang: after the last remove, a drop in the 1-second idle window leaves #started === true and #authenticated === false. The next add() does not call #connect().

Scope

The changeset documents that wss://ws-live-v2.polymarket.com/ws is not deployed yet. A merge to main publishes through Release and canary. Hold the production default, or hold the merge, until that host is live. This is sequencing, not a logic defect in the client.

keyTarget only halves on dropped and never recovers. That is a capacity ratchet, not a wrong price.

Notes

  • The branch added legacy.ts and then deleted it. That matches the RTDS deletion.
  • No prior review threads.
  • The changeset tells consumers to remove protocol, rtdsLegacy, and includeSnapshot. Those names were not on the prior public surface.
  • The example script requires POLYMARKET_INTEGRATION_ENVIRONMENT_CONFIG, so it does not run for an external user on the default production object.

else if (error instanceof Error) {
this.#fail(error);
}
}

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.

Both catch arms call #fail, which rejects every key on this socket, ends every listener, and closes the connection. A bad_filter or a 10-second ack timeout then kills sibling streams that the server kept. Scope SubscriptionRejectedError to the batch keys. Treat ack timeout and send failure as a restart, not a permanent fail-all.

if (state.snapshot !== undefined) listener.event(state.snapshot);
if (!this.#started) {
this.#started = true;
void this.#connect().catch(() => this.#schedule(1006));

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.

A failed first #connect() only schedules reconnect. Nothing rejects state.ready. subscribe() therefore hangs on DNS failure, a bad URL, or an unreachable host, and the caller never gets a handle to close. Reject never-subscribed keys after the first failed connect (or a deadline), and keep reconnect for keys the server already accepted.

if (!this.#started) {
this.#started = true;
void this.#connect().catch(() => this.#schedule(1006));
}

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.

add() reconnects only when #started is false. After the last remove, a drop in the 1-second idle window skips #schedule (#keys.size === 0) and leaves #started === true. The next add() then returns state.ready without calling #connect(). Connect when the socket is not authenticated and not already connecting, rather than only on the first add().

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