> ## 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.

# Changelog

> All notable changes to the PolyNode API and SDKs.

## 2026-06-22 - V3 wallet accounting summary

Added an optional all-time accounting summary to the V3 wallet summary endpoint.

What's new:

* `GET /v3/wallets/{address}?include_accounting_summary=true` now returns `accounting_summary`.
* `accounting_summary.fees_paid.amount` is the exact all-time trader-paid fee total from indexed onchain `OrderFilled` rows.
* `accounting_summary.fees_paid.raw` exposes the exact 6-decimal raw amount.
* Maker rebates and rewards are represented with explicit status fields. `not_loaded` means the source projection has not been loaded; clients should not treat those values as zero.

Existing default responses are unchanged. The accounting object is included only when requested.

***

## 2026-06-15 - PM2 AutoRedeemer redemptions in WebSocket streams

PM2 AutoRedeemer redemptions are now surfaced in both the combo stream and the standard redemption stream.

What's new:

* AutoRedeemer `Redemption`, `BinaryRedemption`, and `NegRiskRedemption` logs emit customer-facing `combo_lifecycle` events with the wallet, exact raw payout, normalized payout, module name, and receipt log index. Binary and negative-risk redemptions also include the condition ID directly.
* Confirmed `combo_status_update` messages can include `lifecycle_events[]` for receipt-only AutoRedeemer lifecycle activity.
* The same AutoRedeemer payout is also bridged into the standard `redemption` event class, attributed to the user wallet instead of the AutoRedeemer contract.
* New `type: "redemptions"` WebSocket preset subscribes directly to `redemption` events. Existing `settlements` or `wallets` subscriptions with `event_types: ["redemption"]` continue to work.

No subscription change is required for existing redemption consumers.

***

## 2026-06-12 - Faster V3 builder trade time filters

Improved reliability and latency for time-filtered builder trade lookups.

What's improved:

* `GET /v3/builders/{code}/trades?after=...` now returns consistently for high-volume builders instead of timing out on large builder histories.
* `after` / `before` filters now work efficiently with broad `tag_slug` or `category` filters when a time bound is provided.
* Time windows outside a builder's activity range return a normal empty response instead of an internal timeout.
* Invalid millisecond timestamps now return a structured `400` with a hint to use Unix seconds.

No response fields or pagination semantics changed.

***

## 2026-06-11 - Polymarket combos: V3 API and WebSocket event references

Added public documentation for Polymarket combo / combinatorial position support.

What's new:

* New combo API group in the API Reference.
* `GET /v3/combos/markets` for observed combo markets and legs.
* `GET /v3/combos/activity` for combo lifecycle activity by market, condition, position, or wallet.
* `GET /v3/wallets/{address}/combos/positions` for combo-only wallet positions.
* `GET /v3/wallets/{address}/combos/trades` for combo-only wallet trades.
* `GET /v3/wallets/{address}/combos/activity` for combo-only wallet lifecycle activity.
* `GET /v3/wallets/{address}/combos/summary` for combo-only wallet P\&L summary.
* `include_combos=true` on wallet summary, wallet P\&L, wallet positions, and wallet trades for additive standard + combo responses.
* New WebSocket event reference pages for `combo_execution`, `combo_status_update`, `combo_lifecycle`, and `combo_approval`.

Notes:

* Existing wallet endpoints keep their default response behavior. Combo data is included only when explicitly requested or when using dedicated combo endpoints.
* If a wallet has no combo exposure, `include_combos=true` returns a normal successful response with a zero combo contribution.

***

## 2026-06-09 - Python SDK v0.10.6 WebSocket resilience

Released `polynode==0.10.6` for Python WebSocket consumers.

What's new:

* Event-stream subscriptions preserve frames that arrive before the subscribe acknowledgement, so handlers attached immediately after `await ...send()` still receive initial snapshot/live events.
* Default event WebSocket subscribe timeout is now 30s and configurable with `WsOptions(subscribe_timeout=...)`.
* Orderbook `subscribe()` now resolves only after the server acknowledges the subscription, and rejects promptly on server errors, disconnects, or timeout.
* Orderbook `unsubscribe(token_ids)` can remove a subset of tokens; `unsubscribe()` with no arguments remains the full unsubscribe behavior.
* Added protocol regression tests for snapshot-before-ack, pending-subscribe errors, timeout handling, and partial unsubscribe behavior.

Upgrade:

```bash theme={null}
pip install --upgrade polynode==0.10.6
```

***

## 2026-06-09 - TypeScript SDK v0.10.17 WebSocket resilience

Released `polynode-sdk@0.10.17` for Node/TypeScript WebSocket consumers.

What's new:

* Event-stream subscriptions preserve frames that arrive before the subscribe acknowledgement, so handlers attached immediately after `await subscribe(...).send()` still receive initial snapshot/live events.
* Default WebSocket subscribe timeout is now 30s and configurable with `subscribeTimeoutMs`.
* Orderbook `subscribe()` now resolves only after the server acknowledges the subscription.
* Orderbook reconnects replay the active token list without replacing existing handlers.
* Orderbook `unsubscribe(tokenIds)` can remove a subset of tokens; `unsubscribe()` with no arguments remains the full unsubscribe behavior.
* Added SDK protocol tests and a live orderbook soak test for multi-token subscribe, partial unsubscribe/resubscribe, snapshots, live batches, protocol errors, unexpected unsubscribe frames, and disconnects.

Upgrade:

```bash theme={null}
npm install polynode-sdk@0.10.17 ws
```

***

## 2026-05-27 - V3 wallet P\&L alignment, fees, rebates, rewards, and PolyUSD flows

Updated V3 wallet accounting surfaces.

What's new:

* Default/all-time `GET /v3/wallets/{address}/pnl/events` now returns a single all-time bucket so it agrees with `GET /v3/wallets/{address}/pnl`.
* Explicit time-windowed P\&L events (`period`, `after`, or `before`) return realized P\&L buckets and ignore timestamp-0 artifacts.
* Added `GET /v3/wallets/{address}/fees-paid` for fee-bearing wallet fills.
* Added `GET /v3/wallets/{address}/polyusd-flows` for wallet PolyUSD deposits and withdrawals.
* Added `GET /v3/wallets/{address}/rebates` for maker rebates.
* Added public reward-market configuration at `GET /v3/rewards/markets` and `GET /v3/rewards/markets/{condition_id}`.

Notes:

* Fees paid and maker rebates are intentionally separate surfaces. LP reward market configuration is public; per-wallet earned LP rewards require authenticated Polymarket data and are not exposed as an arbitrary-wallet lookup in the public docs.
* `GET /v3/wallets/{address}/activity` remains the conditional-token activity feed; PolyUSD deposits and withdrawals now live in the separate wallet PolyUSD flows endpoint.

***

## 2026-05-26 — Webhooks beta: first observed position opens

Added `position_status_change` support for watched-wallet CTF position opens and exits.

What's new:

* `position_status_change` now detects first observed `none` -> `open` transitions for watched wallets from CTF ERC-1155 transfer deltas.
* Existing wallet positions are seeded when a wallet is first watched, so new webhooks emit future transitions without backfilling historical opens.
* Each watched wallet now gets its own seed cursor, so adding a new wallet does not replay stale source rows from before that wallet's baseline.
* Payloads now include `previous_amount`, `new_amount`, `delta_amount`, `transfer_amount`, `source`, `source_event_id`, block/transaction fields, and market enrichment.
* Filters now include `wallet_addresses`, `token_ids`, `condition_ids`, `event_slugs`, `tags`, `status_from`, and `status_to`.

Beta note:

* `wallet_addresses` is required for this beta. Unfiltered/global position-status webhooks are intentionally not enabled yet.

Docs updated:

* See [`position_status_change`](/webhooks/events/position-status-change) for the exact payload and copyable first-open registration example.

***

## 2026-05-26 — TypeScript SDK v0.10.13: V3 parity, resolver, and profile helpers

Released a TypeScript SDK parity update for the current REST API surface.

What's new:

* `pn.v3` namespace for the V3 historical data API while preserving existing top-level SDK behavior.
* Typed V3 methods for wallet summaries, wallet P\&L, P\&L events, wallet trades, wallet positions, global trades, global positions, market search, market price, market trades, market positions, leaderboard, tags, builders, builder trades, fees, resolutions, conditions, and token info.
* Builder trade filters in the SDK: `tokenId`, `conditionId`, `marketSlug`, `eventSlug`, `side`, `category`, `tagSlug`, `minAmount`, `after`, `before`, `limit`, and `offset`.
* `pn.resolve(query)` for wallet / EOA / username resolution, returning `{ safe, eoa, username, type }`.
* Fully typed `pn.walletOnchainPositions()` response including `condition_id`, `unrealized_pnl`, `current_price`, `market_status`, `market`, `slug`, `event_slug`, `outcome`, `won`, `winning_outcome_index`, `last_trade_at`, `closed_at`, `resolved_at`, and top-level `total_unrealized_pnl`.
* V3 Polymarket profile helpers: `polymarketUsernameAvailable`, `createPolymarketUsernameChallenge`, `completePolymarketUsername`, and `polymarketProfile`.

Docs updated:

* Added [TypeScript — V3 Data & Profiles](/sdks/ts/v3-data) with copyable SDK examples.
* Updated [TypeScript — REST API](/sdks/ts/rest-api), [TypeScript — Types](/sdks/ts/types), and [Polymarket Profile Setup](/guides/polymarket-profile-setup) with SDK-specific examples and the exact signing flow.

Notes:

* Existing `pn.market()`, `pn.wallet()`, `pn.leaderboard()`, and other top-level methods keep their historical paths and response shapes.
* Event-by-ID lookup and a fully filterable events listing remain backend/API work, not just SDK wrapper work. Current SDK support reflects the endpoints that exist publicly today.

***

## 2026-06-04 — `/resolve` prefers deployed deposit wallets

`GET /v1/resolve/{query}` keeps the same response fields: `safe`, `eoa`, `username`, and `type`.

The `safe` field now returns the preferred Polymarket trading wallet. If the resolved EOA has a deployed deposit wallet, `safe` is that deposit wallet and `type` is `"deposit_wallet"`. If no deposit wallet is deployed, `safe` falls back to the existing Safe/proxy wallet.

This applies to address, EOA, and cached username lookups.

***

## 2026-05-22 — V3 grouped trade total alias

Added `total_amount_usd` to v3 `group_by=order_hash` trade responses. It is an additive alias of `total_usd` / `amount_usd`, so clients that already use the v2 grouped trade field can read the same total field on v3.

Affected endpoints:

* `GET /v3/wallets/{address}/trades?group_by=order_hash`
* `GET /v3/trades?group_by=order_hash`

Existing fields are unchanged.

***

## 2026-05-22 — Builder metadata and filtered builder trades

Builder analytics now include public builder profile metadata on all builder endpoints:

* `GET /v3/builders`
* `GET /v3/builders/{code}`
* `GET /v3/builders/{code}/trades`

New additive fields:

* `builder_code`
* `builder_name`
* `builder_logo`
* `builder_verified`

`/v3/builders/{code}/trades` also now supports builder-specific filters for `token_id`, `condition_id`, `market_slug`, `event_slug`, `side=buy|sell`, `min_amount`, `after`, `before`, `limit`, and `offset`.

`category` and `tag_slug` filters are available with guardrails. Very broad category/tag requests return a structured `400` asking you to add `market_slug`, `condition_id`, `token_id`, or time bounds.

```bash theme={null}
curl "https://api.polynode.dev/v3/builders/0x4898df15.../trades?market_slug=dota2-tundra-xtreme-2026-05-22-game1&tag_slug=sports&limit=10"
```

Existing fields and existing pagination behavior are unchanged.

***

## 2026-05-18 — PnL methodology note

For users comparing our PnL numbers to what the Polymarket frontend displays: the two are computed differently and will diverge for wallets that hold a lot of settled-but-not-redeemed positions.

**Polymarket's `pnl`** (leaderboard, profile chart) is a **mark-to-market equity curve delta**: `equity(end_of_window) − equity(start_of_window)`. Resolved markets are booked into the curve at settlement, regardless of whether the wallet has called `redeemPositions`.

**Our `net_realized_pnl`** counts realized closes only — CLOB sells (the wallet sold their shares back) plus V2 adapter events (redemptions, merges, NRC conversions). Unrealized gains on settled-but-unredeemed positions stay in `unrealized_pnl`, not `net_realized_pnl`.

**Empirically verified** against PM's `/closed-positions[].realizedPnl` on a representative wallet: per-position realized matches PM within 1%. The "lifetime pnl" headline diverges by the value of settled-not-redeemed positions in the wallet.

`total_pnl` (= `net_realized_pnl + unrealized_pnl`) is the closer comparison to PM's curve delta — it includes both closed and still-open positions priced at current market.

***

## 2026-05-18 — New endpoint: wallet P\&L time series

Added `GET /v3/wallets/{address}/pnl/events` — returns realized P\&L bucketed by `hour`, `day`, `week`, or `month` so you can plot a wallet's P\&L over time without pulling every trade. Each bucket includes `realized_pnl`, a running `cumulative_pnl`, gross profit/loss, win/loss counts, and event count. Supports `?period=1d|7d|30d|1y` shortcuts or explicit `after` / `before` Unix-second timestamps.

Most full-history queries return sub-second.

Other quality-of-life:

* `?after` / `?before` outside the wallet's active window now correctly return zero buckets instead of an error.
* All `/v3/*` paths now accept an optional trailing slash (`/v3/positions/` is equivalent to `/v3/positions`).
* Unknown `/v3/*` paths return a structured 404 with a `hint` pointing to the docs, rather than HTML.
* Invalid `sort` and `order` values now return `400` with the list of valid options instead of silently falling back to a default — typos surface immediately.

### Doc / API parity pass (later same day)

Round 2 of the parity work — pure alignment between the API and the public reference, no breaking changes:

* **`/v3/markets/condition/{condition_id}/positions`** — response field names now match `/v3/markets/{token_id}/positions`. Was returning abbreviated keys (`amt`, `avgp`, `bought`, `rpnl`, `tid`); now returns the long, documented names (`amount`, `avg_price`, `total_bought`, `realized_pnl`, `token_id`). Old keys are gone — update any client code using them.
* **`/v3/resolutions`** — `payout_numerators` is now a JSON array of integers (e.g. `[0, 1]`), matching `/v3/conditions/{cid}`. Was returning quoted strings. `payout_denominator` is also an integer now.
* **`/v3/builders`, `/v3/tags`, `/v3/tokens/{token_id}`** — response envelopes now include `offset` and `limit` like every other list endpoint.
* **Param validation tightened** — invalid `?order=`, `?side=`, `?status=`, `?group_by=`, `?sort_by=` on trades / fees / resolutions / positions endpoints now return `400` with the valid-values list, instead of silently falling back to a default. Valid values still pass through unchanged.
* **Token price scale** — `/v3/markets/{token_id}/price.price` and `/v3/tokens/{token_id}.data[].price` are JSON numbers in USD (range `0`–`1`), not 6-decimal strings. Reference page text updated accordingly.

***

## 2026-05-16 — V3 API goes live + performance fixes

All `/v3/*` endpoints are now generally available (no longer "Coming Soon"). Several queries that timed out or ran slowly have been rewritten to use index-friendly query plans:

* `/v3/wallets/{addr}/positions` — default sort no longer times out, returns in \<100ms
* `/v3/markets/slug/{slug}/trades` — fixed timeout, now \<100ms
* `/v3/tags/{slug}/leaderboard` — small/medium tags are now sub-second; tags larger than 4,000 markets return a structured "too large" hint pointing to the global leaderboard
* `/v3/tags/{slug}` — drops from 4.8s to \~1s
* `/v3/wallets/{addr}/trades` — drops from 2.3s to \~200ms

Tag slugs are now matched case-insensitively and accept URL-style hyphens: `/v3/tags/us-election/leaderboard` resolves to the stored tag `US Election`.

Several doc examples that previously showed a `columns/rows` envelope have been corrected to the `data: [{...}]` shape that the API actually returns.

***

## 2026-05-14 — SDK fix: deposit wallet deploy + approve

Fixed the deposit wallet (V2 / POLY\_1271) onboarding flow across all three SDKs. The relayer's `/submit` endpoint now requires builder authentication for WALLET-type submissions. All SDKs have been updated to include the required auth headers.

If you previously hit `401 invalid authorization` or `API key and signer mismatch` errors when deploying a deposit wallet or setting approvals, update to the latest SDK version:

```bash theme={null}
npm install polynode-sdk@0.10.7    # TypeScript
cargo update -p polynode            # Rust (0.13.4)
pip install polynode==0.10.4        # Python
```

`ensureReady()` handles the full flow automatically — deploy, approve, create credentials. If your wallet is already deployed, it skips to approvals. If approvals are already set, it skips to credentials.

***

## 2026-05-10 — Sort and group trades by order hash

`/v2/onchain/trades` now supports two new query parameters for working with limit order fills:

**`sort_by=order_hash`** — reorders the response so all fills from the same limit order are adjacent, sorted by time within each order. Every fill is still returned individually. No data is collapsed.

```bash theme={null}
curl "https://api.polynode.dev/v2/onchain/trades?wallet=0x...&sort_by=order_hash&limit=100" \
  -H "x-api-key: YOUR_KEY"
```

**`group_by=order_hash`** — aggregates all fills from the same limit order into a single row with `total_amount_usd`, `total_shares`, `avg_price` (VWAP), `fill_count`, time range (`first_fill_at` / `last_fill_at`), and a `tx_hashes` array.

```bash theme={null}
curl "https://api.polynode.dev/v2/onchain/trades?wallet=0x...&group_by=order_hash&limit=100" \
  -H "x-api-key: YOUR_KEY"
```

A single limit order can be filled across hundreds of transactions over hours or days. These parameters let you view that activity at either granularity.

***

## 2026-05-05 — Magic-link wallet EOA recovery in `/resolve`

`/v1/resolve` now returns the controlling EOA for Polymarket magic-link wallets — accounts created with email or social login. Previously these returned `eoa: null` because Polymarket's profile API does not expose the signer for this wallet type.

```json theme={null}
{
  "safe": "0x16cbe223607a6513ae76d1e3751c78e4eabc2704",
  "eoa": "0xbe5ba588ab7173b34efc0706b881794951014293",
  "username": "MRF",
  "type": "proxy"
}
```

**No code changes required.** The response shape is unchanged. The `eoa` field is now populated for the vast majority of `type: "proxy"` wallets. A small number may still return `eoa: null` where the EOA cannot be determined.

Repeat queries are sub-100ms.

***

## 2026-05-02 — Deposit wallet support

Polymarket is rolling out deposit wallets as a new account type for new users. This release adds full support across the API, all three SDKs, and the copy trading engine. **Existing integrations are fully backward compatible** — no code changes required unless you want to take advantage of the new features.

Read the full [Deposit Wallets guide](/guides/deposit-wallets) for details, code examples, and FAQ.

### What's new

**`/resolve` endpoint** — now returns a `type` field (`"safe"`, `"deposit_wallet"`, or `"proxy"`) in all responses. Resolves deposit wallets in both directions: wallet address to EOA and EOA to wallet address. Existing `safe`, `eoa`, and `username` fields unchanged.

```json theme={null}
{
  "safe": "0x8b60bf0f650bf7a0d93f10d72375b37de18f8c40",
  "eoa": "0xa60601a4d903af91855c52bfb3814f6ba342f201",
  "username": null,
  "type": "deposit_wallet"
}
```

**SDK — address derivation and detection** — `deriveDepositWalletAddress(eoa)` computes the deterministic deposit wallet address for any EOA. `detectWalletType()` and `ensureReady()` automatically detect whether an EOA has a Safe, proxy, or deposit wallet. Safe is checked first, so existing users keep their existing wallet type.

**SDK — order signing** — when `signatureType` is `POLY_1271` (3), the SDK wraps the EIP-712 signature using ERC-7739 TypedDataSign format automatically. `order()` / `signV2Order()` / `create_signed_order_v2()` detect the signature type from stored credentials and sign correctly. No code changes needed on your side.

**SDK — onboarding** — `ensureReady()` handles the full flow for deposit wallet users: derives the wallet, deploys via `WALLET-CREATE` if needed, and sets V2 approvals via a signed `WALLET` batch. Same one-call pattern as Safe wallets.

**Copy trading** — the copy trading engine generates the correct typed data for deposit wallet followers (`sig_type: 3`). Your signing code should use `signV2Order()` from the SDK.

### Install

```bash theme={null}
cargo add polynode@0.13.3        # Rust
npm install polynode-sdk@0.10.6  # TypeScript
pip install polynode==0.10.3     # Python
```

### Do I need to update?

* **If you only read data** (positions, trades, resolve): no changes needed. The `type` field is additive.
* **If you place orders via the SDK**: update to the versions above. Your code stays the same.
* **If you build order signatures yourself**: you need to implement ERC-7739 wrapping for `signatureType: 3`. See the [guide](/guides/deposit-wallets#how-poly_1271-signing-works).

***

## 2026-05-01 — Cursor pagination on `/v2/onchain/*/trades`

The two trade-history endpoints now support **cursor pagination** in addition to offset:

* `GET /v2/onchain/markets/{token_id}/trades?cursor=<lastTs>:<lastId>`
* `GET /v2/onchain/wallets/{address}/trades?cursor=<lastTs>:<lastId>`

**Why it matters:** offset pagination slows down the deeper you page — page 500 means the database walks 50,000 rows of wasted work to find your starting point. Cursor pagination loads page 1000 just as fast as page 1 — typically \~1 second per page, regardless of how deep you go.

**How to use:**

1. First page: pass `?cursor=` (empty value) instead of `?offset=0`.
2. Response includes a `pagination` block with `cursor` and `has_more`.
3. Subsequent pages: pass that `cursor` back as `?cursor=<value>`.
4. Stop when `has_more` is `false`.

**Available on every paid tier** — no Growth restriction.

**Compatibility:** purely additive. The existing `?offset=` mode is unchanged and remains fully supported. Existing integrations don't need to change anything — switch to cursor when you next touch the integration.

See the updated docs for full examples: [Trade History (Market)](/api-reference/onchain/market-trades) · [Trade History (Wallet)](/api-reference/onchain/wallet-trades).

***

## 2026-05-01 — Bulk trade-export endpoints (Growth plan and above)

Two new endpoints return the **entire** trade history for a market token or wallet in a **single response** — no client-side pagination required:

* `GET /v2/onchain/markets/{token_id}/trades/all`
* `GET /v2/onchain/wallets/{address}/trades/all`

The endpoint walks the full history for you. Optional `from` and `to` query parameters narrow the window.

**Limits:**

* Hard cap **250,000 trades** per call. Beyond that, response includes `"partial": true` with `"partial_reason": "hard_cap_250000"`.
* Wall-clock budget **180 seconds**. First cold-cache calls on busy markets/heavy wallets may take **1-3 minutes**. Subsequent calls hit Redis cache and return in **under 1 second**.
* Typical payload **1-50 MB**. Worst case (capped): **100-200 MB JSON**.
* Cache TTL **5 minutes** per `(scope, from, to)` combination. Partial results are **not cached**.

**Tier requirement:** **Growth plan (\$200/mo) or above.** Starter and free tier requests receive `403`. The bulk endpoints are gated to paid plans because of their large query and payload sizes.

**Compatibility:** purely additive. The standard paginated `/trades` endpoints are unchanged and remain available on all paid tiers.

Updated the request timeout to 180 seconds so cold-cache calls complete cleanly.

***

## 2026-05-01 — Backtest Copy PnL: new `total_realized_pnl_usdc` field

Added `total_realized_pnl_usdc` to `/v2/copy-pnl/{wallet}` responses. This is the **signed sum of WAVG-realized PnL across every position the matcher touched in the window** — the closest single number to "did this wallet make money trading?" and the recommended primary "PnL" field for customer-facing UI.

Existing `actual_pnl_usdc` is unchanged but is now documented as a **cashflow** metric (USDC delta in/out of the wallet over the window), which is intentionally different from realized PnL — see the [endpoint docs](/api-reference/backtesting/copy-pnl#understanding-cashflow-pnl-vs-realized-pnl) for the worked example.

Also fixed a long-standing bug in the cashflow accounting where neg-risk-conversion events were over-counting `settlement_in` (treating share counts as USDC). NRC-active wallets see corrected numbers across all four periods (`14d`, `30d`, `60d`, `180d`) on the next BYOB cycle refresh.

***

## 2026-04-30 — Backtest Copy PnL: per-position metrics now match Polymarket byte-for-byte

The new fields added earlier today (`avg_entry_prob_weighted`, `positions_closed` on `/v2/copy-pnl/*`) now produce values that match Polymarket's own data-api position math.

Validated against `data-api.polymarket.com/positions` `realizedPnl` across 30 positions on diverse wallets (standard CTF + neg-risk markets):

* **97 %** sub-penny match
* **100 %** sub-\$1 match
* **100 %** sub-\$10 match

No API changes — same field names, same response shape. Existing integrations automatically benefit. Cached values progressively refresh as background scoring cycles complete.

***

## 2026-04-30 — BYOB Snapshot: every wallet × every period in one read

New endpoint `GET /v2/copy-pnl/snapshot` returns every wallet in your tracked-wallet pool with backtest copy-PnL scores across all six time windows (`7d`, `14d`, `30d`, `60d`, `90d`, `180d`) in a single response. Built for stats-card UIs and dashboards that need the whole pool at once instead of issuing one request per period.

* **One request** — no per-period round trips, no client-side stitching.
* **Sub-second cached** — 100 wallets × 6 periods returns in \~70 ms / \~150 KB. 1000 wallets × 6 periods in \~500 ms / \~1.5 MB.
* **Optional period subset** via `?periods=7d,30d` to drop payload when you only need one window.
* **Per-wallet error reporting** — heavy whales that errored on the most recent refresh surface their failure reason inline, so your UI can render "X wallets retrying" instead of hiding gaps.

```bash theme={null}
curl -H "x-api-key: $YOUR_KEY" \
  "https://api.polynode.dev/v2/copy-pnl/snapshot"
```

See [BYOB — Snapshot](/api-reference/backtesting/byob-snapshot) for the full response shape and examples.

***

## 2026-04-30 — `/v2/onchain/markets/{token}/volume` faster

Backend optimization for the market volume endpoint. Response shape and values are unchanged — same `buys`, `sells`, `total_trades`, `buy_volume_usdc`, `sell_volume_usdc`, `volume_usdc`, `found` fields. Light and empty markets see roughly halved latency. No integration changes required.

***

## 2026-04-30 — Wallet activity & redemptions: deep pagination unlocked

Heavy wallets returning more than 1000 records on `/v2/onchain/wallets/{wallet}/redemptions` and `/v2/onchain/wallets/{wallet}/activity` now return the complete dataset:

* **`offset` past 1000 now works.** Previously, pagination beyond the first 1000 records returned empty results regardless of how many records existed. You can now walk a heavy redeemer's full history with `offset=1000`, `offset=2000`, etc.
* **No more silent truncation.** Wallets with >1000 redemptions, splits, merges, or neg-risk conversions previously had results capped at 1000. The full record set is now returned.

No SDK update required. No integration changes. Same URLs, same query parameters, same response shapes — your existing code automatically benefits.

```bash theme={null}
# Walk a heavy redeemer's full history:
curl "https://api.polynode.dev/v2/onchain/wallets/{wallet}/redemptions?limit=1000&offset=0" \
  -H "x-api-key: $YOUR_KEY"

curl "https://api.polynode.dev/v2/onchain/wallets/{wallet}/redemptions?limit=1000&offset=1000" \
  -H "x-api-key: $YOUR_KEY"
```

***

## 2026-04-30 — TypeScript SDK 0.9.7

Pluggable storage for Bun/Deno/edge runtime compatibility:

* **`storage` config option** — pass `'memory'` for an in-memory backend (no native dependencies), a file path for SQLite (default), or your own `TradingStorage` implementation.
* **Auto-fallback** — if `better-sqlite3` isn't available (Bun, Deno, edge runtimes), the SDK automatically falls back to in-memory storage instead of crashing.
* **`BunSqliteBackend`** — persistent SQLite storage using Bun's built-in `bun:sqlite`. Zero native dependencies. Same schema, same persistence as the Node backend.
* **Auto-detection** — if you don't pass a `storage` option, the SDK tries `better-sqlite3` first (Node), then `bun:sqlite` (Bun), then falls back to in-memory. Just works in both runtimes.
* **Exported interface** — `TradingStorage`, `InMemoryStorage`, and `BunSqliteBackend` are all exported so you can use them directly or build custom adapters.

```typescript theme={null}
// Bun — auto-detected, no config needed:
const trader = new PolyNodeTrader({ /* storage auto-detects bun:sqlite */ });

// Bun — explicit:
import { PolyNodeTrader, BunSqliteBackend } from 'polynode-sdk';
const trader = new PolyNodeTrader({
  storage: new BunSqliteBackend('./trading.db'),
});
```

Upgrade:

```bash theme={null}
npm install polynode-sdk@0.9.7
```

***

## 2026-04-30 — TypeScript SDK 0.9.5

Bug fix release for the trading module:

* **Fixed `signer.getAddress is not a function` crash** — `ensureReady()` and `linkWallet()` could throw this error with viem WalletClient signers when Polymarket packages expect an ethers-style `getAddress()` method. The SDK now ensures `getAddress()` is always available on the normalized signer, regardless of which `@polymarket/clob-client` version you have installed.
* **Fixed signer detection logic** — a duplicate guard condition was allowing incomplete signer objects through, causing crashes instead of clean error messages.
* **Hardened relay signer adapter** — address extraction now handles additional wallet shapes during Safe deployment and approval flows.

Upgrade:

```bash theme={null}
npm install polynode-sdk@0.9.5
```

***

## 2026-04-30 — BYOB (Bring Your Own Backtest) — precomputed leaderboards

The on-demand backtest endpoints answer "score this wallet" synchronously. **BYOB inverts that** — you hand us a private wallet pool, we precompute scores in the background across all six period presets, you query the resulting leaderboard with sub-second latency.

Four new endpoints under `/v2/copy-pnl/`:

```
POST   /v2/copy-pnl/wallets       body: {addresses[]}    add to pool (max 1000)
DELETE /v2/copy-pnl/wallets       body: {addresses[]}    remove from pool
GET    /v2/copy-pnl/wallets                              list pool
GET    /v2/copy-pnl/leaderboard   ?period=&sort_by=&order=&limit=&offset=&min_trade_count=&exclude_toxic=
```

Newly-added wallets are scored within \~30s of being added. The full pool refreshes daily in the background. Each result row includes `computed_at` so you can render freshness in your UI.

Per-tenant isolation: each API key has its own private pool keyed on the SHA256 of the key. Your tracked wallets are never visible to other customers.

References: [Backtesting Overview](/api-reference/backtesting/overview) · [Add Wallets](/api-reference/backtesting/byob-add-wallets) · [Remove Wallets](/api-reference/backtesting/byob-remove-wallets) · [List Wallets](/api-reference/backtesting/byob-list-wallets) · [Leaderboard](/api-reference/backtesting/byob-leaderboard)

***

## 2026-04-30 — On-chain wallet/market trades: deep pagination unlocked

`/v2/onchain/wallets/{w}/trades` and `/v2/onchain/markets/{token}/trades` now properly paginate across the full trade history of any wallet or market, no matter how active. Previously these endpoints silently truncated heavy traders to \~2000 fills lifetime — deep `?offset=` queries returned empty.

Customer-visible behavior changes:

* Heavy wallets (e.g. 1M+ fills) now return correct, complete trade history
* Deep `?offset=` queries return real data instead of empty arrays
* No SDK or query-shape changes required — same `?limit=&offset=` parameters
* Lower latency on most queries

***

## 2026-04-30 — Backtest Copy PnL: batch endpoint

Score up to 100 wallets in one call:

```
POST /v2/copy-pnl/batch
{ "addresses": ["0x...", ...], "from": "...", "to": "..." }
```

Same math + response shape as the single-wallet endpoint. Per-wallet errors don't fail the batch — the bad slot returns `{"wallet": "...", "error": "..."}` and the rest still come back.

Reference: [Batch endpoint](/api-reference/backtesting/batch)

***

## 2026-04-30 — Backtest Copy PnL endpoint

New paid endpoint for scoring any wallet's copy-trade quality:

```
GET /v2/copy-pnl/{wallet}?period=30d
```

Walks every fill in the requested window, applies a realistic 2% slippage on buys and sells (capped at \$1.00 per share for buys), settles redemptions / merges / splits at face value, and returns:

* `actual_pnl_usdc` — the wallet's cashflow PnL over the window
* `backtest_copy_pnl_usdc` — what a copier would have earned with friction
* `slippage_amount_usdc` — the dollar gap
* `slippage_cost_rate_pct` — friction as a percentage of actual PnL
* `toxic_for_copying` — `true` when the rate exceeds 15% (wallet's profit relies on execution speed; copier won't replicate)
* `trade_count`, `applied_filters`, `sources`, optional `trades[]` drill-down

**Time window options**: `?period=7d|14d|30d|60d|90d|180d` (default 30d), or `?from=&to=` with `YYYY-MM-DD` or unix seconds.

**Note on PnL definition**: this returns **cashflow** PnL (real dollars moved), not Polymarket's website PnL (which marks open positions at current price). The response includes a `pnl_definition: "cashflow"` field to make this explicit. For PM-website style PnL use [Trader PnL Series](/api-reference/enriched/trader-pnl).

**Limits**: paid tier required, 1 request per 5s per key, 30s server-side budget. Validated on wallets up to \~1.6M fills in the window.

Full reference: [Backtest Copy PnL](/api-reference/backtesting/copy-pnl) · [Backtesting Overview](/api-reference/backtesting/overview)

***

## 2026-04-27 — Positions: 100% Polymarket parity, deterministic resolved-market mark, six new fields, real cursor pagination

`/v2/onchain/positions` now matches Polymarket's `data-api` to the cent on every shared position. Verified across six wallets, 627 total shared positions, 100.00% sub-penny match on `unrealized_pnl`.

What's new on each position row:

* **`initial_value`** — cost basis in USD of currently held shares. Use this when you need the basis number that matches Polymarket `cashPnl`.
* **`redeemable`** — `true` when the market has resolved and the user can call redeem to claim payout / accept loss. Filter on this with `market_status` to detect resolved-but-not-yet-redeemed positions.
* **`outcome_index`** — numeric index of this row's outcome (0 or 1 for binary markets). Stable across the API regardless of UI label.
* **`won`** / **`winning_outcome_index`** — present on `resolved-win` / `resolved-loss` rows. Explicit booleans / ints so consumers don't have to parse outcome label strings.
* **`opposite_asset`** — token ID of the binary counterpart outcome on the same market.

What's fixed on the math:

* **`current_price` is deterministic on resolved markets.** Reads settlement values directly from the CTF contract on Polygon. Returns exactly `1.0` for the winning outcome and `0.0` for the loser, the moment the market settles on chain.
* **`market_status` never lies.** Was previously stuck on `"live"` for fresh resolutions when our metadata was behind. Now uses the chain timestamp as the authoritative resolution signal: `"live"`, `"resolved-win"`, `"resolved-loss"`, `"resolved-unknown"`, or `"closed"`.
* **`unrealized_pnl` is byte-identical to Polymarket `cashPnl`** for any position covered by both sources.

`/v2/onchain/trades` cursor pagination now reports truthful `has_more`. Previously `has_more: false` was returned even when more rows existed beyond the requested `limit`. Walk pages by passing `pagination.pagination_key` back as `?pagination_key=…`.

```bash theme={null}
# returns has_more: true with pagination_key now
curl "https://api.polynode.dev/v2/onchain/trades?wallet=0x…&limit=2" -H "x-api-key: …"

# walk to the next page
curl "https://api.polynode.dev/v2/onchain/trades?wallet=0x…&limit=2&pagination_key=2" -H "x-api-key: …"
```

Time filters on `/v2/onchain/trades` use **`start_time`** and **`end_time`** (unix seconds), not `from` / `to`. Documented and verified.

***

## 2026-04-27 — Trades: unified schema, `direction` & `side` always present

`/v2/onchain/trades`, `/v2/onchain/wallets/{w}/trades`, and `/v2/onchain/markets/{tok}/trades` now return the same `direction` and `side` fields on every row, regardless of which filter combination is passed. Previously these fields appeared only when a `?wallet=` anchor was supplied — market-wide queries (`?condition_id=…`, `?token_id=…`, no filter) omitted both, and consumers had to render two different schemas.

```bash theme={null}
# customer's market-wide query — now returns direction+side on every row
curl "https://api.polynode.dev/v2/onchain/trades?condition_id=0xb778…&limit=3" \
  -H "x-api-key: YOUR_KEY"
```

```json theme={null}
{
  "side": "maker",
  "direction": "BUY",
  "maker": "0xdf0d2c…",
  "taker": "0xb323fd…"
}
```

**Anchor rule:**

* **With `?wallet=`** — `direction` and `side` are wallet-relative. `direction` is `BUY` when the wallet contributed USDC and received outcome shares, `SELL` when it contributed outcome shares and received USDC. `side` is `maker` when the wallet placed the resting order, `taker` when it crossed the spread.
* **Without `?wallet=`** — `direction` is anchored on the **maker** of each fill (the user who signed the limit order; on Polymarket's CLOB this is always a user EOA). `side` is therefore always `"maker"` in this case.

This means a single fill viewed from a wallet-filtered query and from a market-wide query may report different `direction` values — both are correct, just anchored on different parties. See the ["Direction & side semantics"](/api-reference/onchain/trades#direction--side-semantics) section on the trades reference page.

Strictly additive — calls that previously returned `direction`/`side` continue to return the exact same values.

***

## 2026-04-26 — New endpoint: `GET /v2/onchain/tags` for tag discovery

Lists every tag slug in the polynode taxonomy — currently 5,779 tags. Lightweight by default (just slug strings, 73 KB / 43 ms cold for the full list) or `?details=true` for per-tag enrichment (markets, events, first/last seen).

```bash theme={null}
# all tags
curl "https://api.polynode.dev/v2/onchain/tags" -H "x-api-key: YOUR_KEY"

# top 700 mainstream tags with stats
curl "https://api.polynode.dev/v2/onchain/tags?min_markets=100&details=true" \
  -H "x-api-key: YOUR_KEY"

# discover NBA-related tags
curl "https://api.polynode.dev/v2/onchain/tags?prefix=nba&details=true" \
  -H "x-api-key: YOUR_KEY"
```

Values returned here are exactly what to pass to `?tag_slug=` on the [wallet positions](/api-reference/wallets/onchain-positions) and [unified positions](/api-reference/onchain/positions) endpoints. Use it to populate filter dropdowns or autocomplete inputs. The underlying materialized view refreshes hourly so newly-added Polymarket tags appear within an hour.

***

## 2026-04-26 — Wallet Positions: `tag_slugs` field + `?tag_slug=` filter

Every row of [`GET /v2/wallets/{address}/positions/onchain`](/api-reference/wallets/onchain-positions) now includes a `tag_slugs` array carrying the Polymarket event-level tags that apply to that market — typical values like `nba`, `basketball`, `sports`, `politics`, `crypto`, `fed`, `2025-predictions`, plus event-specific slugs like `2026-fifa-world-cup-winner-595`. Order is most-specific to most-general.

A new optional `?tag_slug=<slug>` query parameter filters the response to only positions whose market carries that tag. Composes with `since` / `until`, so you can ask for e.g. "all NBA positions traded in the last 30 days" in one request:

```bash theme={null}
SINCE=$(( $(date -u +%s) - 86400 * 30 ))
curl "https://api.polynode.dev/v2/wallets/0x.../positions/onchain?tag_slug=nba&since=$SINCE" \
  -H "x-api-key: YOUR_KEY"
```

When the filter is supplied, aggregates recompute over the filtered set and the response gains `filtered: true` plus `applied_filters: { since, until, tag_slug }` so the behavior is self-documenting. Strictly additive — when no filter param is supplied, the response shape is byte-identical to before.

Coverage is effectively universal: tags are populated on every event Polymarket indexes (sampled empirically at 100% in our backfill audit), so any non-test market should return a populated `tag_slugs` array.

***

## 2026-04-26 — Unified Trades: additive `direction` (BUY/SELL) field on `/v2/onchain/trades`

[`GET /v2/onchain/trades`](/api-reference/onchain/trades) now also returns `direction` (`"BUY"` / `"SELL"`) on every row when filtered by `wallet` — same semantics as the wallet-trades endpoint, applied to the unified-trades flow. Independent of the existing `side` field. When no wallet anchor is supplied, `direction` is omitted (no perspective to derive against). Strictly additive: every other field is byte-identical to prior responses, and queries without `wallet` are unchanged.

Verified live: 1,214 trades cross-checked against Polymarket data-api `side` — zero drift.

***

## 2026-04-26 — Wallet Trades: additive `direction` (BUY/SELL) field

Every row of [`GET /v2/onchain/wallets/{address}/trades`](/api-reference/onchain/wallet-trades) now carries a `direction` field with values `"BUY"` or `"SELL"`, derived from whether the queried wallet contributed USDC or outcome tokens on the fill.

This is independent of the existing `side` field, which remains the exchange role (`"maker"` / `"taker"`) and is unchanged. The two answer different questions:

* `side` — did the wallet provide liquidity or take it?
* `direction` — did the wallet enter (BUY) or exit (SELL) outcome shares?

All four combinations (`maker`+`BUY`, `maker`+`SELL`, `taker`+`BUY`, `taker`+`SELL`) appear in real responses. Strictly additive — every other field is byte-identical to prior responses.

The information `direction` carries was always derivable from `maker_asset_id` / `taker_asset_id` (exactly one is `"0"` on every fill, that's the buyer). This change just makes the derivation explicit so analytics integrations don't have to compute it client-side.

***

## 2026-04-26 — Wallet Positions: per-position timestamps, parent event slug, opt-in window filter

Four new fields on every row of [`GET /v2/wallets/{address}/positions/onchain`](/api-reference/wallets/onchain-positions) (and the unified [`GET /v2/onchain/positions`](/api-reference/onchain/positions) feed). Two new optional query parameters for server-side window filtering. Every change is **strictly additive** — calls with no new parameters return responses byte-identical to before.

**New per-position fields:**

* **`last_trade_at`** — unix seconds. Latest fill on this token across V1 + V2 exchanges. The right field for "active in the last 30 / 90 days" leaderboard windows.
* **`closed_at`** — unix seconds. Latest moment the wallet redeemed any outcome of the market for collateral. Distinct from `resolved_at` — `resolved_at` is when the market itself resolved on-chain; `closed_at` is when this specific wallet actually redeemed.
* **`resolved_at`** — unix seconds. Moment the market became redeemable (oracle reported payouts on-chain). Recent markets are fully covered; markets that resolved before polynode began tracking return `null`.
* **`event_slug`** — the parent **event** slug, distinct from the per-row `slug` (which is the per-market slug). For multi-market events (NBA games with several lines, election markets with multiple candidates, FIFA World Cup with one market per team), `event_slug` is the parent the markets share. For single-market events, `event_slug` equals `slug`. Lets you group positions by event without an extra metadata round-trip.

**New optional query parameters** — opt-in only:

* **`?since=<unix_seconds>`** — keep positions whose `last_trade_at >= since`.
* **`?until=<unix_seconds>`** — keep positions whose `last_trade_at <= until`.

When either is supplied, all aggregates (`count`, `open_count`, `closed_count`, `total_realized_pnl`, `total_unrealized_pnl`, `total_pnl`, `positions_with_pnl`) recompute over the filtered set, and the response gains two top-level keys to make the shift self-documenting: `filtered: true` and `applied_filters: { since, until }`. These keys are absent from default responses, so existing integrations see zero behavior change.

```bash theme={null}
# 30-day window — leaderboard-style request
SINCE=$(( $(date -u +%s) - 86400 * 30 ))
curl "https://api.polynode.dev/v2/wallets/0x.../positions/onchain?since=$SINCE" \
  -H "x-api-key: YOUR_KEY"
```

See the [Positions & P\&L (Wallet)](/api-reference/wallets/onchain-positions) reference for the full field catalog and filtering rules.

***

## 2026-04-26 — X Search API beta

New paid endpoints for live X (Twitter) search and account-timeline data. Aimed at teams adding social context next to their prediction-market data — sentiment around a market, replies on a leader's post, what's being said about an event.

* **`GET /v2/x/search?q=…&max=…`** — search by query, with full operator support (`from:`, `since:`, `min_faves:`, hashtags, `lang:`).
* **`GET /v2/x/user/{handle}/tweets?max=…`** — most-recent tweets from any public X account.

Available on starter, growth, and enterprise tiers with monthly quotas of 500 / 1,000 / 5,000. Hard rate cap of 1 request per second per key. Track usage live via `X-Quota-Used` / `X-Quota-Limit` response headers.

See the [X Search API guide](/api-reference/x-search) for the full schema and examples.

***

## 2026-04-25 — Pricing section retired: candles deduplicated, "Market Card" introduced

The "Pricing" sidebar section has been removed. It contained two endpoints:

* **`GET /v1/stats/{token_id}`** — kept, renamed to **Market Card**, moved further down the sidebar (just above "System"). This one is genuinely useful: a single call returns the condition\_id, outcomes, neg-risk flag, end date, current orderbook liquidity, 24h OHLCV summary, and last price. Saves you from chaining 3–4 calls when you just need a market overview tile.
* **`GET /v1/candles/{token_id}`** — removed from documentation. It was an in-memory rolling buffer with no historical depth, no pagination, no VWAP, and no buy/sell split. Use **`GET /v2/onchain/candles/{token_id}`** instead — same OHLCV plus VWAP, volume in shares, trade count, full history, and pagination. The endpoint itself still responds on the API for backward compatibility.

***

## 2026-04-25 — Wallet endpoints reorganized: V1 wallet endpoints removed from documentation

The "Wallets" and "Onchain" sidebar sections have been merged into a single section called **Wallets / Positions / Onchain**. All wallet, position, and trade endpoints now live in one place — the V2 onchain endpoints.

**What changed in the docs:**

The seven legacy V1 wallet/market endpoints have been removed from the documentation sidebar. They were inferior duplicates of the V2 onchain equivalents — three of them returned empty or error responses, and the rest lacked PnL, average-price, current-price, and market-status fields that the V2 versions provide directly from chain data.

| Removed from docs                          | Use instead                                          |
| ------------------------------------------ | ---------------------------------------------------- |
| `GET /v1/wallets/{addr}/positions`         | `GET /v2/onchain/positions?wallet=…`                 |
| `GET /v1/wallets/{addr}/closed-positions`  | `GET /v2/onchain/positions?wallet=…&status=closed`   |
| `GET /v1/wallets/positions` (multi-wallet) | Loop `GET /v2/onchain/positions?wallet=…` per wallet |
| `GET /v1/wallets/{addr}/trades`            | `GET /v2/onchain/wallets/{address}/trades`           |
| `GET /v1/markets/{id}/positions`           | `GET /v2/onchain/positions?token_id=…`               |
| `GET /v1/markets/{id}/trades`              | `GET /v2/onchain/markets/{token_id}/trades`          |
| `GET /v1/resolve/{query}`                  | `GET /v2/resolve/{query}`                            |

**Important — backward compatibility preserved:**

The V1 endpoints **still respond on the API**. We have not turned them off. If you have integrations hitting the legacy paths, they continue to work exactly as before — we just stopped surfacing them in the documentation because the V2 onchain replacements are strictly better. You should migrate, but you don't have to migrate today.

**Why V2 onchain is better:**

V1 wallet endpoints returned mostly social-flavored fields (bio, profile picture, pseudonym). V2 onchain endpoints return rich trading-relevant fields: `avg_price`, `realized_pnl`, `unrealized_pnl`, `current_price`, `total_bought`, `market_status`, `order_hash`, `fee`, `maker_amount`, `taker_amount`, `condition_id`, and more. Same data your equity curve, leaderboard, and PnL endpoints already use.

***

## 2026-04-25 — Equity Curve endpoint: faster default + four new query parameters

The `/v2/trader/{wallet}/equity` endpoint is faster, more flexible, and more accurate.

**Default is now realized-only.** The curve is built from completed P\&L by default — the locked-in result of every closed position, partial sell, and resolution. Same query, same numbers, every time. Whales return in 1–2 seconds (was 8+).

If you want mark-to-market on currently-open positions (the old behavior), pass `include_unrealized=1`. That path is still supported, just opt-in now because it adds latency and makes responses non-deterministic as live prices move.

**Four new optional query parameters:**

| Param                  | Description                                                 |
| ---------------------- | ----------------------------------------------------------- |
| `from=YYYY-MM-DD`      | Start date (also accepts unix seconds).                     |
| `to=YYYY-MM-DD`        | End date (same format).                                     |
| `max_markets=N`        | Keep only the most recent N markets the wallet has touched. |
| `include_unrealized=1` | Mark-to-market open positions (was the old default).        |

All four compose. Example — fast realized-only curve over the wallet's last 50 markets in early April:

```bash theme={null}
curl "https://api.polynode.dev/v2/trader/{wallet}/equity?from=2026-04-01&to=2026-04-15&max_markets=50&normalize=1" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**New response fields:** `markets_count` (distinct outcome groups in the curve), `applied_filters` (echoes what was applied, useful for debugging), and `partial` (true if the request hit the 10-second server-side cap).

**Coverage fix.** Wallets with more than 500 lifetime trades previously had positions whose first-trade timestamp wasn't found, causing those positions to be incorrectly stamped at "now" on the curve. The endpoint now walks all relevant trade history and falls back to sibling-token and redemption timestamps for positions acquired through USDC splits. Curves are dramatically more accurate for active traders.

Full reference: [/api-reference/enriched/equity-curve](/api-reference/enriched/equity-curve).

***

## 2026-04-24 — Fee Escrow V2 live on Polygon (pUSD collateral, V2 CLOB)

We've deployed a V2 variant of the Fee Escrow contract so platforms building on the V2 CLOB can charge fees in pUSD (V2's native collateral) instead of USDC.e. Both V1 and V2 are live and fully supported — there is no migration. If you're already running V1, nothing changes.

**FeeEscrow V2 (new):**

| Field          | Value                                                          |
| -------------- | -------------------------------------------------------------- |
| Address        | `0x3A43D88ef8Aae4dF5a50B3abf67122CAAeEF7c9F`                   |
| Collateral     | pUSD `0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB` (6 decimals) |
| EIP-712 domain | `name="PolyNodeFeeEscrowV2"`, `version="2"`, chainId 137       |

**FeeEscrow V1 (unchanged):**

| Field          | Value                                                  |
| -------------- | ------------------------------------------------------ |
| Address        | `0xa11D28433B79D0A88F3119b16A090075752258EA`           |
| Collateral     | USDC.e `0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174`    |
| EIP-712 domain | `name="PolyNodeFeeEscrow"`, `version="1"`, chainId 137 |

The contracts have byte-identical calldata, identical function signatures, and identical semantics. The only functional differences are the collateral token and the domain name / version. The `FeeAuth` typed-data struct is unchanged.

**Opting into V2 today:**

The cosigner accepts an `escrow_contract` field in `fee_auth`. Absent or set to the V1 address = V1 (default). Set to the V2 address = V2. Sign the `FeeAuth` against the V2 domain when using V2.

```jsonc theme={null}
"fee_auth": {
  "escrow_contract": "0x3A43D88ef8Aae4dF5a50B3abf67122CAAeEF7c9F",
  "escrow_order_id": "0x...",
  "payer":  "0xSafe...",
  "signer": "0xEOA...",
  "fee_amount": "27500",
  "deadline": 1777000000,
  "nonce": 0,
  "signature": "0x...",                   // signed against the V2 domain
  "affiliate": "0xPartnerWallet...",
  "affiliate_share_bps": 10000
}
```

**SDKs:** V2 fee escrow is wired up in all three SDKs. Set `exchangeVersion` to V2 in your `TraderConfig` and the SDK automatically signs `FeeAuth` against the V2 domain, reads nonces from the V2 contract, approves pUSD against the V2 FeeEscrow during `ensureReady()`, and tells the cosigner to route to the V2 operator. Install:

```bash theme={null}
npm install polynode-sdk@0.9.2
pip install "polynode==0.9.2"
cargo add polynode@0.12.2
```

V1 customers: no changes, V1 stays the default. Omit `exchangeVersion` (or set it to V1) and everything keeps working exactly as before.

**Validation:** 8/8 end-to-end fork tests pass against the deployed V2 contract, exercising pullFee + distribute (30/70 treasury/affiliate split) + refund + 72-hour claimRefund + every revert path (expired deadline, wrong nonce, tampered signature). The test impersonates a real on-chain pUSD whale to fund the payer. Contract bytecode is a strict 2-change diff from V1 (collateral address + domain strings).

Full guide: [/guides/fee-escrow](/guides/fee-escrow).

***

## 2026-04-21 (late) — Unrealized P\&L and market status on positions

The `/v2/onchain/positions` and `/v2/wallets/{address}/positions/onchain` endpoints now return unrealized P\&L, the current market price, and a status flag per position — so you don't have to fetch prices separately or special-case resolved markets in client code.

**New per-position fields:**

* `unrealized_pnl` — paper P\&L on the held size at the current price. For resolved markets, uses the settlement price (1.0 for winners, 0.0 for losers). For live markets, uses the current market price. Returns 0 when `size = 0`.
* `current_price` — the price used to compute `unrealized_pnl`. `null` when `size = 0` or no price is available.
* `market_status` — one of `live`, `resolved-win`, `resolved-loss`, or `closed`. Lets you tell apart a live-tradable holding from a resolved-but-never-redeemed position without looking up the market separately.

**New portfolio totals** on `/v2/wallets/{address}/positions/onchain`:

* `total_unrealized_pnl`
* `total_pnl` — `total_realized_pnl + total_unrealized_pnl`. This is the number that matches the "Profit" value shown on a Polymarket profile page.

Drop-in — every existing field keeps its name and type.

***

## 2026-04-21 (late) — Gasless Safe wrap/unwrap via the Polymarket relayer

Patch release. `wrapToPolyUsd` / `unwrapFromPolyUsd` (and their Python/Rust equivalents) now route through the Polymarket relayer automatically when called on a Safe or Proxy wallet — the flow `ensureReady` already uses. Previously these helpers sent the transaction directly from the EOA, which failed for Safe wallets because the funds live in the Safe, not on the EOA, and the EOA has no MATIC for gas.

**Install:**

```bash theme={null}
npm install polynode-sdk@0.9.1       # TypeScript
pip install "polynode>=0.9.1"        # Python
cargo add polynode@0.12.1            # Rust
```

**Verified end-to-end with real Safe-via-relayer transactions:**

* TS: wrap `0xde6c02c3...`, unwrap `0x006cd3dd...`
* Python: wrap `0x7de53811...`, unwrap `0xccf9cf32...`
* Rust: wrap `0xd80af0f4...`, unwrap `0xd21d6ad6...`

All six txs mined on Polygon with `status: 0x1`, funded by third-party relayer EOAs — your EOA only signs the Safe `execTransaction` EIP-712 payload.

**Requirements for Safe wrap/unwrap:** pass `builderCredentials` in `TraderConfig`. The SDK uses your Polymarket builder HMAC key to authenticate the relayer submit. Mint one at [polymarket.com/settings?tab=builder](https://polymarket.com/settings?tab=builder).

**Tip — avoid stale-cache order rejection after wrap:** after `wrapToPolyUsd()`, the V2 CLOB's cached balance view is stale for a few seconds. If you place an order immediately, the CLOB can return a cryptic `"error parsing fee rate bps"` instead of the actual "balance not yet visible". Call `await trader.refreshBalanceAllowance()` before the order, or wait \~3 seconds. This is why we don't auto-refresh in wrap — not every wrap is followed by an immediate order.

**Also in this patch:**

* **Rust:** fixed `encode_unwrap` selector (was `0x39f47693`, correct value is `0x8cc7104f`). All Rust V2 unwraps prior to 0.12.1 hit the wrong contract function and reverted on-chain.
* **Rust:** `get_polyusd_balance()` / `get_usdce_balance()` now read the funder address (Safe) instead of the EOA. Previously they always returned 0 for Safe-wallet users.
* **Python:** `relayer.py` module added with a minimal Python port of `@polymarket/builder-relayer-client`. Implements EIP-712 Safe tx hashing, EIP-191 personal\_sign via `eth_account`, builder HMAC headers, and `/submit` + `/transaction` polling.

***

## 2026-04-21 — SDK V2 order flow hardening (all three SDKs)

End-to-end V2 order placement verified live on `clob-v2.polymarket.com` across all three SDKs. Matched fills tagged with your builder code now appear in the V2 CLOB's `/builder/trades` attribution feed.

**Install:**

```bash theme={null}
npm install polynode-sdk@0.9.0       # TypeScript
pip install "polynode>=0.9.0"        # Python
cargo add polynode@0.12.0            # Rust
```

**What changed:**

* **V2 approvals now include the V1 `NegRiskAdapter`** in the spender list. The V2 CLOB's balance-allowance check monitors this address alongside the two V2 exchanges — orders without this approval were rejected with `"not enough balance / allowance"`.
* **New methods: `refreshBalanceAllowance()` / `getBalanceAllowance()`.** The V2 CLOB maintains a cached balance-allowance view per API key; `ensureReady()` now auto-refreshes it after setting V2 approvals so the first order after onboarding goes through.
* **Amount math hardened against float-precision edge cases.** Raw-units now derived via string-based decimal conversion instead of `Math.trunc(x * 1e6)` (TS) / `(x * 1_000_000.0) as u64` (Rust), which could truncate to `N-1` for prices that round-trip badly through float.
* **V2 order body now matches `@polymarket/clob-client-v2` exactly** — adds `taker`, `postOnly`, `deferExec` fields to the POST payload.
* **`checkBalance()` is V2-aware** — reads pUSD on V2, USDC.e on V1. Previously always read USDC.e.
* **Market neg-risk detection fix (TS).** The old check treated every market as neg-risk (truthiness of the response object rather than its `neg_risk` field), which signed orders against the wrong exchange on standard markets.

**Migration:** drop-in. `trader.order(...)` signature is unchanged; if you're on V2 and your orders were going through before, they still will. If you had a custom `ensureReady`-equivalent flow, you may now drop the manual `/balance-allowance/update` call — `ensureReady()` handles it on fresh V2 approvals.

The full V2 wire format, required approvals, fee math, and common failure modes are documented in each SDK at `src/trading/V2_ORDER_FLOW.md` (TS, Rust) / `polynode/trading/V2_ORDER_FLOW.md` (Python).

**End-to-end verification (from each public registry, fresh sandbox, real V2 orders against `clob-v2.polymarket.com`):**

| SDK                  | Installed from                  | V2 order placed      | Cancelled |
| -------------------- | ------------------------------- | -------------------- | --------- |
| `polynode-sdk@0.9.0` | `npm install polynode-sdk`      | `0xb088a702...` LIVE | ✓         |
| `polynode 0.9.0`     | `pip install polynode[trading]` | `0x4906757c...` LIVE | ✓         |
| `polynode 0.12.0`    | `cargo add polynode@0.12`       | `0x6d3623c6...` LIVE | ✓         |

Each sandbox pulled from the public registry, imported the SDK, placed a real V2 GTC BUY with builder attribution, confirmed `status: "live"` from the V2 CLOB, then cancelled. No local source overrides, no pre-compiled artifacts — same install path a new user follows.

**Docs fix also shipped today:** the V2 section of `/sdks/trading` now inlines `ensureReady` / `ensure_ready` so a V2-first reader who pastes only that block hits a copy-paste runnable flow. Previously the snippet silently assumed the reader had already gone through the Quick Start section.

***

## 2026-04-20 — CLOB v2 API launched

New paid REST namespace `/clobv2/*` exposing every Polymarket CLOB v2 fill, position, builder, and neg-risk event.

**11 endpoints, all enriched with market metadata (question, slug, outcome, image, condition\_id) inline:**

* `GET /clobv2/trades` — global fills, filterable by wallet/builder/market/time/size
* `GET /clobv2/positions` — every open/closed v2 position
* `GET /clobv2/candles/{token_id}` — OHLCV at 1m/5m/15m/1h/4h/1d with VWAP + buy/sell split
* `GET /clobv2/wallets/{address}/trades` — per-wallet fill history with `side`
* `GET /clobv2/markets/{token_id}/{trades,volume,orderbook}` — per-outcome aggregates
* `GET /clobv2/builders` — v2 builder leaderboard (v2-exclusive)
* `GET /clobv2/neg-risk/events{,/{parent_id}/children}` — multi-outcome event drilldown
* `GET /clobv2/volume/hourly` — platform-wide hourly volume buckets

Every list response includes `count`, `pagination.{limit,offset,has_more}`, and rate-limit headers. Paid tier required; free-tier keys receive `402`. See the [overview](/api-reference/clobv2/overview) for auth + numeric-precision conventions.

**`/v2/onchain/*` enrichment restored**

Trades / wallet-trades / market-trades / market-volume / candles on the legacy `/v2/onchain/*` endpoints now carry `market`, `market_slug`, `outcome`, `image`, and `condition_id` inline again. These fields had been quietly lost during a backend refactor.

***

## 2026-04-19 — Expanded orderbook wire format + Rust SDK 0.11.0

The orderbook WebSocket now delivers PM's full per-level payload so you can maintain a tick-accurate local book without REST polling. Verified at **100% best-bid / 100% best-ask** across 50 concurrent markets.

**What's new in the `price_change` message**

Every entry in the `assets` array now includes four extra fields:

* `size` — the absolute size at that level after the change. `"0"` means the level was removed.
* `side` — `"BUY"` (affects bids) or `"SELL"` (affects asks)
* `best_bid`, `best_ask` — best bid/ask on the asset after the change

Old clients parsing only `{asset_id, price}` are unaffected — the new fields are additive. Multiple distinct levels on the same asset may now appear in one batch; repeat hits on the same `(asset_id, price, side)` within the 250ms coalesce window still merge to last-write-wins.

See the [orderbook message reference](/orderbook/messages) for the full field list.

**Rust SDK 0.11.0**

* `PriceChangeAsset` now carries `size`, `side`, `best_bid`, `best_ask`
* New `LocalOrderbook::apply_price_change(&PriceChange)` — apply incremental updates directly to your local book
* `OrderbookEngine` automatically applies `price_change` events to its shared state so every `EngineView` stays tick-accurate
* `OrderbookUpdate::LastTradePrice(BookTrade)` — executed-trade events now deserialize cleanly (previously dropped)

**Install:**

```bash theme={null}
cargo add polynode@0.11.0
```

**Breaking:** none. Enum variants added are additive under existing serde routing. Explicit exhaustive matches on `OrderbookUpdate` will need to handle the new `LastTradePrice` variant — add a `_ => {}` arm or match it explicitly.

***

## 2026-04-19 — V2-ready SDKs released (Rust 0.9.0, TypeScript 0.8.0, Python 0.8.0)

All three polynode trading SDKs are now wire-compatible with the Polymarket V2 exchange ahead of the **April 28, 2026 cutover**. Upgrade before then. V1 orders submitted after cutover will be rejected with `order_version_mismatch`.

**What changed (all three SDKs):**

* V2 CLOB POST body now includes `"expiration": "0"` to match `@polymarket/clob-client-v2`'s wire format
* `OrderParams` now exposes a `builder` field (bytes32). Pass your builder code on each order to attribute trades on-chain. Defaults to zero (no attribution). Mint your V2 builder code at [polymarket.com/settings?tab=builder](https://polymarket.com/settings?tab=builder)
* All V2 order placement paths live-verified against `clob-v2.polymarket.com`

**Install:**

```bash theme={null}
cargo add polynode@0.9.0          # Rust
npm install polynode-sdk@^0.8.0   # TypeScript
pip install "polynode>=0.8.0"     # Python
```

**Breaking (Rust only):** `OrderParams` added a required struct field. If you construct `OrderParams { ... }` explicitly without `..Default::default()`, add `builder: None` to your literal. No code change required if you use struct update syntax.

See the [V2 Migration Guide](/guides/v2-migration) for the full cutover checklist, the single-line switch, and a troubleshooting reference for common V2 errors.

***

## 2026-04-18 — Rust SDK 0.8.1: batch orderbook API

### New: batch + all-tracked queries

Every per-token orderbook method on `OrderbookEngine` and `EngineView` now has a batch and an all-tracked variant. One read lock, one round-trip, results returned as a `HashMap<String, T>` keyed by token ID.

```rust theme={null}
let mids   = engine.midpoints(&ids).await;       // requested tokens
let mids   = engine.midpoints_all().await;        // every tracked token

// Same shape for: spreads / best_bids / best_asks / books
let books  = engine.books_all().await;            // full L2 for everything
```

Tokens not in local state are silently skipped, so callers can pass mixed lists without pre-filtering.

### New: detect inactive markets

Each tracked token now records when its local copy was last touched. Use it to find markets that have stopped moving.

```rust theme={null}
use std::time::Duration;

if let Some(ts) = engine.last_change("token_a").await {
    println!("last update {:?} ago", ts.elapsed());
}

let stale = engine.inactive_since(Duration::from_secs(60)).await;
```

### New: direct state access

`engine.state()` returns the underlying `Arc<RwLock<LocalOrderbook>>` so callers can hold the lock and walk the full state themselves — useful for snapshotting all tokens at one consistent moment or building custom views.

```rust theme={null}
let state = engine.state();
let guard = state.read().await;
let mids  = guard.midpoints_all();
let books = guard.books_all();
```

### Install

```bash theme={null}
cargo add polynode@0.8.1
```

All existing per-token methods (`engine.midpoint(id)`, `engine.book(id)`, etc.) are unchanged. This release is purely additive.

***

## 2026-04-17 — Unrealized P\&L + redemption fix

### New: `unrealized_pnl` field on positions

Every position returned by `/v2/onchain/positions` now includes an `unrealized_pnl` field showing the paper profit or loss on remaining open shares.

* **Resolved markets**: uses the final settlement price ($1 for winners, $0 for losers)
* **Active markets**: uses the current market price, refreshed every 15 minutes

### Fixed: realized P\&L for redeemed positions

Positions that were closed via onchain payout redemption (rather than selling on the orderbook) now correctly reflect their realized P\&L. Previously, these showed `realized_pnl: 0` even when the position had a gain or loss.

***

## 2026-04-15 — Trade-based candles endpoint

### New: `GET /v2/onchain/candles/{token_id}`

Server-built OHLCV candles from real onchain fills. Each candle includes open, high, low, close, total volume in USD and shares, separate buy and sell volume, trade count, and VWAP.

Pagination is anchor-based: each request returns up to 1000 trades worth of candles, anchored at a timestamp, block number, or transaction hash. Walk older history by passing the cursor from the previous response. Same model used by major exchange APIs.

```bash theme={null}
curl "https://api.polynode.dev/v2/onchain/candles/$TOKEN_ID?resolution=1h" \
  -H "x-api-key: YOUR_KEY"
```

Resolutions: `1m`, `5m`, `15m`, `1h`, `4h`, `1d`. Optional `gap_fill=true` for charting libraries that expect a continuous time axis. Full reference at [/api-reference/onchain/candles](/api-reference/onchain/candles).

***

## 2026-04-14 — Positions sort fix + `last_activity` field

### Fixed: `/v2/onchain/positions` wallet queries now sort by recency

When querying positions for a specific `wallet`, results are now ordered by the most recent on-chain activity for each position. Previously the `order=desc` parameter did not reflect recency, causing newer positions to appear below older ones.

The most recently traded positions now appear at the top. Positions with no fill history (rare — acquired purely via split, merge, or redemption) are placed at the end.

### New: `last_activity` response field

Wallet queries now include a `last_activity` field on each position, containing the unix timestamp of the most recent fill for that position. Use it to display "last traded" times in your UI.

```json theme={null}
{
  "wallet": "0x4aefd329896464da0ffb16c4ebcd083a4360c181",
  "token_id": "99969794444523158222638792918531145371262906249733117976401662588024805483979",
  "size": 0,
  "status": "closed",
  "market": "Suns vs. Lakers",
  "market_slug": "nba-phx-lal-2026-04-10",
  "outcome": "Lakers",
  "last_activity": 1776232830
}
```

### Pagination behavior

Wallet queries return every position for the wallet in a single response (up to 500), ordered by recency. `has_more` is always `false` and no cursor is returned — request `limit=500` once and read all results. Wallets with more than 500 lifetime positions are capped.

Non-wallet queries (filtering by `market_slug`, `condition_id`, `token_id`, or `min_size` alone) continue to use cursor-based pagination.

***

## 2026-04-12 — SDK v0.7.0: Fee Escrow + Order History

### New: Per-Order Fee Collection

The trading SDK now supports optional per-order fee collection with on-chain escrow. Platforms can charge fees on trades with a single config option.

```bash theme={null}
npm install polynode-sdk@0.7.0
```

```typescript theme={null}
const trader = new PolyNodeTrader({
  polynodeKey: 'pn_live_...',
  feeConfig: { feeBps: 50 },  // 0.5% fee
});

const result = await trader.order({
  tokenId: '...',
  side: 'BUY',
  price: 0.55,
  size: 100,
});

console.log(result.feeEscrowTxHash); // on-chain TX hash
console.log(result.feeAmount);       // "0.0275" USDC
```

* **Automatic fee handling** -- fees are pulled into escrow before the order, distributed on fill, refunded on cancel
* **Cancel auto-refund** -- cancelling an order automatically returns the fee to the user's wallet, inline in the same request
* **Affiliate revenue sharing** -- split fees with partners on a per-order basis via `affiliate` and `affiliateShareBps`
* **72-hour safety net** -- if the operator doesn't settle, users can self-refund on-chain
* **Zero overhead when disabled** -- `feeBps: 0` (default) skips the escrow entirely with no behavior change
* **7th approval** -- added to `ensureReady()` batch, gasless for Safe wallets

### Improved: Order History

Local order history now persists fee escrow data (`feeAmountRaw`, `escrowOrderId`, `feeEscrowTxHash`). Existing databases auto-migrate on first open. No action required.

### SDK Releases

* **TypeScript** `polynode-sdk@0.7.1` on [npm](https://www.npmjs.com/package/polynode-sdk) — adds `FeeConfig` export
* **Rust** `polynode@0.7.1` on [crates.io](https://crates.io/crates/polynode) — fixes compile error in 0.7.0
* **Python** `polynode@0.7.2` on [PyPI](https://pypi.org/project/polynode/) — fixes `__version__` reporting

See the [Fee Escrow Guide](/guides/fee-escrow) for the full architecture, security model, and code examples.

***

## 2026-04-11 — polynode-charts v0.1.5

### New: `createShortFormOverlay`

One-liner to add price-to-beat overlays to any live chart. Renders interval buttons (5m/15m/1h), auto-discovers Polymarket short-form markets, draws a dashed price line, and shows live odds with a countdown timer.

```typescript theme={null}
import { createChart, createShortFormOverlay } from 'polynode-charts'

const chart = createChart('#btc')
const series = chart.addLineSeries({ color: '#f7931a' })
series.setLive(true)
chart.timeScale().goLive()

createShortFormOverlay(chart, series, { coin: 'btc' })
```

### Improvements

* **Multi-outcome event discovery** now prioritizes genuine multi-outcome markets (`neg_risk: true`) over resolved binary matches
* **`MarketInfo.outcome`** field identifies which side of a binary market each token represents ("Yes" or "No")
* **Price-to-beat badge** renders correctly on the right axis for all price ranges
* **Orderbook tooltip** no longer stays stuck after data refresh
* **Tighter chart padding** for a denser, more professional look

***

## 2026-04-11 — polynode-charts v0.1.0

New npm package for interactive charting in the browser. Zero dependencies, Canvas 2D rendering at 60fps, purpose-built for prediction market data and live crypto price streams.

### Install

```bash theme={null}
npm install polynode-charts
```

### What's included

* **Candlestick, line, area, and volume** series types with smooth animation
* **Live streaming mode** with lerp animation, pulsing dots, and auto-scroll
* **Orderbook visualization** with depth chart and spread display
* **Short-form market overlays** — price-to-beat lines with live odds rotation for 5m/15m/1h crypto markets
* **Multi-outcome support** for prediction markets (up to 20+ outcomes with auto-assigned colors)
* **Built-in data providers** — `PolynodeProvider` (REST), `PolynodeOBProvider` (WebSocket orderbook), `ShortFormProvider` (market discovery + rotation)
* **Interactive** pan, zoom, scroll, pinch-to-zoom on mobile
* **Crosshair** with snap-to-candle and OHLC tooltip

### Quick start

```typescript theme={null}
import { createChart, PolynodeProvider } from 'polynode-charts'

const provider = new PolynodeProvider({ apiKey: 'pn_...' })
const chart = createChart('#chart', {
  rightPriceScale: { mode: 'probability' },
})

const series = chart.addCandleSeries({
  upColor: '#22c55e',
  downColor: '#ef4444',
})

const data = await provider.candles(tokenId, '1h')
series.setData(data)
```

### Documentation

Full docs at [Charts SDK Overview](/charts/overview), including API reference, data providers, series types, examples, and configuration.

<Note>
  `polynode-charts` is the **browser visualization** companion to `polynode-sdk` (the Node.js data SDK). They are separate npm packages. Building a frontend? Install both.
</Note>

***

## 2026-04-09 — Confirmed Fills on Status Updates

`status_update` events now include a `confirmed_fills` array with exact execution data from on-chain `OrderFilled` receipt logs. This is the same data source Polymarket's own APIs read.

### What changed

* `status_update` events now carry a `confirmed_fills` field — an array of per-fill objects with exact price, size, fee, order hash, maker, taker, and token ID
* Each fill is decoded directly from `OrderFilled` receipt logs on the CTF Exchange and NegRisk CTF Exchange contracts
* Prices match Polymarket's activity data exactly (verified across 1,300+ fills with zero price discrepancies)

### Why this matters

polynode detects settlements 3-5 seconds before on-chain confirmation by decoding transaction calldata from the mempool. For single-maker fills, calldata prices are exact. For multi-maker fills (\~5% of trades), the calldata only has aggregate amounts, so per-maker prices are estimated within 0.01-0.04.

With `confirmed_fills`, you now get both: the fast pre-confirmation signal AND the exact on-chain execution data in a single subscription. Use whichever fits your use case.

### Use cases

* **Copy trading**: Use the pending `settlement` event (speed matters, price difference is negligible)
* **Analytics and bookkeeping**: Use `confirmed_fills` from the `status_update` (exact prices, fees, and sizes)
* **Full lifecycle**: Track both to compare pre-confirmation estimates against final execution

### Size difference vs Polymarket

`confirmed_fills` reports gross token amounts from the OrderFilled event. Polymarket's activity API reports sizes net of fees. The `fee` field on each fill lets you compute the net amount if needed.

### Subscribe

No subscription changes needed. If you already subscribe to `settlements`, you're already receiving `status_update` events with `confirmed_fills`.

```json theme={null}
{"action": "subscribe", "type": "settlements"}
```

### Documentation

* [Status Update Event Reference](/websocket/events/status-update) — updated with `confirmed_fills` schema and examples
* [Trade Tracking Guide](/guides/trade-tracking) — when to use pending settlements vs confirmed fills

***

## 2026-04-08 — Redemption Event Streaming

New `redemption` event type in the WebSocket stream. Every time a user redeems their outcome tokens after a market resolves, you get a real-time event with the redeemer address, payout amount, condition ID, and full market metadata enrichment.

### WebSocket Stream

* New event type: `redemption` — available in the firehose and via `event_types` filter
* Decoded from `PayoutRedemption` logs on the Conditional Tokens contract
* Includes payout amount in USDC, redeemer wallet, outcome slots redeemed
* Enriched with market title, slug, image, tokens map, neg\_risk flag
* Filter with `min_size` to see only winning redemptions, or track specific wallets

### Subscribe

```json theme={null}
{"action": "subscribe", "type": "settlements", "filters": {"event_types": ["redemption"]}}
```

### Documentation

* [Redemption Event Reference](/websocket/events/redemption) — full field reference, examples, and use cases

***

## 2026-04-07 — Polymarket V2 Exchange Support

polynode now supports the Polymarket V2 exchange system alongside V1. All V2 contracts have been identified, decoded, and verified against live mainnet data.

### Settlement Stream

* V2 settlements are detected and streamed automatically — no subscription changes needed
* Same event types: `settlement`, `status_update`, `trade`, `deposit`
* PolyUSD wrapping/unwrapping events now included in deposit stream
* V2 detection is automatic alongside V1 (both supported simultaneously)

### Trading SDK (Rust v0.6.0, TypeScript v0.6.0, Python v0.7.0)

* New `ExchangeVersion::V2` / `exchangeVersion: "v2"` config option
* V2 order signing with updated EIP-712 domain (version "2")
* `wrapToPolyUsd()` and `unwrapFromPolyUsd()` helper methods
* `getPolyUsdBalance()` and `getUsdceBalance()` balance checking
* V2 approval management (PolyUSD to V2 exchange contracts)
* One config change to switch from V1 to V2 — everything else stays the same

### Documentation

* [V2 Migration Guide](/guides/v2-migration) — what changed, what stayed the same, SDK examples
* [PolyUSD Guide](/guides/polyusd) — how to wrap/unwrap PolyUSD with code examples
* [V2 Technical Details](/guides/v2-details) — contract addresses, order struct, event signatures (subscribers only)
* Deposit and settlement event pages updated with V2 notes

### What We Verified

* V2 order placement tested live on the V2 CLOB
* EIP-712 order hash matches the live V2 exchange contract on Polygon mainnet
* PolyUSD wrapping detected through our event pipeline in real time
* All existing market data, token IDs, and enrichment identical between V1 and V2

***

## 2026-04-05 — Short-Form Docs: Full Field Reference + Orderbook Guide

Updated the [Short-Form Markets](/sdks/short-form) documentation with the complete `ShortFormMarket` field reference. All 14 fields are now documented, including `conditionId`, `clobTokenIds`, `windowStart`, `windowEnd`, `outcomes`, and `outcomePrices` which were previously undocumented.

Added a new **"Connect the Orderbook to Crypto Markets"** section showing how to pass `clobTokenIds` from a short-form rotation event directly to `OrderbookEngine` for real-time depth data on crypto prediction markets. Includes TypeScript and Rust examples.

No SDK changes. All fields were already available in the SDK — this is a docs-only update.

***

## 2026-04-04 — Crypto Price Fix (All SDKs)

Fixed stale price-to-beat values in short-form crypto markets. Polymarket changed the variant parameter on their crypto-price endpoint for 5-minute and hourly markets. The SDK now sends the correct values, matching what Polymarket's own frontend uses.

**Affected intervals:** 5-minute and hourly. 15-minute was unaffected.

```bash theme={null}
npm install polynode-sdk@0.5.9    # TypeScript
cargo add polynode@0.5.8          # Rust
pip install polynode==0.6.2       # Python
```

***

## 2026-04-04 — TypeScript SDK v0.5.8

Fixed a bug where `price_feed` events from Chainlink subscriptions were silently dropped in the TypeScript SDK. The `.on('price_feed', ...)` handler now fires correctly.

```typescript theme={null}
const sub = await pn.ws
  .subscribe('chainlink')
  .feeds(['BTC/USD'])
  .send();

sub.on('price_feed', (event) => {
  console.log(`${event.feed}: $${event.price}`);
});
```

Update with `npm install polynode-sdk@0.5.8`.

***

## 2026-04-02 — Position Management (All SDKs)

Split, merge, and convert positions directly from the SDK. No need to interact with smart contracts manually.

**TypeScript** — gasless execution via the Polymarket relayer:

```typescript theme={null}
await trader.split({ conditionId: '0x...', amount: 100 });
await trader.merge({ conditionId: '0x...', amount: 100 });
await trader.convert({ marketId: '0x...', outcomeIndices: [0, 1], amount: 100 });
```

**Rust & Python** — transaction builders that return ready-to-submit calldata:

```python theme={null}
tx = build_split_txn("0x...", 100.0, neg_risk=True)
tx = build_convert_txn("0x...", [0, 1], 100.0)
```

* [Position Management guide](/guides/position-management) — full walkthrough with examples
* Convert is unique to neg-risk multi-outcome markets (e.g. "Who will win the World Cup?")
* TypeScript SDK handles neg-risk vs standard market routing automatically

***

## 2026-04-02 — Positions Converted Event (WebSocket API)

New `positions_converted` event type for tracking position conversions on neg-risk multi-outcome markets.

When a trader converts NO positions into USDC + YES positions on complementary outcomes (via the NegRiskAdapter `convertPositions` function), this event fires with the full decoded details.

```json theme={null}
{
  "data": {
    "event_type": "positions_converted",
    "event_title": "Hungary Parliamentary Election Winner",
    "stakeholder": "0x30cecdf29f069563ea21b8ae94492e41e53a6b2b",
    "converted_outcomes": ["Fidesz-KDNP", "TISZA"],
    "amount": 98,
    "market_id": "0x355e7310dd6e18ef5fa456de7ce1331bd8c7540c...",
    "index_set": "0x...0021",
    "neg_risk": true
  }
}
```

* `converted_outcomes` decodes the bitmask into human-readable outcome names
* `event_title` shows the parent multi-outcome event
* Included by default in `wallets` and `markets` subscription types
* Add explicitly via `event_types: ["positions_converted"]` for other subscription types
* Fires \~13 times per minute across all active neg-risk markets

***

## 2026-04-01 — Builder Credentials (All SDKs)

### TypeScript SDK v0.5.7 / Rust SDK v0.5.7 / Python SDK v0.6.1

Platforms can now pass their own Polymarket builder credentials for order attribution. All volume gets credited to your builder profile on the [Builder Leaderboard](https://builders.polymarket.com).

```typescript theme={null}
const trader = new PolyNodeTrader({
  polynodeKey: 'pn_live_...',
  builderCredentials: {
    key: process.env.POLY_BUILDER_API_KEY,
    secret: process.env.POLY_BUILDER_SECRET,
    passphrase: process.env.POLY_BUILDER_PASSPHRASE,
  },
});
```

* Get your builder credentials at [polymarket.com/settings?tab=builder](https://polymarket.com/settings?tab=builder)
* polynode never stores your builder credentials. They're used per-request for HMAC signing only.
* When omitted, polynode's default builder attribution is used (orders still go through).

Install:

```bash theme={null}
npm install polynode-sdk@0.5.7    # TypeScript
cargo add polynode@0.5.7          # Rust
pip install polynode==0.6.1       # Python
```

***

## 2026-03-31 — TypeScript SDK v0.5.6 (Cache Crash Recovery)

### TypeScript SDK v0.5.6

Fixes a crash that could occur when stopping the cache mid-backfill, and adds proper crash recovery for interrupted backfills.

**Graceful shutdown**: `cache.stop()` now waits for any in-flight backfill operation to complete before closing the database. Previously, stopping during an active backfill could crash with a `Cannot read properties of null` error on the SQLite handle.

**Crash recovery**: on startup, any backfills that were interrupted by a previous crash or kill are automatically detected and retried. The cache logs exactly what's happening:

```
# Restart after crash — resumes incomplete backfills only
[PolyNodeCache] Reset 1 interrupted backfill(s) from previous session.
[PolyNodeCache] Backfilling 2 entities (1 page of 500 each) — ETA: ~1s

# Clean restart — all data persisted, no network calls needed
[PolyNodeCache] All 4 entities already backfilled, skipping.
```

**Accurate logging**: the startup log now only reports entities that actually need backfilling, instead of the total watchlist count.

```bash theme={null}
npm install polynode-sdk@0.5.6
```

***

## 2026-03-31 — Equity Curve Endpoint

### New: Trader Equity Curve

Full equity curve for any Polymarket wallet. Returns a time-ordered series of cumulative profit and loss across every position the wallet has ever taken.

**Endpoint:** `GET /v2/trader/{wallet}/equity?period=all&normalize=1`

**Features:**

* Raw P\&L curve with realized gains/losses and mark-to-market on open positions
* \$1-normalized curve for copy-trade analysis: each position scaled to \$1 of risk, showing expected returns per dollar deployed
* Period filtering: `7d`, `30d`, `90d`, `1y`, `all`
* Accuracy within 2-3% of Polymarket's own P\&L calculations (variance from real-time price differences on open positions only)

**\$1 normalization** answers the question: "If I copy every trade this wallet makes but only risk \$1 per position, what are my returns?" Useful for evaluating trader skill independent of position sizing.

```bash theme={null}
curl "https://api.polynode.dev/v2/trader/0x.../equity?period=all&normalize=1" \
  -H "x-api-key: YOUR_KEY"
```

```json theme={null}
{
  "positions_count": 278,
  "open_count": 83,
  "raw": {
    "final_pnl": -395.90,
    "curve": [{"t": 1774555427, "pnl": 10.56}, ...]
  },
  "normalized": {
    "bet_size": 1,
    "final_pnl": -3.19,
    "curve": [{"t": 1774555427, "pnl": 0.07}, ...]
  }
}
```

See the [full documentation](/api-reference/enriched/equity-curve) for methodology details and response field reference.

### Fix: Market Price Fetching

Fixed an issue where current market prices were not being fetched correctly for open positions across multiple endpoints. Price accuracy is now significantly improved for any endpoint that reports unrealized P\&L.

***

## 2026-03-29 — Rust SDK v0.5.6 (Order Placement Fix)

### Rust SDK v0.5.6

Critical fixes for order placement. The trading module now passes full end-to-end testing: wallet onboarding, order placement, cancellation, and order history.

**Order signing fixes:**

* Fixed EIP-712 struct hashing (domain type filtering)
* Fixed signature `v` normalization for CLOB compatibility
* Fixed amount rounding to match tick-size-based precision rules
* Fixed salt generation range for CLOB compatibility

**Authentication fixes:**

* Fixed HMAC base64 decoding for URL-safe encoded secrets
* Fixed wallet address checksumming (EIP-55) in all auth headers and order payloads

**Privy integration fixes:**

* Fixed address formatting in Privy wallet API calls
* Fixed signature normalization in Privy response extraction

```toml theme={null}
[dependencies]
polynode = { version = "0.5.6", features = ["trading"] }
```

***

## 2026-03-29 — Python SDK v0.6.0

### Python SDK v0.6.0

Trading module fixes and Privy server-side wallet support.

**Trading fixes:**

* Fixed CLOB credential creation (endpoint, auth headers, EIP-712 message structure)
* Fixed order signing (domain name, signature format, payload serialization)
* Fixed HMAC authentication for URL-safe base64 secrets
* Fixed `fetch_tick_size` and `fetch_neg_risk` response parsing
* Full end-to-end verified: wallet generation, onboarding, order placement, cancellation, gasless flow

**New: Privy signer**

* `PrivySigner` for server-side trading with Privy-managed wallets
* No private key needed, signing via Privy's wallet API
* Works with all trading methods (`ensure_ready`, `order`, `cancel_all`, etc.)

**Other fixes:**

* `markets_by_category()` now correctly filters via query parameter
* Fixed subscription filters code example in docs

```bash theme={null}
pip install polynode==0.6.0
# With trading support:
pip install "polynode[trading]==0.6.0"
```

***

## 2026-03-29 — Rust SDK v0.5.0 (Feature Parity)

### Rust SDK v0.5.0

Major release bringing the Rust SDK to feature parity with the TypeScript SDK.

**New REST endpoints (12 methods):**

* Orderbook REST: `orderbook_rest()`, `midpoint()`, `spread()`
* Enriched data: `leaderboard()`, `trending()`, `activity()`, `movers()`
* Trader analytics: `trader_profile()`, `trader_pnl()`
* Events: `event()`, `search_events()`, `markets_by_category()`

**Trading module** (`--features trading`):

* `PolyNodeTrader` for full order lifecycle on Polymarket
* Wallet generation, onboarding (auto-detect Safe/Proxy), order placement, cancellation
* Native EIP-712 signing via `TradingSigner` trait + `PrivateKeySigner`
* CREATE2 address derivation for Safe and Proxy wallets
* CLOB authentication with L2 HMAC headers
* Builder attribution via co-signer proxy
* Local SQLite storage for credentials and order history
* On-chain approval and balance checks

**Privy integration** (`--features privy`):

* `PrivySigner` for server-side trading with Privy-managed wallets
* Pure HTTP-based, no native SDK dependency

**Redemption watcher:**

* Monitor wallets for redeemable positions after oracle resolution
* Fires alerts with winner detection and payout estimation

```toml theme={null}
[dependencies]
polynode = "0.5"

# With trading:
# polynode = { version = "0.5", features = ["trading"] }
```

Full documentation: [Rust SDK](/sdks/rust)

***

## 2026-03-29 — Trading Module v0.5.5 (TypeScript SDK)

### TypeScript SDK v0.5.5

* **EOA auto-approvals:** `ensureReady()` now automatically sends approval transactions for EOA wallets with proper nonce management. Requires ~~0.05 MATIC in the wallet (~~\$0.01).
* **Fix:** `createPrivySigner`, `createPrivyClient`, and other Privy helpers are now correctly exported from the package root. In v0.5.2 these were only reachable through non-public module paths.
* **Privy server-auth integration:** `createPrivySigner()` and `createPrivyClient()` for server-side trading with Privy-managed wallets.
* **Unlimited gasless operations:** Paid tiers get unlimited gasless on-chain operations. Free tier gets basic gasless onboarding.
* **ethers v5/v6 compatibility:** Safe transaction signing works with both ethers v5 and v6.

```bash theme={null}
npm install polynode-sdk@0.5.5 viem better-sqlite3 @polymarket/clob-client @polymarket/builder-relayer-client @polymarket/builder-signing-sdk
```

Full documentation: [Trading](/sdks/trading)

***

## 2026-03-29 — Trading Module (TypeScript SDK v0.5.0)

### TypeScript SDK — Order Placement on Polymarket

Place and manage orders on Polymarket through the polynode SDK. One function call handles wallet setup, credential creation, approvals, and order placement.

```bash theme={null}
npm install polynode-sdk@0.5.0 viem better-sqlite3 @polymarket/clob-client @polymarket/builder-relayer-client @polymarket/builder-signing-sdk
```

**One-call onboarding:**

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

const trader = new PolyNodeTrader({ polynodeKey: 'pn_live_...' });

// Auto-detects your wallet type. Deploys Safe, sets approvals, creates credentials.
const status = await trader.ensureReady('0xYOUR_PRIVATE_KEY');

const result = await trader.order({
  tokenId: '...',
  side: 'BUY',
  price: 0.55,
  size: 100,
});

await trader.cancelOrder(result.orderId);
```

**Key features:**

* **Auto-detection:** Pass your private key. The SDK checks if you have a Safe, Proxy, or EOA on-chain and uses the right one. No wallet type guessing.
* **Wallet generation:** `PolyNodeTrader.generateWallet()` for users starting from scratch.
* **Gasless onboarding:** Safe deployment + 6 token approvals in one call, \~10 seconds, zero gas.
* **Local credential custody:** CLOB credentials stored in local SQLite. Export, import, and back up freely.
* **Builder attribution:** Orders route through polynode's relay for affiliate tracking. If the relay is down, orders fall back to direct CLOB submission.
* **All three wallet types:** EOA (type 0), Proxy (type 1, legacy Magic Link), Safe (type 2, browser wallets).
* **Dome drop-in:** Same `@polymarket/clob-client` signing under the hood. Import existing Dome credentials with `linkCredentials()`.

Full documentation: [Trading](/sdks/trading) | [Dome Migration](/dome-migration#order-placement)

***

## 2026-03-28 — Onchain Data Endpoints + Market Metadata Enrichment

### Enrichment: All Onchain Endpoints Now Include Market Metadata

Every onchain endpoint now returns enriched data with human-readable market context. Responses include `market` (question text), `slug`, `outcome` label, `image`, and `condition_id` alongside the raw onchain data. Powered by a full backfill of all 706K+ Polymarket markets into a local metadata store that refreshes every 5 minutes.

### New: Onchain Data Section

Seven new endpoints for blockchain settlement data. These provide complete, accurate data that never times out or drops records.

All onchain endpoints are under `/v2/onchain/` and documented in the new [Onchain](/api-reference/onchain/wallet-trades) section of the API reference.

#### Wallet endpoints

* **`GET /v2/onchain/wallets/{addr}/trades`** — Complete trade fill history for any wallet. Every fill, every counterparty, every fee.
* **`GET /v2/onchain/wallets/{addr}/redemptions`** — All redemptions with payout amounts. See who cashed out, when, and how much. Not available anywhere else.
* **`GET /v2/onchain/wallets/{addr}/activity`** — Splits, merges, and multi-outcome conversions. Shows how wallets interact with the CTF contract beyond simple trading.

#### Market endpoints

* **`GET /v2/onchain/markets/{tokenId}/trades`** — Complete trade tape for any market token.
* **`GET /v2/onchain/markets/{tokenId}/volume`** — Lifetime volume stats: total trades, buys, sells, USDC volume with buy/sell breakdown.
* **`GET /v2/onchain/markets/{conditionId}/oi`** — Per-market open interest in USDC.
* **`GET /v2/onchain/oi`** — Global platform open interest (currently \~\$469M).

All endpoints support `limit` and `offset` pagination where applicable. Responses are cached for 1-5 minutes depending on the endpoint.

***

### Wallet P\&L: Complete Position History + Accurate Realized P\&L

#### What changed

The existing `GET /v1/wallets/{addr}/positions` endpoint only returns **open** positions. Once a market resolves or a position is fully sold, it disappears from the response. The existing `GET /v1/wallets/{addr}/trades` endpoint silently drops trades for high-volume wallets (we verified one wallet showing 403 trades through the API vs 4,405 actual onchain trades).

This made it impossible to compute accurate P\&L through our API. That's fixed now.

Two new v2 endpoints return complete position history with accurate P\&L sourced from onchain settlement data. Per-position `realized_pnl` values come from the same onchain settlement data Polymarket uses and match individual position P\&L to the penny. The `total_realized_pnl` aggregate measures total realized gains, which is different from Polymarket's profile PnL (see [docs](/api-reference/wallets/onchain-positions#total_realized_pnl-vs-polymarket-profile-pnl) for a full breakdown).

#### `GET /v2/wallets/{address}/positions/onchain`

**One call. No pagination. All positions (open + closed).**

Unlike the v1 positions endpoint which requires the SDK to paginate through trades page by page, this endpoint returns the complete picture in a single request with up to 20,000 positions.

```bash theme={null}
curl "https://api.polynode.dev/v2/wallets/0xbddf61af533ff524d27154e589d2d7a81510c684/positions/onchain" \
  -H "x-api-key: YOUR_KEY"
```

```json theme={null}
{
  "wallet": "0xbddf61af533ff524d27154e589d2d7a81510c684",
  "source": "onchain",
  "count": 873,
  "open_count": 334,
  "closed_count": 288,
  "total_realized_pnl": 17183579.48,
  "positions_with_pnl": 309,
  "positions": [
    {
      "token_id": "3415885798119615...",
      "size": 0,
      "avg_price": 0.316481,
      "realized_pnl": 447182.95,
      "total_bought": 654236.31
    }
  ]
}
```

| Field                      | Description                                    |
| -------------------------- | ---------------------------------------------- |
| `count`                    | Total positions (open + closed)                |
| `open_count`               | Positions still held (`size > 0`)              |
| `closed_count`             | Fully exited positions with nonzero P\&L       |
| `positions_with_pnl`       | Number of positions with nonzero realized P\&L |
| `total_realized_pnl`       | Sum of all `realized_pnl` values               |
| `positions[].realized_pnl` | Profit or loss for this position in USDC       |
| `positions[].avg_price`    | Volume-weighted average entry price            |
| `positions[].total_bought` | Total tokens acquired                          |

Responses are cached for 5 minutes. First request takes 200ms-3s depending on position count. Cached responses return in under 50ms.

#### `GET /v2/wallets/{address}/closed-positions`

Closed positions with full metadata (title, outcome, slug). Paginated.

```bash theme={null}
curl "https://api.polynode.dev/v2/wallets/0xbddf61af533ff524d27154e589d2d7a81510c684/closed-positions?limit=3&sortBy=REALIZEDPNL&sortDirection=DESC" \
  -H "x-api-key: YOUR_KEY"
```

Parameters: `limit` (max 50), `offset`, `sortBy` (REALIZEDPNL, AVGPRICE, PRICE, TITLE, TIMESTAMP), `sortDirection` (ASC/DESC).

#### SDK: Automatic onchain P\&L backfill

The TypeScript (`polynode-sdk@0.4.10`) and Rust (`polynode@0.4.3`) SDKs now fetch onchain position data automatically during backfill. No code changes needed if you're already using `cache.start()`. The backfill makes one additional API call per wallet (the onchain positions endpoint above) and stores the results in your local SQLite database.

```bash theme={null}
npm install polynode-sdk@0.4.10
```

**Before:** The SDK backfilled trades page by page (up to 6 requests per wallet) and computed P\&L from incomplete trade history. Wallets with dropped trades got wrong numbers.

**After:** One call per wallet returns all positions with precomputed P\&L. The SDK stores this in SQLite and uses it as the source of truth for all P\&L queries.

```typescript theme={null}
const cache = new PolyNodeCache(client);
await cache.start();

// P&L from local cache — no API calls
const pnl = cache.computeRealizedPnl("0xbddf61af533ff524d27154e589d2d7a81510c684");
```

```json theme={null}
{
  "wallet": "0xbddf61af533ff524d27154e589d2d7a81510c684",
  "total_realized_pnl": 17183579.48,
  "total_unrealized_pnl": 3030.53,
  "total_pnl": 17186610.01,
  "confidence": "full",
  "trades_analyzed": 403,
  "tokens": [
    {
      "token_id": "3415885798119615...",
      "market_title": "Nuggets vs. Warriors",
      "outcome": "Warriors",
      "realized_pnl": 447182.95,
      "avg_cost": 0.3170,
      "remaining_size": 0
    }
  ]
}
```

`trades_analyzed` reflects cached trades from the REST API (403 in this case). The `total_realized_pnl` and per-token `realized_pnl` values come from onchain settlement data and are accurate regardless of cached trade count.

`walletDashboard()` now includes `realized_pnl`, `pnl_confidence`, and `token_pnl` fields:

```typescript theme={null}
const dash = cache.walletDashboard("0xbddf61af533ff524d27154e589d2d7a81510c684");
// dash.realized_pnl     → 17183579.48
// dash.pnl_confidence   → "full"
// dash.token_pnl        → per-token P&L breakdown
// dash.total_pnl        → realized + unrealized combined
```

***

### Crypto Price Streaming (WebSocket)

Real-time cryptocurrency prices are now available on the same WebSocket connection you already use for settlements and oracle events. Seven feeds, each updating roughly once per second:

**BTC/USD**, **ETH/USD**, **SOL/USD**, **BNB/USD**, **XRP/USD**, **DOGE/USD**, **HYPE/USD**

Subscribe with `{"action": "subscribe", "type": "chainlink"}`. Optionally filter to specific feeds:

```json theme={null}
{
  "action": "subscribe",
  "type": "chainlink",
  "filters": { "feeds": ["BTC/USD", "ETH/USD"] }
}
```

Each price update includes bid, ask, and mid price:

```json theme={null}
{
  "type": "price_feed",
  "feed": "BTC/USD",
  "timestamp": 1774675338,
  "data": {
    "feed": "BTC/USD",
    "price": 66240.86,
    "bid": 66234.85,
    "ask": 66243.30,
    "timestamp": 1774675338
  }
}
```

BTC/USD includes actual bid/ask spread. Other feeds currently report bid and ask equal to the mid price.

See the [full documentation](/crypto/overview) for connection examples, feed details, and event format reference.

***

### Crypto REST Endpoints

Five new REST endpoints for crypto prediction market data:

* `GET /v1/crypto/markets` — All crypto prediction markets with volume, liquidity, and open interest (3 min cache)
* `GET /v1/crypto/candles?symbol=BTC` — \~30 five-minute OHLC candles (30s cache)
* `GET /v1/crypto/price?symbol=BTC&window={epoch}` — Open/close price for a specific market window (10s cache)
* `GET /v1/crypto/active` — Currently active 5-minute up-or-down markets across all 7 coins with live odds (30s cache)
* `GET /v1/crypto/series` — Recurring crypto market series across timeframes (5 min cache)

```bash theme={null}
curl "https://api.polynode.dev/v1/crypto/candles?symbol=ETH" \
  -H "x-api-key: YOUR_KEY"
```

Supported symbols: `BTC`, `ETH`, `SOL`, `BNB`, `XRP`, `DOGE`, `HYPE`.

See the [REST API reference](/crypto/rest-api) for full parameter details and response samples.

***

## 2026-03-26

### API — Wallet Resolver

New endpoint that instantly resolves any Polymarket wallet identity. Pass a proxy wallet address, EOA address, or username and get back all three.

```bash theme={null}
curl "https://api.polynode.dev/v1/resolve/DTCahill" \
  -H "x-api-key: YOUR_KEY"
```

```json theme={null}
{
  "safe": "0x0ecdd241ec1bc84a40b8142bebe65787aee97514",
  "eoa": "0xddd57cce99ca962fe23aa2da95b139f86a241459",
  "username": "DTCahill"
}
```

Covers \~3 million Polymarket wallets and is updated daily. Lookups are sub-millisecond. Accepts any of the three identifiers as input:

* `GET /v1/resolve/{safe_address}` — proxy wallet to EOA + username
* `GET /v1/resolve/{eoa_address}` — EOA to proxy wallet + username
* `GET /v1/resolve/{username}` — username to both addresses (case-insensitive)

See the [full documentation](/api-reference/wallets/resolve) for details.

***

### API — `order_hash` on Settlements and Trades

Every settlement trade and confirmed trade event now includes an `order_hash` field — the EIP-712 hash that uniquely identifies a limit order on Polymarket's order book.

**Why this matters:** A single limit order can be partially filled across multiple transactions. The `order_hash` stays the same across all of them, letting you track order lifecycle, group partial fills, and build order-level analytics.

* **Settlement events:** `order_hash` appears on each trade inside the `trades[]` array. Available on pending (pre-chain) settlements, computed from transaction calldata before block confirmation.
* **Trade events:** `order_hash` appears as a top-level field, extracted directly from the on-chain `OrderFilled` event log.

```json theme={null}
{
  "type": "trade",
  "data": {
    "order_hash": "0x916fa5c2728c93e19ea5d7c254b07234815ad01e59597573c09d852fe53ee564",
    "maker": "0x4b8cf80092c60b9b23d22133eb63eb4508fe4d31",
    "price": 0.71,
    "size": 3.0
  }
}
```

No SDK update required — the field is automatically included in the WebSocket stream.

### API — `order_hashes` on Status Updates

The `status_update` event now includes an `order_hashes` field containing every EIP-712 order hash from the original pending settlement. This lets you correlate confirmed settlements back to specific limit orders without needing to store state from the initial pending event.

```json theme={null}
{
  "type": "status_update",
  "data": {
    "tx_hash": "0xbb26c8cd...",
    "order_hashes": [
      "0xafc3488df3505c4dbba8797b8b6c645630c8ee9decf811a0acc4d63fdecfdd36",
      "0x9b5a06de7196bde87b4d8baa6cb80681151338d263fac0e643a2d53c4497e27f"
    ],
    "maker_wallets": ["0xd408...", "0x9443..."],
    "latency_ms": 3959
  }
}
```

Purely additive. Existing clients are unaffected.

***

### TypeScript SDK — RedemptionWatcher Memory Management

The `RedemptionWatcher` now automatically evicts resolved conditions and zero-size positions from its local watch set. This keeps memory bounded to only active, non-zero positions regardless of how long the watcher runs or how many wallets it tracks.

Previously, resolved markets and fully-sold positions accumulated in memory indefinitely. For long-running services tracking thousands of wallets, this could grow to hundreds of megabytes over weeks.

**What changed:**

* Resolved conditions are removed from the index immediately after alerts fire
* Positions that drop to zero size (full sell) are removed automatically
* `refreshInterval` default changed from `0` (disabled) to `300_000` (5 minutes). This periodic REST refresh acts as a safety net, re-populating any positions missed during brief WebSocket disconnections.

**No breaking changes.** Alert behavior is identical. If you were explicitly setting `refreshInterval: 0`, that still works.

```bash theme={null}
npm install polynode-sdk@latest
```

See [RedemptionWatcher — Memory Management](/sdks/redemption-watcher#memory-management) for details.

***

### Trader Profile — EOA Wallet Resolution

The `/v1/trader/{wallet}` endpoint now returns an `eoaWallet` field that resolves the underlying EOA (externally owned account) for Polymarket proxy wallets. This is derived onchain from the Gnosis Safe contract.

```json theme={null}
{
  "wallet": "0xc2e7800b5af46e6093872b177b7a5e7f0563be51",
  "eoaWallet": "0xb49e5499562a4bc3345c1a1f2db13a5360dfddac",
  "name": "beachboy4",
  ...
}
```

For older lightweight proxy wallets, `eoaWallet` returns `null`.

### Tier-Aware Rate Limits on Data-Heavy Endpoints

A handful of data-heavy endpoints now have their own per-key rate limits that scale with your plan:

| Tier       | Limit       |
| ---------- | ----------- |
| Free       | 1 req/sec   |
| Starter    | 30 req/sec  |
| Growth     | 50 req/sec  |
| Enterprise | 100 req/sec |

Affected endpoints: `/v1/trader/{wallet}`, `/v1/trader/{wallet}/pnl`, `/v1/leaderboard`, `/v1/trending`, `/v1/activity`, `/v1/event/{slug}`, `/v1/movers`, `/v2/markets/{category}`.

All other REST endpoints use your plan's standard rate limit. See [Rate Limits](/guides/rate-limits) for full details.

***

## 2026-03-25

### TypeScript SDK — RedemptionWatcher

New high-level class that monitors wallets and fires alerts the instant any position becomes redeemable on-chain.

```bash theme={null}
npm install polynode-sdk@latest
```

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

const watcher = new RedemptionWatcher({ apiKey: 'pn_live_...' });

watcher.on('alert', (alert) => {
  if (alert.isWinner) {
    console.log(`${alert.wallet} can redeem "${alert.marketTitle}" — ~$${alert.estimatedPayoutUsd}`);
  }
});

await watcher.start(['0xabc...', '0xdef...']);
```

**What it does:** Fetches wallet positions via REST, indexes by `condition_id`, subscribes to the oracle stream for `condition_resolution` events, and cross-references in real-time. Optional live position tracking via the wallets stream keeps sizes accurate as users trade.

**Key features:**

* Runtime `addWallets()` / `removeWallets()` with automatic re-subscription
* Callback (`.on('alert', ...)`) and async iterator (`for await`) consumption
* Winner detection with payout estimates
* Full market metadata on every alert (title, slug, image, outcomes)

See the [full documentation](/sdks/redemption-watcher) for lifecycle, configuration, and a production example.

***

### Oracle Stream — `condition_resolution` Event

The oracle WebSocket stream now includes `condition_resolution` events. This is the on-chain signal that positions are redeemable on the Conditional Tokens contract.

Previously, the stream included `resolution` events (outcome decided on the UMA adapter) but not the separate on-chain step where `reportPayouts()` is called on the CTF contract. For neg-risk markets (the majority on Polymarket), there is a \~2-3 minute gap between these two events. `condition_resolution` closes that gap.

**Subscribe and filter:**

```json theme={null}
{
  "action": "subscribe",
  "type": "oracle",
  "event_types": ["condition_resolution"]
}
```

**Payload includes:** `resolved_price`, `payouts`, `condition_id`, `question_id`, and full market metadata (title, outcomes, token IDs, image).

**Use case:** Trigger redemption workflows the instant positions become redeemable, instead of polling or waiting for the Polymarket UI to update.

**SDK support:** Available in Rust SDK `polynode 0.4` via `OracleEventType::ConditionResolution`. TypeScript SDK update coming soon.

***

### TypeScript SDK v0.4.8 — Composable Leaderboard Builder

The local cache now supports composable leaderboards with multi-metric ranking, market filtering, slug pattern matching, wallet scoping, and time windows.

**New:** Call `cache.leaderboard()` with no arguments to get a `LeaderboardBuilder`:

```typescript theme={null}
const rows = cache.leaderboard()
  .metrics(['total_pnl', 'volume', 'win_rate'])
  .slugs(['*election*'])
  .since(weekAgo)
  .limit(10)
  .build();

// Each row: { wallet, label, rank, metrics: { total_pnl, volume, win_rate } }
```

**7 new metrics** (11 total): `roi`, `realized_pnl`, `volume`, `avg_trade_size`, `largest_win`, `largest_loss`, `market_count`.

**Builder methods:**

* `.metric()` / `.metrics()` — single or multi-metric per row
* `.sortBy()` / `.sort('ASC' | 'DESC')` — control ranking
* `.markets([conditionIds])` — filter by market
* `.slugs([patterns])` — glob match on market slugs (`*election*`, `btc-*`)
* `.category({ name, slugs })` — reusable named slug groups
* `.wallets([addrs])` — rank a wallet subset instead of full watchlist
* `.since(ts)` / `.until(ts)` — time window for trade metrics
* `.limit(n)` — top N

Backward compatible. Existing `cache.leaderboard('total_pnl')` still returns the same `LeaderboardEntry[]` format.

```bash theme={null}
npm install polynode-sdk@0.4.8
```

Full documentation: [Local Cache — Leaderboard Builder](/sdks/local-cache#leaderboard-builder)

***

### WebSocket — Application-Level Keepalive

Cloud platform reverse proxies (Railway, Render, Heroku, AWS ALB, fly.io) can intercept WebSocket Ping/Pong control frames, preventing the server from detecting that your client is still alive. This caused connections to drop after 1-2 minutes with an empty error and no close frame.

**What changed:**

* The server now accepts any incoming message (not just Pong frames) as proof the client is alive
* New `{"action": "ping"}` message returns `{"type": "pong", "ts": ...}` as an application-level keepalive
* Stale connection timeout extended from 90 seconds to 5 minutes
* Stale disconnects now include a close frame with a reason, instead of a silent drop

**If you're running on a cloud platform**, add a periodic ping to your connection:

<CodeGroup>
  ```python Python theme={null}
  async def keepalive():
      while True:
          await asyncio.sleep(30)
          await ws.send(json.dumps({"action": "ping"}))

  ping_task = asyncio.create_task(keepalive())
  ```

  ```javascript Node.js theme={null}
  setInterval(() => ws.send(JSON.stringify({ action: "ping" })), 30000);
  ```
</CodeGroup>

If you're running on bare metal or a VM with no reverse proxy in front of your client, this doesn't affect you. Standard WS Ping/Pong still works as before.

See [WebSocket Overview](/websocket/overview#application-level-keepalive) for full connection examples.

***

### WebSocket — Reconnect Gap-Fill with `since`

Subscribe messages now accept an optional `since` filter (UNIX milliseconds). When set, the initial snapshot returns all events after that timestamp instead of just the most recent N, letting you fill gaps after a disconnect or cold start without missing data.

```json theme={null}
{
  "action": "subscribe",
  "type": "settlements",
  "filters": { "since": 1774412600000 }
}
```

The response is the same `snapshot` message you already handle. Existing clients are unaffected — `since` is fully optional.

**Lookback windows by tier:**

| Tier       | Max lookback |
| ---------- | ------------ |
| Free       | 30 seconds   |
| Starter    | 2 minutes    |
| Growth     | 5 minutes    |
| Enterprise | 5 minutes    |

If `since` is older than your tier's window, it's automatically clamped. Within the window, there is no event count limit — you get everything that matches your filters.

***

## 2026-03-24

### API — Onchain Redemption State on Wallet Positions

The `/v1/wallets/{addr}/positions` and `/v1/wallets/positions` endpoints now include onchain redemption data for resolved markets. Three new fields are returned on positions where `redeemable` is `true`:

* **`redeemed`** — `true` if the wallet has burned their tokens onchain (claimed USDC payout), `false` if tokens are still held.
* **`unredeemed_balance`** — Number of tokens still held onchain. Tells you exactly how much hasn't been claimed.
* **`unredeemed_usd`** — USD value of unclaimed tokens (\$1 per token on winning positions).

This data is not available from Polymarket's API. polynode checks the Conditional Token Framework contract onchain in a single batched call, adding \~130ms only when redeemable positions are present. Active (unresolved) positions are unaffected.

```bash theme={null}
curl "https://api.polynode.dev/v1/wallets/0x62d2.../positions" \
  -H "x-api-key: pn_live_..."
```

```json theme={null}
{
  "title": "Lana Del Rey divorce in 2025?",
  "redeemable": true,
  "redeemed": false,
  "unredeemed_balance": 418.73,
  "unredeemed_usd": 418.73,
  "size": 418.73
}
```

***

## 2026-03-23

### API — Event Search & Token IDs

Two changes to event endpoints:

* **New:** `GET /v1/events/search?q=...&limit=N` — search events by text query. Returns events with all sub-markets, each including `tokenId` (YES token for CLOB price history via `/v1/candles`) and current `price`. Multi-outcome events like "How many Fed rate cuts in 2026?" return as a single result with all outcomes.
* **Updated:** `GET /v1/event/{slug}` — now includes `tokenId` on every market in the response. Previously, token IDs were not available on this endpoint.

```bash theme={null}
curl "https://api.polynode.dev/v1/events/search?q=recession&limit=5" \
  -H "x-api-key: pn_live_..."
```

Rate limit: 1 request per second per API key (shared with other enriched data endpoints).

### TypeScript SDK v0.4.6

* **New:** `searchEvents(query, { limit })` — typed method for event search, returns `EventSearchResponse`
* **New types:** `EventSearchResponse`, `EventSearchResult`, `EventSearchMarket`
* **Updated:** `EventMarket` now includes optional `tokenId` field

```bash theme={null}
npm install polynode-sdk@0.4.6
```

```typescript theme={null}
const results = await pn.searchEvents('Fed rate', { limit: 5 });
for (const event of results.events) {
  console.log(event.title, `(${event.markets.length} outcomes)`);
  for (const m of event.markets) {
    // Use tokenId to fetch price history
    const candles = await pn.candles(m.tokenId, { resolution: '1h' });
  }
}
```

***

## 2026-03-21

### TypeScript SDK v0.4.4 — Enriched Data Methods

Eight new typed methods on the `PolyNode` client for all enriched data endpoints:

* `leaderboard()` — top 20 traders by profit or volume, with period filtering
* `trending()` — carousel, breaking markets, hot topics, featured events, and biggest movers
* `activity()` — platform-wide trade feed (50 most recent)
* `movers()` — markets with largest 24h price swings
* `traderProfile(wallet)` — full trader stats: PnL, volume, trades, largest win
* `traderPnl(wallet, { period })` — cumulative PnL time series
* `event(slug)` — full event detail with all sub-markets
* `marketsByCategory(category)` — browse markets by category

All methods are fully typed with dedicated response interfaces. Install:

```bash theme={null}
npm install polynode-sdk@0.4.4
```

***

### TypeScript SDK v0.4.3 — Cache UI Primitives

Four new features for building dashboards on top of the local cache.

* **View methods** — `watchlistSummary()`, `walletDashboard()`, `leaderboard()`, `marketOverview()`. Pre-shaped data for common dashboard patterns. No SQL, no aggregation.
* **Reactive subscriptions** — `onChange()` and `onWalletChange()` fire callbacks when new trades or settlements land from the live WebSocket stream. Returns an `unsub()` function for cleanup.
* **Export helpers** — `exportCSV()`, `exportJSON()`, `exportRows()` dump filtered data for charting libraries, spreadsheets, or custom analysis.
* **Query builder** — Chainable fluent API: `.wallet()`, `.side()`, `.since()`, `.market()`, `.minPnl()`, `.limit()`, `.run()`. Complex filters without writing SQL.

```bash theme={null}
npm install polynode-sdk@0.4.3
```

Full documentation: [Local Cache](/sdks/local-cache)

***

## 2026-03-21

### API — Enriched Data Endpoints

Eight new REST endpoints for trader analytics, market discovery, and platform trends.

* `GET /v1/leaderboard` — Top 20 traders ranked by profit or volume (daily, weekly, monthly, all-time)
* `GET /v1/trader/{wallet}` — Full trader profile: PnL, volume, trade count, largest win, portfolio value
* `GET /v1/trader/{wallet}/pnl` — PnL time series at 4 resolutions (1D, 1W, 1M, ALL)
* `GET /v1/trending` — Carousel highlights, breaking markets, hot topics, featured events, biggest movers
* `GET /v1/activity` — Platform-wide live trade feed (last 50 trades with tx hashes)
* `GET /v1/event/{slug}` — Full event data with all sub-markets, outcome prices, condition IDs
* `GET /v1/movers` — Markets with the largest 24h price changes
* `GET /v2/markets/{category}` — Category market listings with counts (crypto, politics, sports, etc.)

Rate limit: 1 request per second per API key. Responses cached 1-3 minutes.

### TypeScript SDK v0.4.1

* **Fix:** ESM imports now work correctly. v0.4.0 crashed on `cache.start()` when using `import` syntax.
* **Fix:** Eliminated `MODULE_TYPELESS_PACKAGE_JSON` Node.js warning.
* **New:** `getActiveTestWallet()` / `getActiveTestWallets()` — returns known-active wallet addresses for testing and examples.

```bash theme={null}
npm install polynode-sdk@0.4.1
```

### API — Wallet positions fix

* Fixed `firstTradeAt` / `lastTradeAt` returning `null` for certain wallets on `GET /v1/markets/{id}/positions?includeTrades=true`.
* Added fallback query path for wallets where the primary trade lookup returns empty.

### API — Per-endpoint rate limiting

* `includeTrades=true` on market positions now has its own 20 req/min rate limit per key.
* Separate bucket from standard rate limit — doesn't consume your normal quota.

***

## 2026-03-21

### TypeScript SDK v0.4.0

* **New:** Local Cache — SQLite-backed local storage for instant offline queries.
* Backfill wallet history in seconds (1 request per wallet, up to 500 trades).
* Live WebSocket stream keeps the cache up to date automatically.
* Query trades, positions, and settlements locally with zero API calls.
* Watchlist file with hot-reload and runtime add/remove API.
* Full documentation at [Local Cache](/sdks/local-cache).

### Rust SDK v0.4.0

* **New:** Local Cache ported from TypeScript. Same architecture, same SQL schema, same query methods.
* Builder pattern: `PolyNodeCache::builder(client).db_path(...).build()?`
* `rusqlite` (bundled) + `notify` as optional dependencies behind `cache` feature flag.

***

## 2026-03-20

### Web Frontend

* Speed comparison test redesigned with 3-second warm-up period, win % scoreboard, and visual win ratio bar.
* Updated "Run this test yourself" code snippet to match new logic.

***

## 2026-03-19

### TypeScript SDK v0.3.0

* **New:** `OrderbookEngine` — higher-level orderbook client with shared state and filtered views per component.
* **New:** `LocalOrderbook` — local orderbook state management.
* **New:** `ShortFormStream` — condensed event stream for bandwidth-constrained environments.

### API

* Orderbook REST endpoints: `/v1/orderbook/{token}`, `/v1/midpoint/{token}`, `/v1/spread/{token}`.
* Wallet trades `limit` parameter now allows up to 1,000 (was 500).

***

## 2026-03-15

### API v1 — Initial Public Release

* **WebSocket streaming:** settlements, trades, prices, blocks, wallets, markets, large trades, oracle, chainlink
* **REST API:** markets, search, candles, stats, settlements, wallets
* **Orderbook streaming** via `ob.polynode.dev`
* **RPC endpoint** via `rpc.polynode.dev` — JSON-RPC with TX #1 delivery
* **CLI** (`pn`) — stream events, query markets, manage API keys from the terminal
