Packages

Standalone Elixir client for the Polymarket CLOB API.

Current section

Files

Jump to
Raw

README.md

# PolymarketClob
Standalone Elixir client for the Polymarket CLOB API.
This package is being built as the first publishable version of `polymarket_clob`.
It targets Polymarket's current exchange model and starts from fixture-proven auth and
order-signing primitives before adding higher-level HTTP, order construction, and
WebSocket APIs.
## Current Status
Implemented:
- current CLOB contract address config for Polygon and Amoy
- client struct with address derivation and signature type defaults
- L1 ClobAuth signing with `nonce`
- L2 HMAC request signing
- HTTP wrapper with public/private helpers and L2 header injection
- read-only market-data and account endpoint wrappers
- write endpoint wrappers for posting already-built signed orders and cancellations
- float-based order rounding and local limit/market order builders
- order EIP-712 typed data, order hash, and signature generation
- deterministic parity fixtures generated from `Polymarket/py-clob-client-v2`
Not implemented yet:
- allowance updates and write endpoints beyond already-built order posting/cancellation
- WebSocket clients
## Examples
### Client
```elixir
client =
PolymarketClob.Client.new(
private_key: System.fetch_env!("POLY_PRIVATE_KEY"),
api_key: System.get_env("POLY_API_KEY"),
api_secret: System.get_env("POLY_API_SECRET"),
api_passphrase: System.get_env("POLY_API_PASSPHRASE")
)
```
If API credentials are omitted, the client defaults to EOA signature type `0`. If all API
credential fields are present, it defaults to Polymarket Gnosis Safe signature type `2`.
Partial API credentials raise.
### Public Market Data Reads
```elixir
{:ok, book} =
PolymarketClob.API.MarketData.get_order_book("1234567890")
{:ok, price} =
PolymarketClob.API.MarketData.get_price("1234567890", :buy)
{:ok, market_info} =
PolymarketClob.API.MarketData.get_clob_market_info("0xconditionid")
```
### Private Account Reads
```elixir
{:ok, balance} =
PolymarketClob.API.Account.get_balance_allowance(client,
asset_type: "CONDITIONAL",
token_id: "1234567890"
)
{:ok, open_orders_page} =
PolymarketClob.API.Account.get_open_orders(client,
market: "0xconditionid",
next_cursor: "MA=="
)
{:ok, trades_page} =
PolymarketClob.API.Account.get_trades(client,
market: "0xconditionid",
next_cursor: "MA=="
)
```
Private GET requests sign the base endpoint path, matching the official Python client, while
query params are still appended to the URL sent to the CLOB.
### Local Signed Limit Order
```elixir
signed_order =
PolymarketClob.Order.Builder.build_limit_order(
client,
"1234567890",
10.0,
0.55,
:buy,
tick_size: "0.01",
neg_risk: false
)
```
This only builds and signs the order locally. To post an already-built signed order:
```elixir
{:ok, response} =
PolymarketClob.API.Orders.post_order(client, signed_order)
```
## Endpoint Coverage
Audited against `Polymarket/py-clob-client-v2` commit
`a2ec069fb66096e2b80ff0b7fdf628fc41d6352c`.
Coverage as of the audited commit: 44 of 50 candidate endpoints implemented
(rewards, RFQ, and builder surface are intentionally skipped — see
`ENDPOINT_COVERAGE.md`).
Implemented public read-only endpoints:
- `GET /ok`
- `GET /version`
- `GET /time`
- `GET /markets`
- `GET /simplified-markets`
- `GET /sampling-markets`
- `GET /sampling-simplified-markets`
- `GET /markets/:condition_id`
- `GET /clob-markets/:condition_id`
- `GET /markets-by-token/:token_id`
- `GET /markets/live-activity/:condition_id`
- `GET /book?token_id=...`
- `POST /books`
- `GET /tick-size?token_id=...`
- `GET /neg-risk?token_id=...`
- `GET /fee-rate?token_id=...`
- `GET /midpoint?token_id=...`
- `POST /midpoints`
- `GET /price?token_id=...&side=...`
- `POST /prices`
- `GET /spread?token_id=...`
- `POST /spreads`
- `GET /last-trade-price?token_id=...`
- `POST /last-trades-prices`
- `GET /prices-history?...`
Implemented private read-only endpoints:
- `GET /auth/api-keys`
- `GET /auth/ban-status/closed-only`
- `GET /data/order/:order_id`
- `GET /data/orders?...`
- `GET /data/pre-migration-orders?...`
- `GET /data/trades?...` (also exposed as `Account.get_trades_paginated/3`)
- `GET /order-scoring?order_id=...`
- `GET /balance-allowance?...`
- `GET /notifications?signature_type=...`
Implemented private write endpoints:
- `POST /order` with an already-built signed order map
- `POST /orders` with already-built signed order maps
- `POST /orders-scoring` with a list of order IDs
- `DELETE /order` with an order ID
- `DELETE /orders` with order IDs
- `DELETE /cancel-all`
- `DELETE /cancel-market-orders` with `market` and/or `asset_id`
- `GET /balance-allowance/update?...`
- `DELETE /notifications?ids=...`
Not implemented yet:
- L1/L2 API key lifecycle (`create_api_key`, `derive_api_key`, `delete_api_key`,
readonly key management) — see `ENDPOINT_COVERAGE.md` for the gating
product/domain decisions
- reward, RFQ, builder, and WebSocket endpoints (intentionally out of scope for
this package; see `ENDPOINT_COVERAGE.md`)
- bot integration
Intentional differences from the Python client:
- `get_open_orders/3`, `get_trades/3`, and `get_pre_migration_orders/3` are direct page
wrappers. They do not auto-paginate yet.
- `get_prices_history/2` accepts caller-supplied CLOB query names such as `:market`,
`:startTs`, `:endTs`, `:fidelity`, and `:interval`; it does not validate interval
combinations yet.
- `get_fee_rate/2` is exposed for legacy compatibility; order-building work should prefer
`get_clob_market_info/2` fee data when fee-aware helpers are added.
## Installation
After publication, add `polymarket_clob` to your dependencies:
```elixir
def deps do
[
{:polymarket_clob, "~> 0.1.0"}
]
end
```
## Fixture Generation
The checked-in fixtures under `test/fixtures/parity` were generated from the official Python
v2 client at commit `a2ec069fb66096e2b80ff0b7fdf628fc41d6352c`:
```bash
PY_CLOB_CLIENT_V2_PATH=/path/to/py-clob-client-v2 \
python3 scripts/generate_parity_fixtures.py
```
The script defaults to `/tmp/py-clob-client-v2`, which is useful for local scratch
checkouts during development.