# @elizaos/plugin-polymarket
Adds Polymarket prediction-market discovery, orderbook reading, position viewing, and trading-readiness context to an Eliza agent.
## Purpose / role
Opt-in elizaOS plugin. Load it by adding `@elizaos/plugin-polymarket` to the agent's plugin list. It registers one action, one provider, one service, seven REST routes, and one GUI view. Public market reads are always available; signed CLOB order placement is disabled in this app integration and exposed only as readiness reporting.
## Plugin surface
### Actions
- **`PREDICTION_MARKET`** — unified prediction-market router. Dispatches to `PredictionMarketService`.
- `action=read, kind=status` — configuration and readiness report.
- `action=read, kind=markets` — paginated active market list from Gamma API.
- `action=read, kind=market` — single market by `id` or `slug`.
- `action=read, kind=orderbook` — full CLOB orderbook for a `tokenId`.
- `action=read, kind=positions` — wallet positions from Data API.
- `action=place_order` — reports trading readiness; actual order signing is disabled.
- Legacy similes (still accepted): `POLYMARKET_READ`, `POLYMARKET_STATUS`, `POLYMARKET_GET_MARKETS`, `POLYMARKET_GET_ORDERBOOK`, `POLYMARKET_PLACE_ORDER`, `POLYMARKET_BUY`, `POLYMARKET_SELL`, and ~14 others (full list in `POLYMARKET_READ_COMPAT_SIMILES` / `POLYMARKET_PLACE_ORDER_COMPAT_SIMILES`, actions.ts).
### Providers
- **`POLYMARKET_STATUS`** (`polymarketStatusProvider`) — injects per-turn context text: public-read readiness, API base URLs, trading credential status. Active only in `finance` / `crypto` contexts.
### Services
- **`PredictionMarketService`** (type `"prediction-market"`) — extensible provider registry. Starts with `polymarket` registered. Accepts additional providers via `registerProvider()`. Accessed by the action via `runtime.getService("prediction-market")`.
### Routes (all `rawPath: true`)
| Method | Path | Notes |
|--------|------|-------|
| GET | `/api/polymarket/status` | Credential and readiness summary |
| GET | `/api/polymarket/markets` | Paginated markets (`limit`, `offset`, `active`, `closed`, `order`, `ascending`, `tag_id`) |
| GET | `/api/polymarket/market` | Single market (`id` or `slug`) |
| GET | `/api/polymarket/orderbook` | CLOB orderbook (`token_id`) |
| GET | `/api/polymarket/orders` | Returns 501 — trading disabled |
| POST | `/api/polymarket/orders` | Returns 501 — trading disabled |
| GET | `/api/polymarket/positions` | Wallet positions (`user`) |
### Views
One declaration in `plugin.ts` (`modalities: ["gui"]`, `componentExport: "PolymarketView"`, path `/polymarket`) ships the GUI surface:
- **`PolymarketSpatialView`** (`src/components/PolymarketSpatialView.tsx`) — the presentational component, authored with `@elizaos/ui/spatial` primitives. Purely a snapshot + action-callback in, primitives out.
- **`PolymarketView`** (`src/PolymarketView.tsx`) — the thin data wrapper that owns live state (`usePolymarketState`) and renders ``. This is the view-registry `componentExport`, bundled into `dist/views/bundle.js` via `polymarket-view-bundle.ts`.
- **`interact`** — `polymarket-view.interact.ts` exposes programmatic view capabilities for state, market, orderbook, positions, and trading-readiness checks.
## Layout
```
src/
index.ts Public re-exports
plugin.ts Exported `polymarketPlugin` (Plugin object); wires actions, services, providers, routes, views
actions.ts PREDICTION_MARKET action + PredictionMarketService class + polymarketActions[]
provider.ts polymarketStatusProvider
provider-text.ts derivePolymarketStatusText() — pure env-to-text helper used by provider
routes.ts handlePolymarketRoute() — all HTTP route logic; fetches Gamma/CLOB/Data APIs
polymarket-contracts.ts All shared interfaces and API base URL constants
orderbook.ts derivePolymarketTopOfBook() — best-bid/ask derivation from raw CLOB levels
client.ts PolymarketClient — type intersection of ElizaClient with typed fetch helpers for each route (methods patched onto ElizaClient.prototype)
register.ts appRegister entry: registers the PolymarketView app-shell page (native)
register-routes.ts registerAppRoutePluginLoader() — lazy-loads polymarketPlugin for app-route mounting
PolymarketView.tsx PolymarketView — GUI data wrapper; owns live state, renders
polymarket-view.helpers.ts loadPolymarketViewState() and postPolymarketCommand() — async helpers used by the interact handler
polymarket-view.interact.ts interact() view capability handler; re-exported by polymarket-view-bundle
polymarket-view-bundle.ts Vite view-bundle entry: re-exports PolymarketView + interact
usePolymarketState.ts usePolymarketState() React hook for view state
components/
PolymarketSpatialView.tsx Spatial-primitives view component (PolymarketSpatialView, PolymarketSnapshot)
__fixtures__/
contract.ts Test fixture contracts
polymarket-real.recorded.json Recorded API responses for tests
actions.test.ts Unit tests for actions
PolymarketView.test.tsx Render + interact tests for PolymarketView
routes.contract.test.ts Contract tests for routes
routes.real.test.ts Live API integration tests (gated on POLYMARKET_LIVE_TEST=1)
routes.test.ts Unit tests for routes
routes.positions.test.ts Keyless tests for the positions surface
components/
PolymarketSpatialView.test.tsx Tests for PolymarketSpatialView
```
## Commands
```bash
bun run --cwd plugins/plugin-polymarket build # tsup + vite views + tsc types
bun run --cwd plugins/plugin-polymarket build:js # tsup only
bun run --cwd plugins/plugin-polymarket build:views # Vite view bundle only
bun run --cwd plugins/plugin-polymarket build:types # tsc declaration emit only
bun run --cwd plugins/plugin-polymarket clean # rm -rf dist
bun run --cwd plugins/plugin-polymarket test # vitest run
```
## Config / env vars
| Var | Required | Notes |
|-----|----------|-------|
| `POLYMARKET_PRIVATE_KEY` | Trading readiness only | Wallet private key presence check for signed CLOB order readiness |
| `CLOB_API_KEY` | Trading only | Alias: `POLYMARKET_CLOB_API_KEY` |
| `CLOB_API_SECRET` | Trading only | Alias: `POLYMARKET_CLOB_SECRET` |
| `CLOB_API_PASSPHRASE` | Trading only | Alias: `POLYMARKET_CLOB_PASSPHRASE` |
Public reads (markets, orderbook, positions) require no credentials. The `GET /api/polymarket/status` route reports which trading vars are missing.
## How to extend
**Add a new prediction-market provider** (e.g. Manifold):
1. Implement the internal `PredictionMarketProvider` interface (name, aliases, supportedSubactions, execute).
2. In a plugin `onStart` or service extension, call `runtime.getService("prediction-market").registerProvider(myProvider)`.
3. Callers pass `target: "manifold"` to the `PREDICTION_MARKET` action.
**Add a new route**:
1. Add the handler case to `handlePolymarketRoute()` in `src/routes.ts`.
2. Add a `Route` entry to the `polymarketRoutes` array in `src/plugin.ts`.
3. Add a typed method to `PolymarketClient` in `src/client.ts`.
**Add a new read kind**:
1. Add the string to `READ_KINDS` in `src/actions.ts`.
2. Add a `case` to `handleReadOperation()`.
3. Add a handler function.
## Conventions / gotchas
- **Orderbook token id vs condition id.** Use the CLOB `token_id` for orderbook queries, not the Gamma `conditionId`. A market has one condition id but one or more CLOB token ids (one per outcome).
- **Signed trading is disabled.** `POST /api/polymarket/orders` returns 501. The `place_order` action reports readiness only; it does not place trades.
- **One GUI view, no rich-DOM duplicate.** The single `PolymarketSpatialView` (spatial primitives) is the presentational view component. The wrapper `PolymarketView` is the `componentExport`. Do not reintroduce a parallel DOM-only view.
- **Views use a separate Vite build.** `build:js` (tsup) produces the runtime entry; `build:views` (Vite) produces `dist/views/bundle.js` consumed by the view registry. Both must run for a complete build. The Vite entry is `src/polymarket-view-bundle.ts` (not `PolymarketView.tsx` directly); `interact` is re-exported from `polymarket-view.interact.ts` through that bundle.
- **Route handler receives Node `http.IncomingMessage` / `ServerResponse`.** The plugin.ts adapter casts `RouteRequest` / `RouteResponse` to Node types; routes.ts depends on real Node HTTP objects.
- **Context gating.** The action fires only when structured routing selects `finance`, `crypto`, `prediction-market`, or `payments` (canonical `__contextRouting` plus legacy `selectedContexts`). It does not keyword-scan raw user text. Outside those contexts the action is skipped.
- **API base URLs** are constants in `src/polymarket-contracts.ts` (`POLYMARKET_GAMMA_API_BASE`, `POLYMARKET_DATA_API_BASE`, `POLYMARKET_CLOB_API_BASE`). Change there to target a different environment.
See the root [AGENTS.md](../../AGENTS.md) for repo-wide architecture rules, logger conventions, and git workflow.
## ⛔ NON-NEGOTIABLE — evidence, trajectories & real end-to-end tests
> The binding, repo-wide standard is **[AGENTS.md](../../AGENTS.md)**. Read it.
> Nothing in this package is *done* until it is *proven* done — a reviewer must confirm it
> works **without reading the code**, from the artifacts you attach. This applies to **every**
> feature, fix, refactor, and chore here. "Tests pass" is not proof; "CI is green" is not proof.
- **Record AND read model trajectories.** Capture the *actual* inputs and outputs of the model
from a **live** LLM — not the deterministic proxy, not a mock: the prompt, the
providers/context, the raw model output, every tool/action call, and the result. Then **open
the trajectory and review it by hand.** A captured-but-unread trajectory is not evidence
(`packages/scenario-runner/bin/eliza-scenarios run --report `).
- **Real, full-featured E2E — no larp.** Every feature ships detailed end-to-end tests that
drive the *real* path end to end. Not the happy "front door" only: cover error paths,
edge/empty/invalid input, concurrency, roles/permissions, and adversarial input. A test that
asserts against a mock/stub/fixture standing in for the thing under test **does not count**.
If the real model/device/chain/connector/account is hard to reach, **make it reachable — that
is the work**, not an excuse to mock. If the existing tests here are shallow or mocked, fixing
them is part of your change.
- **Screenshots + logs at every phase**, plus a **complete walkthrough video/run-through** of
the entire feature or view, start to finish (`bun run test:e2e:record`).
- **Manually review every artifact the change touches** — never just the green check: client
logs (console + network), server logs (`[ClassName] …`), the model trajectories in and out,
before/after full-page screenshots, **and the domain artifacts listed below for this package.**
- **No residuals. No shortcuts.** The goal is not "done" — it is *everything* done. Clear every
blocker by the **hard path**: build the real architecture, stand up the real
model/device/service, actually test it. Never leave a TODO, a stub, a stepping-stone, or a
"follow-up." When unsure, research thoroughly, weigh the options, and ship the best,
highest-effort, production-ready version. Keep going until every possibility is exhausted.
Artifacts → attached inline in the PR (MP4 video, JPG screenshots, logs in ``); attach each evidence type **or**
explicitly mark it N/A with a reason — never leave it blank. If `develop` moved and changed
behavior, **re-capture** evidence; stale proof is worse than none.
**Capture & manually review for this package — wallet / chain / contracts:**
- On-chain transaction hash(es) + explorer link, wallet balance **before and after**, and the signed-payload/log trail — run against a testnet or fork.
- Revert / insufficient-funds / nonce / gas-estimation-failure paths, and signature-authorization (role/permission) checks.
- The decision trajectory when an agent initiated the on-chain action.
- Never a mocked RPC asserted green — prove the chain state actually changed.