> ## Documentation Index
> Fetch the complete documentation index at: https://docs.polynode.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Short-Form Markets and Chainlink TWAP

> Follow rotating 5-minute, 15-minute, 1-hour, and 4-hour crypto markets with the matching Chainlink price window.

The managed short-form stream discovers the active Polymarket crypto market, subscribes to its settlements, follows the matching Chainlink price source, and rotates to the next market window.

Starting **August 7, 2026 at 00:00 UTC**, affected Polymarket short-form markets resolve against Chainlink-computed time-weighted average prices. Current SDKs select the required lookback automatically:

| Market duration | Chainlink price selection |
| --------------- | ------------------------- |
| 5 minutes       | 30-second TWAP            |
| 15 minutes      | 60-second TWAP            |
| 1 hour          | spot / default stream     |
| 4 hours         | 60-second TWAP            |

`30` and `60` are lookback windows, not publication intervals. Read [Polymarket's August 7 announcement](https://x.com/polymarketdevs/status/2082813706996772881), the [PolyNode transition guide](/crypto/twap), and [Polymarket's Chainlink TWAP guide](https://docs.polymarket.com/market-data/chainlink-twap) for the schedule and underlying price semantics.

<Info>
  Use TypeScript `0.12.0+`, Python `0.12.0+`, or Rust `0.15.0+`. Earlier versions do not implement the complete interval-to-TWAP mapping and rotation behavior documented here.
</Info>

## Managed stream

Choose an interval and, optionally, a subset of supported assets. The default is all seven: BTC, ETH, SOL, XRP, DOGE, HYPE, and BNB.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    import { PolyNode } from 'polynode-sdk';

    const apiKey = process.env.POLYNODE_API_KEY;
    if (!apiKey) throw new Error('Set POLYNODE_API_KEY');
    const pn = new PolyNode({ apiKey });

    const stream = pn.ws.shortForm('5m', { coins: ['btc', 'eth'] });

    stream.on('rotation', (rotation) => {
      console.log('window', rotation.windowStart, rotation.windowEnd);
      for (const market of rotation.markets) {
        console.log({
          coin: market.coin,
          slug: market.slug,
          priceToBeat: market.priceToBeat,
          twapWindowSeconds: market.twapWindowSeconds,
          upOdds: market.upOdds,
          timeRemaining: rotation.timeRemaining,
        });
      }
    });

    stream.on('price_feed', (price) => {
      console.log(price.feed, price.price, price.twap_window_seconds);
    });
    stream.on('settlement', (event) => console.log(event.status, event.tx_hash));
    stream.on('error', (error) => console.error(error.message));

    process.once('SIGINT', () => stream.stop());
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import asyncio
    import os
    from polynode import AsyncPolyNode

    async def main() -> None:
        async with AsyncPolyNode(api_key=os.environ["POLYNODE_API_KEY"]) as pn:
            stream = pn.ws.short_form("5m", coins=["btc", "eth"])

            def on_rotation(rotation) -> None:
                for market in rotation.markets:
                    print(
                        market.coin,
                        market.slug,
                        market.price_to_beat,
                        market.twap_window_seconds,
                        rotation.time_remaining,
                    )

            stream.on("rotation", on_rotation)
            stream.on(
                "price_feed",
                lambda price: print(
                    price.feed, price.price, price.twap_window_seconds
                ),
            )
            stream.on("settlement", lambda event: print(event.status, event.tx_hash))
            stream.on("error", lambda error: print(error))

            try:
                await asyncio.Event().wait()
            finally:
                stream.stop()

    asyncio.run(main())
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    use polynode::{
        Coin, PolyNodeClient, PolyNodeEvent, ShortFormInterval, ShortFormMessage,
    };

    #[tokio::main]
    async fn main() -> polynode::Result<()> {
        let client = PolyNodeClient::new(
            std::env::var("POLYNODE_API_KEY").expect("Set POLYNODE_API_KEY")
        )?;
        let mut stream = client
            .short_form(ShortFormInterval::FiveMin)
            .coins(&[Coin::Btc, Coin::Eth])
            .start()
            .await?;

        while let Some(message) = stream.next().await {
            match message {
                ShortFormMessage::Rotation(rotation) => {
                    for market in rotation.markets {
                        println!(
                            "{} {} {:?} {:?}",
                            market.coin.id(),
                            market.slug,
                            market.price_to_beat,
                            market.twap_window_seconds,
                        );
                    }
                }
                ShortFormMessage::PriceFeed(price) => {
                    println!("{} {} {:?}", price.feed, price.price, price.twap_window_seconds);
                }
                ShortFormMessage::Event(PolyNodeEvent::Settlement(event)) => {
                    println!("{:?} {}", event.status, event.tx_hash);
                }
                ShortFormMessage::Error(error) => eprintln!("{error}"),
                _ => {}
            }
        }
        Ok(())
    }
    ```
  </Tab>
</Tabs>

## What rotation means

Market slugs and outcome-token IDs change at every boundary. Chainlink selection is also scoped to the WebSocket connection. The managed helper therefore owns a dedicated socket and performs this sequence at each boundary:

1. close the old dedicated socket
2. discover the new market slugs and token IDs
3. connect a replacement socket
4. subscribe to `settlements` for the exact new slugs
5. subscribe to the interval's exact Chainlink TWAP window, when applicable
6. emit `rotation` with the new market metadata

This reconnect is expected and necessary. Do not place unrelated subscriptions on the managed stream's dedicated connection; the helper isolates it for you.

If discovery or subscription fails, the helper emits an error. Keep the error handler visible in production and restart the stream if it stops.

## Rotation payload

Each discovered market includes the fields needed to connect other user-facing components:

| Field                          | Meaning                                                  |
| ------------------------------ | -------------------------------------------------------- |
| `slug` / `conditionId`         | current Polymarket market identifiers                    |
| `clobTokenIds`                 | outcome token IDs for orderbook or trading calls         |
| `chainlinkFeed`                | canonical feed, such as `BTC/USD`                        |
| `twapWindowSeconds`            | `30`, `60`, or `null` for the 1-hour spot/default stream |
| `priceToBeat`                  | opening Chainlink reference when available               |
| `upOdds` / `downOdds`          | current outcome probabilities                            |
| `liquidity` / `volume24h`      | current market metadata                                  |
| `feesEnabled` / `takerBaseFee` | fee metadata returned for the market                     |

Python and Rust expose the same fields in `snake_case`. Rust's `RotationInfo` also exposes `twap_window_seconds` for the selected interval.

## Choose another interval

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    const fiveMinute = pn.ws.shortForm('5m');
    const fifteenMinute = pn.ws.shortForm('15m');
    const hourly = pn.ws.shortForm('1h');
    const fourHour = pn.ws.shortForm('4h');
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    five_minute = pn.ws.short_form("5m")
    fifteen_minute = pn.ws.short_form("15m")
    hourly = pn.ws.short_form("1h")
    four_hour = pn.ws.short_form("4h")
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    let five_minute = client.short_form(ShortFormInterval::FiveMin);
    let fifteen_minute = client.short_form(ShortFormInterval::FifteenMin);
    let hourly = client.short_form(ShortFormInterval::Hourly);
    let four_hour = client.short_form(ShortFormInterval::FourHour);
    ```
  </Tab>
</Tabs>

Each managed stream owns its own connection. If you need several intervals, monitor connection usage and stop streams you no longer need.

## Subscribe to TWAP prices manually

Use a manual Chainlink subscription when you want price events without managed market discovery. Combine every feed and lookback needed on that socket into one subscription.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    const prices = await pn.ws.subscribe('chainlink')
      .feeds(['BTC/USD', 'ETH/USD'])
      .twapWindows([30, 60])
      .send();

    console.log(prices.priceSource, prices.twapWindows, prices.warnings);
    prices.on('price_feed', (event) => {
      const key = `${event.feed}:${event.twap_window_seconds}:${event.timestamp}`;
      console.log(key, event.price);
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    prices = await (
        pn.ws.subscribe("chainlink")
        .feeds(["BTC/USD", "ETH/USD"])
        .twap_windows([30, 60])
        .send()
    )
    print(prices.price_source, prices.twap_windows, prices.warnings)
    prices.on(
        "price_feed",
        lambda event: print(
            event.feed, event.twap_window_seconds, event.timestamp, event.price
        ),
    )
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    use polynode::ws::{Subscription, SubscriptionType};

    let ack = stream.subscribe_with_ack(
        Subscription::new(SubscriptionType::Chainlink)
            .feeds(vec!["BTC/USD".into(), "ETH/USD".into()])
            .twap_windows(vec![30, 60]),
    ).await?;
    println!("{:?} {:?} {:?}", ack.price_source, ack.twap_windows, ack.warnings);
    ```
  </Tab>
</Tabs>

On every price event, inspect `is_twap` and `twap_window_seconds`; do not infer the price type from arrival frequency. If both windows are selected, key state by feed, window, and timestamp.

## Connect the current market to the orderbook

Use the rotation payload's outcome token IDs. Replace the previous token set whenever `rotation` fires:

```typescript theme={null}
import { OrderbookEngine } from 'polynode-sdk';

const engine = new OrderbookEngine({ apiKey, integrity: true });

stream.on('rotation', async ({ markets }) => {
  const tokenIds = markets.flatMap((market) => market.clobTokenIds);
  await engine.subscribe(tokenIds);
});
```

See [Orderbook streaming and PN1](/sdks/orderbook) for readiness, integrity errors, and cleanup.

## Shutdown

Stop every managed stream you create. This cancels its rotation timer, removes subscriptions, and closes its dedicated connection.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    stream.stop();
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    stream.stop()
    ```
  </Tab>

  <Tab title="Rust">
    ```rust theme={null}
    stream.stop().await;
    ```
  </Tab>
</Tabs>
