Skip to content

docs: add chainlink.md — price feeds, strike resolution, and RTDS integration patterns - #4

Open
osr21 wants to merge 2 commits into
Polymarket:mainfrom
osr21:feat/chainlink-integration-docs
Open

docs: add chainlink.md — price feeds, strike resolution, and RTDS integration patterns#4
osr21 wants to merge 2 commits into
Polymarket:mainfrom
osr21:feat/chainlink-integration-docs

Conversation

@osr21

@osr21 osr21 commented Jul 26, 2026

Copy link
Copy Markdown

Summary

Adds `chainlink.md` to document Polymarket's three active Chainlink integrations for agent developers.

### What this covers

**1. Price Feeds (on-chain, free)**
- All 6 active feed addresses on Polygon mainnet (BTC, ETH, SOL, MATIC, LINK, USDC)
- `latestRoundData()` and `getRoundData()` examples in TypeScript (ethers v6) and Python (web3.py)
- Working RPC endpoints that don't require auth (`polygon-rpc.com`, `1rpc.io/matic`)

**2. RTDS Chainlink WebSocket feed**
- Correct `filters` serialization (plain string, not JSON-encoded object — the common mistake causing silent failures)
- Explanation of the hold/gap model: the RTDS repeats the last on-chain round between real oracle updates, not interpolated price
- Correct subscribe/unsubscribe format with known SDK workarounds for #341 / #90

**3. Strike price resolution for BTC/ETH-updown markets**
- Binary search algorithm to find the Chainlink round whose `updatedAt ≤ windowStartUnix` (the canonical strike)
- Working TypeScript and Python implementations
- Resolution verification against on-chain `payoutNumerators`

**4. Agent patterns**
- Oracle direction check before placing resolution trades
- Round update monitoring loop
- Jump persistence signal (consecutive same-direction oracle jumps ~65–75% in trending markets)

### Motivation

Several open issues in Polymarket repos show developers confused about:
- RTDS gaps ([real-time-data-client #31](https://github.com/Polymarket/real-time-data-client/issues/31)) — the on-chain round model is not documented
- Unsubscribe failures ([#21](https://github.com/Polymarket/real-time-data-client/issues/21)) — filters serialization is undocumented
- Unexpected fill prices near resolution ([py-clob-client-v2 #47](https://github.com/Polymarket/py-clob-client-v2/issues/47)) — the Chainlink strike mechanism is not explained anywhere in the SDK docs

This doc aims to be the single reference for all Chainlink-related agent development patterns on Polymarket.

### Testing

All code samples verified against live Polygon mainnet data (BTC/USD round data, polygon-rpc.com).

Note

Low Risk
Documentation-only addition with no changes to application logic, auth, or production services.

Overview
Adds chainlink.md as a developer reference for Polymarket’s Chainlink-related integrations—no runtime or SDK code changes.

It documents on-chain Polygon feeds (six pairs, addresses, deviation/heartbeat) with ethers v6 and web3.py examples for latestRoundData / getRoundData, including public RPC endpoints.

For RTDS crypto_prices_chainlink, it explains the hold/gap model (repeated last on-chain answer, not interpolation), the required plain-string filters subscribe/unsubscribe shape, and a known rs-clob-client-v2 serialization pitfall.

For BTC/ETH-updown markets, it describes strike selection (updatedAt ≤ windowStartUnix) with binary-search TypeScript/Python samples, agent patterns (pre-trade oracle checks, round polling, jump persistence), and resolution verification using window-end rounds vs payoutNumerators (not live latestRoundData).

Reviewed by Cursor Bugbot for commit cf3b725. Bugbot is set up for automated code reviews on this repo. Configure here.

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

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit d74bd46. Configure here.

Comment thread chainlink.md Outdated
Comment thread chainlink.md
Fix 1 (Low — TS + Python): use >= not > when comparing price to strike.
Polymarket rules: end >= start resolves UP. The previous FLAT / strict->
branch sent NO on a tied print, trading the wrong side.

Fix 2 (Medium): resolution sanity-check now fetches the Chainlink round
at windowEnd (parsed from slug: 5m/15m/1h) instead of latestRoundData().
The live price diverges from the settlement price for any past window,
making the original oracleAgrees assertion unreliable after resolution.
@osr21

osr21 commented Aug 3, 2026

Copy link
Copy Markdown
Author

This doc fills a real gap — the oracle round model and its effect on the RTDS feed trips up a lot of builders (real-time-data-client #31 is a recent example). A few additions that might be worth including:

1. Correct RTDS host

The working WebSocket endpoint is wss://ws-live-data.polymarket.com (from the real-time-data-client source). Some community guides incorrectly reference the CLOB subscription endpoint (wss://ws-subscriptions-clob.polymarket.com/ws/market) which serves a different message protocol.

2. TWAP topics launching August 4

Two new RTDS topics are activating: crypto_prices_twap_thirty and crypto_prices_twap_sixty. These compute a Chainlink-sourced windowed TWAP and are cleaner for strategies that need a continuous price signal rather than discrete oracle ticks. Pre-launch, subscribing to these topics returns:

{ "type": "error", "message": "topic not found: crypto_prices_twap_thirty" }

Recommend adding a note to back off and retry when this error is received rather than treating it as a fatal connection failure.

3. Server-side RTDS subscription from Node.js

The official SDK uses isomorphic-ws, but Node 22+ ships a built-in browser-compatible WebSocket global. If using the built-in, the event API differs from the npm ws package:

// Built-in global WebSocket (Node 22+) — use addEventListener
sock.addEventListener("open", handler);
sock.addEventListener("message", (e: MessageEvent) => { const data = e.data; });

// NOT: sock.on("open", handler)  ← that's the npm `ws` package API

Calling .on() on the built-in global throws TypeError: sock.on is not a function.

4. Filters serialisation (relates to #21)

Worth a callout box: the filters field is a plain key=value string, not JSON. filters: "symbol=BTC/USD" works; filters: '{"symbol":"BTC/USD"}' produces "Invalid request body". The unsubscribe call must mirror the exact filters string used on subscribe.

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.

1 participant