predict-fun-sdk 0.4.0

Rust SDK for the Predict.fun prediction market API — EIP-712 order signing, REST client, WebSocket feeds, and execution pipeline
Documentation
# Changelog

## [0.4.0] - 2026-03-07

### Changed
- **HTTP/2 via rustls**: switched from `native-tls` to `rustls-tls` for both reqwest and
  tokio-tungstenite. Enables ALPN h2 negotiation with predict.fun (Cloudflare). No more
  OpenSSL system dependency — pure Rust TLS.
- **20 new tests** (44 → 64 total):
  - Cache tests (5): cache hit/miss, preload_markets parallel, prepare_with_meta zero-network,
    refresh_market_meta, clear_cache
  - Edge cases (9): invalid market ID, zero price, negative price, invalid private key,
    unsupported chain ID, zero quantity amounts, extreme prices, invalid topic IDs,
    single-side orderbook
  - WebSocket edge cases (4): unsubscribe removes topic, duplicate subscribe idempotent,
    raw topic string, multiple orderbook markets
  - HTTP/2 verification (1)
  - Helper refactors (1): shared `make_exec_client()` and `find_btc_market_id()`
- **No QUIC/HTTP3**: predict.fun (Cloudflare) does not advertise `alt-svc: h3` — HTTP/2 is
  the best available transport

## [0.3.0] - 2026-03-07

### Changed — Network & Simplification
- **Market metadata cache** in `PredictExecutionClient`: token IDs, fee rates, and flags
  are cached after first fetch. Eliminates redundant `GET /markets/{id}` on every order.
  - `market_meta(id)` — get-or-fetch with RwLock cache
  - `preload_markets(&[ids])` — parallel pre-warm for multiple markets
  - `prepare_limit_order_with_meta(req, meta)` — zero network calls when meta provided
  - `refresh_market_meta(id)` — force cache refresh
  - `clear_cache()` — invalidate all cached metadata
- **JWT refresh**: `refresh_jwt()` method for re-authentication when token expires
- **`MarketMeta` struct** exported at crate root — holds everything needed to sign orders
- **Removed dead code**: `PREDICT_ENDPOINTS` array (30 entries), `EndpointSpec`, `MethodName`
  enums — never used at runtime, just noise
- **Simplified `RawApiResponse`**: dropped raw `body: String` field, keeps only `json: Option<Value>`
  — responses are parsed to JSON directly via `resp.json()` instead of `resp.text()` + reparse
- **HTTP client tuning**: added `connect_timeout(5s)`, `tcp_keepalive(30s)`, reduced
  `timeout` from 15s to 10s
- **WebSocket reconnect loop**: iterative instead of recursive `Box::pin` — simpler,
  no stack growth, resets attempt counter on success
- **WebSocket `active_topics`**: `HashSet<String>` instead of `Vec<String>` — O(1) lookup
  instead of O(n) scan
- **Deduplicated subscribe/unsubscribe**: shared `send_and_wait()` method
- **Extracted `WsStream` type alias** for readability

### Line count
- Before: 2389 lines (api 576, execution 322, lib 72, order 442, ws 977)
- After: 2129 lines (api 369, execution 455, lib 61, order 442, ws 802)
- Net: **-260 lines** (-11%) while adding caching, JWT refresh, and preloading

## [0.2.1] - 2026-03-07

### Added
- Integration test suite (`tests/integration.rs`) covering full live API + WebSocket flow
  - REST: auth, search, orderbook, timeseries, market stats, categories, tags, account, positions
  - WebSocket: connect, subscribe to orderbook + price feeds, receive push messages, heartbeat
  - EIP-712: order signing, hash recovery, payload structure validation
  - Execution: dry-run limit/market/sell orders, remove orders path
- `CHANGELOG.md`

## [0.2.0] - 2026-03-07

### Added
- **`ws` module** — WebSocket client for real-time market data via `wss://ws.predict.fun/ws`
  - `PredictWsClient` with connect, subscribe/unsubscribe, auto-reconnect (exponential backoff)
  - Typed topics: `Orderbook`, `AssetPrice`, `PolymarketChance`, `KalshiChance`, `WalletEvents`
  - Typed messages: `OrderbookSnapshot` (with `mid()`, `spread()`, `best_bid()`, `best_ask()`),
    `AssetPriceUpdate`, `CrossVenueChance`, `WalletEvent`
  - Heartbeat echo (60s timeout) per predict.fun protocol
  - Known feed IDs: `feeds::BTC` (1), `feeds::ETH` (4), `feeds::BNB` (2)
  - `PredictWsConfig` with mainnet/testnet presets
- Re-exports for `ws` types at crate root

### Protocol Discovery
- Endpoint: `wss://ws.predict.fun/ws` (mainnet), `wss://ws.bnb.predict.fail/ws` (testnet)
- Custom JSON RPC: `{requestId, method, params}``{type: "R"/"M", ...}`
- Sub-second orderbook updates, ~3-5 price updates/sec per asset
- No auth needed for market data feeds
- Cross-venue topics (`polymarketChance`, `kalshiChance`) confirm predict.fun tracks competitor prices

## [0.1.0] - 2026-03-07

### Added
- **`api` module** — REST client covering all 30 endpoints from the [OpenAPI spec]https://api.predict.fun/docs
  - Public: markets, orderbook, timeseries, categories, tags, search, positions by address
  - JWT-gated: orders (create/list/cancel/get), account, activity, positions, yield
  - OAuth: finalize, orders (list/create/cancel), positions
  - Raw request helpers for diagnostics
- **`order` module** — EIP-712 order signing using alloy (native Rust)
  - `PredictOrderSigner`: auth message signing + order signing with signature recovery
  - `predict_limit_order_amounts`: BUY/SELL amount math matching TypeScript SDK
  - `predict_exchange_address`: all 8 contracts (mainnet+testnet × CTF/NegRisk/Yield/YieldNegRisk)
  - `PredictOrder`, `SignedPredictOrder`, `PredictCreateOrderRequest` structs
- **`execution` module** — high-level auth → sign → submit pipeline
  - `PredictExecutionClient`: JWT auth, market lookup, order preparation, submission
  - `PredictExecConfig`: env-based or manual config with dry-run guard (`live_execution` flag)
  - `place_limit_order`, `remove_order_ids`, `prepare_limit_order` methods