Real-time arbitrage dashboard that compares prediction market prices between Polymarket and Kalshi for BTC price markets. Displays live bid/ask spreads, asset price charts from both platforms overlaid, and an arbitrage difference chart — all updating in real time via WebSockets and polling.
The app finds the current active BTC 15-minute prediction market on both Polymarket and Kalshi, then:
- Best Bids Table — Shows side-by-side Yes/No bid, ask, and spread for both markets along with the price-to-beat and current BTC price.
- Asset Price Chart — Overlays real-time BTC prices as reported by Polymarket (via Chainlink data streams) and Kalshi, with base price reference lines for each platform.
- Arbitrage Diff Chart — Tracks the absolute difference in Yes bids, No bids, and spreads between the two markets over time, highlighting exploitable gaps.
- Countdown Timer — Shows time remaining until the current market window closes, then automatically fetches the next market.
src/
├── providers/
│ ├── polymarket.tsx # WebSocket connections to Polymarket (market bids + Chainlink prices)
│ └── kalshi.tsx # HTTP polling for Kalshi order book + price data (1s intervals)
├── components/
│ ├── BestBids.tsx # Best bid/ask/spread table for both markets
│ ├── Chart.tsx # Overlaid Polymarket vs Kalshi asset price chart (Recharts)
│ ├── ArbChart.tsx # Yes/No/Spread difference chart between platforms
│ ├── Countdown.tsx # Market expiry countdown timer
│ ├── Header.tsx # Navigation header with branding
│ ├── Footer.tsx # Footer
│ └── ThemeToggle.tsx # Light/Dark/Auto theme switcher
├── hooks/
│ ├── useMarketSelector.tsx # Fetches + selects the active market for a given platform/asset/period
│ ├── usePolymarketPrice.tsx # Fetches historical BTC start price via Chainlink (Superposition GraphQL)
│ └── useBinancePrice.tsx # Fetches historical BTC start price from Binance API
├── utils/
│ ├── findMarket.ts # Constructs market slugs from timestamps, fetches active markets via proxy workers
│ ├── formatPolymarketBids.ts # Normalizes Polymarket best_bid_ask WebSocket messages into Orderbook format
│ ├── formatKalshiBids.ts # Normalizes Kalshi order book responses into Orderbook format
│ ├── getPriceFromChainlink.ts # Fetches historical BTC/USD price from Superposition's Chainlink GraphQL endpoint
│ └── getPriceFromBinance.ts # Fetches historical BTC/USDT kline close price from Binance
├── config.ts # Platform-specific WebSocket URLs and market data selectors
├── types.ts # TypeScript types (Orderbook, BidAsk, PricePoint, ArbDiff, market types)
├── routes/
│ ├── __root.tsx # Root layout with Header, Footer, QueryClientProvider, theme init
│ └── index.tsx # Main page composing all providers and chart components
└── styles.css # Tailwind CSS styles
websocket/
└── index.mjs # Kalshi WebSocket proxy server (Fastify + ws) with RSA-PSS auth signing
-
Market Discovery —
findMarket.tscalculates the current 15-minute window timestamp and constructs platform-specific slugs (btc-updown-15m-{ts}for Polymarket,KXBTC15M-{ts}for Kalshi). These are fetched via Cloudflare Workers proxies (*.9livesso.workers.dev). -
Polymarket (WebSocket) — Two persistent WebSocket connections:
ws-subscriptions-clob.polymarket.com— Streamsbest_bid_askevents for the active market. On each message, updates the Polymarket orderbook in the React Query cache and computes the arbitrage diff against the latest Kalshi data.ws-live-data.polymarket.com— Streams real-time BTC/USD prices via Chainlink'scrypto_prices_chainlinktopic.
-
Kalshi (Polling) — Two polling loops at 1-second intervals:
- Order book fetched from
kalshi-orderbook.9livesso.workers.dev. - Price fetched from
klashi-price.9livesso.workers.dev.
- Order book fetched from
-
State Management — All data flows through TanStack React Query. Providers write directly to the query cache via
setQueriesData, and chart components read from the cache using passive queries (enabled: false) — a pub/sub pattern over React Query.
- React 19 + TanStack Start (SSR framework with file-based routing)
- TanStack React Query — State management and data fetching
- TanStack Router — File-based routing
- Recharts — Charting (ComposedChart, LineChart with animated reference dots)
- Tailwind CSS v4 — Styling
- Vite 7 — Build tool
- Cloudflare Workers — Deployment target (via Wrangler) + API proxy workers
- Biome — Linting and formatting
- Vitest — Testing
- Node.js
- pnpm
pnpm install
pnpm devThe dev server starts on http://localhost:3000.
pnpm run deployThis builds the app and deploys to Cloudflare Workers via Wrangler.
The websocket/ directory contains a standalone Fastify server that proxies authenticated WebSocket connections to the Kalshi trade API. It uses RSA-PSS signing with a private key file.
cd websocket
pnpm install
KALSHI_API_KEY=your_key node index.mjsRuns on port 3001.
pnpm test # Run tests with Vitest
pnpm lint # Lint with Biome
pnpm format # Format with Biome
pnpm check # Biome check (lint + format)© 9lives. All rights reserved.