Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .changeset/realtime-price-streams.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'@polymarket/client': minor
'@polymarket/bindings': minor
---

Breaking migration: replace RTDS completely with authenticated PolyBolt price streams. The production endpoint is now `wss://ws-live-v2.polymarket.com/ws`, with no legacy fallback. Production price subscriptions require that endpoint to be deployed; use a staging environment fork while deployment is pending.

Migration:

- Use a secure client for all price subscriptions and provide explicit, nonempty symbol or asset filters.
- Replace `prices.crypto.binance` with `prices.crypto`, `prices.crypto.chainlink.twap` with `prices.crypto.twap`, and `prices.equity.pyth` with `prices.equity`. Feed symbols depend on the deployed source; there is no implicit symbol set.
- Use `webSockets.realtime` and `RealtimeWebSocketManager`. Remove `webSockets.rtds`, `RtdsWebSocketManager`, and `RtdsWebSocketManagerOptions` references.
- Move environment overrides from `rtds` to `realtime: { ws, headers? }`. Remove `protocol` and `rtdsLegacy`; there is only one price transport.
- Remove `comments` and `prices.crypto.chainlink` subscriptions: these streams are unsupported. HTTP comments APIs remain available.
- Replace legacy comment/reaction event, `CryptoPrices*`, `EquityPrices*`, and `RealtimeEvent` binding imports with the source-neutral price event/spec types. The legacy RTDS schemas and price payload types are removed.
- Handle both `subscribe` history snapshots and `update` events for crypto, TWAP, and equity. Remove `includeSnapshot`; snapshots are included by default. Use `CryptoPriceEvent`, `CryptoTwapPriceEvent`, and `EquityPriceEvent` with the corresponding subscription types.

Add secure `prices.polymarket` best-bid-and-offer updates with `PolymarketPriceEvent` and `PolymarketPriceSubscription`. Price values preserve exact decimal precision, timestamps represent producer time, and events expose optional `seq` and `dropped` fields. Subscriptions await server acceptance and can throw `SubscriptionRejectedError` or `ConnectionLostError`. Connections authenticate on reconnect, pool filters beyond 64 keys, and refresh history after reported drops.
22 changes: 22 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@ name: Integration Tests
on:
workflow_call:
inputs:
environment_config:
description: JSON environment fork for integration endpoints
required: false
default: '{}'
type: string
realtime_soak:
description: Run the 30-minute realtime price soak
required: false
default: false
type: boolean
test_file_pattern:
description: Optional integration test file pattern
required: false
Expand All @@ -15,6 +25,16 @@ on:
type: string
workflow_dispatch:
inputs:
environment_config:
description: JSON environment fork for integration endpoints
required: false
default: '{}'
type: string
realtime_soak:
description: Run the 30-minute realtime price soak
required: false
default: false
type: boolean
test_file_pattern:
description: Optional integration test file pattern
required: false
Expand Down Expand Up @@ -61,6 +81,8 @@ jobs:
pnpm test:client:integration
fi
env:
POLYMARKET_INTEGRATION_ENVIRONMENT_CONFIG: ${{ inputs.environment_config }}
POLYMARKET_REALTIME_SOAK: ${{ inputs.realtime_soak && '1' || '0' }}
POLYMARKET_RUN_METERED_TESTS: '1'
PRIVY_TEST_APP_ID: ${{ vars.PRIVY_TEST_APP_ID }}
PRIVY_TEST_APP_SECRET: ${{ secrets.PRIVY_TEST_APP_SECRET }}
Expand Down
57 changes: 15 additions & 42 deletions examples/scripts/src/public-streams.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,20 @@
import { createPublicClient } from '@polymarket/client';

const client = createPublicClient();

const stream = await client.subscribe([
{
topic: 'prices.crypto.chainlink',
},
{
topic: 'prices.crypto.chainlink.twap',
windowSeconds: 30,
symbols: ['btc/usd'],
},
{
topic: 'sports',
},
]);

let eventCount = 0;

for await (const event of stream) {
eventCount += 1;

switch (event.topic) {
case 'prices.crypto.chainlink':
console.log(
`${new Date(event.payload.timestamp).toLocaleString()} - Chainlink price update: ${event.payload.value} ${event.payload.symbol}`,
);
break;

case 'prices.crypto.chainlink.twap':
console.log(
`${new Date(event.payload.timestamp).toLocaleString()} - Chainlink ${event.payload.windowSeconds}s TWAP update: ${event.payload.value} ${event.payload.symbol}`,
);
break;

case 'sports':
console.log(
`Sports event update: ${event.payload.homeTeam} vs ${event.payload.awayTeam} - score ${event.payload.score}`,
);
break;
}

if (eventCount === 10) {
await stream.close();
try {
const stream = await client.subscribe([{ topic: 'sports' }]);
let eventCount = 0;
for await (const event of stream) {
console.log(
'Sports event update:',
event.payload.homeTeam,
'vs',
event.payload.awayTeam,
'score',
event.payload.score,
);
if (++eventCount === 10) break;
}
} finally {
await client.closeSubscriptions();
}
35 changes: 35 additions & 0 deletions examples/scripts/src/realtime-prices.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {
createSecureClient,
type EnvironmentConfigFork,
forkEnvironmentConfig,
} from '@polymarket/client';
import { privateKey } from '@polymarket/client/viem';
import { requireEnv } from './lib/env';

// Supply the JSON endpoint fork used by the integration suite, including
// realtime.ws for the staging price endpoint.
const fork = JSON.parse(
requireEnv('POLYMARKET_INTEGRATION_ENVIRONMENT_CONFIG'),
) as EnvironmentConfigFork;
const client = await createSecureClient({
environment: forkEnvironmentConfig(fork),
wallet: requireEnv('POLYMARKET_DEPOSIT_WALLET'),
signer: privateKey(requireEnv('POLYMARKET_PRIVATE_KEY')),
});

try {
const prices = await client.subscribe([
{ topic: 'prices.crypto', symbols: ['btcusd', 'ethusd'] },
{ topic: 'prices.crypto.twap', symbols: ['btc/usd'], windowSeconds: 60 },
{ topic: 'prices.equity', symbol: 'aapl' },
]);
let count = 0;
for await (const event of prices) {
if (event.type === 'subscribe')
console.log(event.topic, event.payload.symbol, event.payload.data);
else console.log(event.topic, event.payload.symbol, event.payload.value);
if (++count === 20) break;
}
} finally {
await client.closeSubscriptions();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Captured from the internal staging WebSocket edge on 2026-09-08. Frames are unmodified; no credentials were captured. The public edge rejected this machine's upgrade.

The `capture-auth` acknowledgment was captured separately with public dummy credentials in shadow-auth mode. It pins the acknowledgment format, not credential enforcement. The integration protocol suite also verifies authentication using the secure client's credentials.

Live crypto (btcusd, ethusd), equity (aapl), and 60-second TWAP were available. The TWAP full_accuracy_value is a decimal string, not an E18 integer. The 30-second window, btcusdt, and the test BBO asset returned empty barriers. No dropped envelope or auth_required rejection could be elicited in staging shadow authentication mode. Deterministic malformed/precision cases are explicitly synthetic tests, not captured frames. These captures do not establish live coverage for the empty feeds or credential enforcement.
Loading