Skip to main content
PN1 lets a client verify that its local bid/ask depth matches the depth delivered by PolyNode. It adds a per-token sequence number and checksum to the existing Orderbook Stream without changing the default stream. Enable it with one flag:
pn1 means PolyNode orderbook integrity protocol, version 1. You only send integrity=1; the protocol name appears in server messages so the checksum bytes are unambiguous.
PN1 verifies the continuity and resulting state of PolyNode-delivered depth. It is not a Polymarket sequence number, does not call Polymarket while processing a frame, and is not a cryptographic signature. PolyNode’s separate upstream accuracy monitoring remains out of band and adds no stream latency.

The client rule

For each token:
  1. Accept a message with anchor: true as a new baseline. Replace the entire local book, compute PN1, compare the checksum, and store seq.
  2. For the next depth update, require prev_seq to equal the stored sequence.
  3. Apply every changed level. A size of "0" removes that price; every other size is the new absolute size.
  4. Recompute PN1 and require it to equal the message checksum.
  5. Store seq only after both checks pass.
  6. On any failure, stop using that token and request a new anchor.
The server gates that token while it creates the ordered replacement anchor. It replies with resyncing, a book_snapshot anchor, then snapshots_done with "reason":"resync". If one is already in flight, resync_in_progress tells you to keep the token gated and retry after retry_after_ms. If an acknowledged anchor cannot complete, the server fails closed with integrity_backpressure or integrity_internal_reset and closes the socket; reconnect for fresh anchors.

Wire format

The subscribe acknowledgement is queued before any anchors:
Every subscribed token receives a standalone snapshot, including an empty book:
Depth mutations carry the post-update checksum and consecutive sequence:
In integrity mode, each depth-mutating price_change is token-scoped: it has a top-level asset_id and exactly one row in assets. This keeps verification identical for every depth frame. A non-depth price_change has no integrity object. last_trade_price never changes depth, never advances seq, and has no checksum. The normal stream (integrity omitted or integrity=0) keeps its existing payloads. Integrity mode does not support markets:["*"]; pass an explicit list of up to 500 resolved token IDs. Existing account limits still apply if they are lower. The process-wide launch limit is 5,000 unique PN1 tokens.

PN1 checksum

PN1 stores one canonical size for each (side, price) level. B means bid and A means ask. Canonical decimal rules:
  • Accept only a non-negative base-10 string with ASCII digits and at most one decimal point.
  • Add a missing integer zero, remove leading integer zeroes, and remove trailing fractional zeroes.
  • Remove an empty fraction and decimal point. Every representation of zero becomes 0.
  • A canonical size of 0 removes the level.
Examples: .50, 0.500, and 000.5000 are all 0.5; 01.000 is 1. For every active level:
XOR all 32-byte level hashes together. The published checksum is:
Rust is not part of the client contract. These bytes can be produced in any language with SHA-256, UTF-8/ASCII byte encoding, and byte-array XOR. The fixed vectors below let a Go, Java, C#, C++, or other implementation confirm that it produces the same result before connecting. The simplest client can recompute from its complete local book after every update in O(total depth). The incremental accumulator shown below produces the identical checksum in O(changed levels). The full-recompute functions accept the same [{"price":"...","size":"..."}] bid and ask arrays as a snapshot. If your local book uses maps, convert each map to those rows before calling the function. On a live frame, compute the checksum after applying that frame and compare it to integrity.checksum. No REST request, Rust process, SDK, or server-side helper is involved on the client.

Copyable checksum implementations

Choose the language your orderbook client already uses. Node.js and Python include both full-recompute and incremental forms; Go and Rust show the shortest full-recompute form. Every block is executable and contains the same fixed PN1 vectors.
This implementation uses only the Node.js standard library.
This implementation uses only the Python standard library.
Go’s standard library supplies SHA-256. This version favors clarity and recomputes from the complete local book.
Rust’s standard library does not include SHA-256, so add sha2 = "0.10" to Cargo.toml. No PolyNode SDK is required.

Applying frames

Use one PN1Book and stored sequence per token, per connection. This block uses the Node.js PN1Book above and handles standalone anchors, batched updates, targeted resync, terminal errors, and optional compress=zlib frames:
Keep your normal WebSocket close handler as the final reconnect trigger. A new connection and subscription establish new anchors; never reuse sequence state across sockets.
Never keep applying a token after a sequence or checksum failure. Gate it immediately and wait for a valid replacement anchor. If the server reports integrity_backpressure or integrity_internal_reset, reconnect; the new connection will establish fresh anchors.