Packages

Higher-level Elixir SDK for Polymarket: Gamma market discovery, Data API, CTF/pUSD helpers, and convenience facades. Depends on polymarket_clob for the low-level CLOB surface.

Current section

Files

Jump to
Raw

README.md

# Polymarket
Higher-level Elixir SDK for Polymarket.
This package builds on `polymarket_clob`, which owns the low-level CLOB
surface (auth, EIP-712 order signing, order placement, balances, order books,
prices, market metadata). Use `polymarket_clob` directly if you only need that
surface.
`polymarket` is for users who want the broader Polymarket SDK — market
discovery, normalized public data, P&L helpers, on-chain CTF and pUSD
operations, and the real-time trade feed — without bringing in the runnable
trading bot.
## Status — 0.1.0
The package compiles, depends on `polymarket_clob`, exposes
`Polymarket.Config` for shared host configuration, and provides the first
read-only slice of `Polymarket.Gamma` for market discovery. Subsequent
phases will add the Data API, CTF/pUSD helpers, RTDS, and a higher-level
facade.
Implemented:
- Project metadata and Hex packaging readiness.
- `Polymarket.Config` — shared Gamma and Data API host constants.
- `Polymarket.HTTP` — shared transport helper (host, path, params, retry,
Req.Test plug). Used by `Gamma` and `Data`.
- `Polymarket.Gamma` — read-only Gamma API wrappers:
- `get_markets/1` — list markets with caller-provided query params
(limit, offset, active, closed, archived, order, conditionId, slug, …).
- `get_market/2` — fetch a single market by Gamma id or slug.
- `get_events/1` — list events with caller-provided query params.
- `get_event/2` — fetch a single event by Gamma id or slug.
- `Polymarket.Data` — read-only Data API wrappers:
- `get_positions/2` — current positions for a wallet address (size,
avg_price, current_value, redeemable). `:size_threshold` opt; default
`0.1` matches the live bot's reconciler usage.
- `get_activity/2` — TRADE + REDEEM events for a wallet address with
fee-inclusive `usdcSize`. `:limit` (default `1000`), `:offset`.
- `get_trades/2` — public trade history for a wallet address.
`:limit` (default `20`), `:after`, `:asset_id`.
- `Polymarket.Activity` — pure normalization and join helpers (no HTTP):
- `normalize/1` — turn raw `/activity` rows into typed
`Polymarket.Activity.Event` structs.
- `cost_for_order/3` — fee-inclusive USDC cost for a CLOB order id,
joining activity ↔ fills via `transactionHash`.
- `revenue_for_order/3` — fee-inclusive USDC revenue for a SELL.
- `redemption_for_market/2` — sum REDEEM payouts for a `conditionId`.
- Returns `nil` when no event matches; sums across multiple matching
events (handles GTC partial fills correctly).
- `Polymarket.ABI`, `Polymarket.ERC20`, `Polymarket.CTF`, `Polymarket.Collateral`
— pure on-chain calldata builders for the fixed surfaces (ERC-20
`approve`, CTF `redeemPositions` / `mergePositions` / `splitPosition`,
Collateral `wrap` / `unwrap`). Validated byte-for-byte against
ethers.js v6 in `test/fixtures/calldata.json`.
- Submit wrappers compose calldata + tx + RPC into one call:
`Polymarket.ERC20.approve/2`, `Polymarket.CTF.redeem_positions/2`,
`Polymarket.CTF.merge_positions/2`, `Polymarket.CTF.split_position/2`,
`Polymarket.Collateral.wrap/2`, `Polymarket.Collateral.unwrap/2`.
Each takes `(rpc, opts)` and returns `{:ok, tx_hash}` or
`{:error, term}`. Required `opts` are caller-provided
(`:nonce`, `:gas_price`, `:gas_limit`, `:chain_id`,
`:private_key`, `:contract`, plus function-specific args). No
auto-fetch of nonce/gas — use `Polymarket.RPC.nonce/2`,
`gas_price/1`, and `estimate_gas/2` first.
- `Polymarket.RLP` — RLP encoder for Ethereum transaction
serialization. Bytes, non-negative integers, and lists.
- `Polymarket.Tx` — legacy (type-0) EIP-155 transaction
serialization and signing. `new/1`, `signing_payload/1`,
`signing_hash/1`, `sign/2`, `serialize/1`, `hash/1`. Validated
byte-for-byte against ethers.js v6 in `test/fixtures/tx.json`.
EIP-1559 type-2 deferred.
- `Polymarket.RPC` — stateless JSON-RPC 2.0 client + nonce/gas
helpers. `new/1`, `call/3` raw, plus typed helpers `chain_id/1`,
`block_number/1`, `gas_price/1`, `nonce/2-3`, `estimate_gas/2`,
`send_raw_transaction/2` (accepts hex, raw binary, or a signed
`Polymarket.Tx`). Tuple-shape errors:
`{:rpc_error, code, message, data}`, `{:http_error, ...}`,
`{:transport_error, ...}`, `{:invalid_response, ...}`.
- `Polymarket.RTDS` — minimal real-time trade feed client:
- `start_link(callback: pid)` connects to
`wss://ws-live-data.polymarket.com` and subscribes to the global
trades topic.
- Forwards every trade as `{:polymarket_rtds, :trade, payload}` to the
callback PID. `payload` is the raw map; pair with
`Polymarket.Activity.normalize/1` for typed structs.
- Configurable `:url` and `:ping_interval`.
- Phase 10 deliberate non-goals (deferred): backpressure / batching,
supervised reconnect strategy, topic generalization beyond trades,
and `{module, function}` callback shape. See module docs.
- Compile-time integration with `polymarket_clob` (path dep in this
workspace; will be replaced with the published Hex version before
publishing this package).
Not implemented yet:
- CTF redeem/merge/split (`Polymarket.CTF.*`).
- pUSD wrap/unwrap (`Polymarket.Collateral.*`).
- RTDS real-time trade feed (`Polymarket.RTDS.*`).
- Higher-level convenience facade.
## Response contract
All `Polymarket.Gamma` and `Polymarket.Data` wrappers return one of:
- `{:ok, body}` on success.
- `{:error, {:http_error, status, body}}` for non-2xx responses.
- `{:error, {:transport_error, reason}}` for Req-level failures.
See `Polymarket.HTTP`'s `@moduledoc` for the full options list (`:host`,
`:plug`, `:timeout`, `:retry`, `:max_retries`, `:headers`).
## Gamma usage
```elixir
{:ok, markets} =
Polymarket.Gamma.get_markets(
params: [limit: 100, active: true, closed: false, order: "volume24hr"]
)
{:ok, market} = Polymarket.Gamma.get_market("will-x-happen")
{:ok, events} = Polymarket.Gamma.get_events(params: [limit: 50, active: true])
```
## Data API usage
```elixir
address = "0x8f70343472CC7Fd382DE383723A0E6Ee02e2CAe5"
{:ok, positions} = Polymarket.Data.get_positions(address)
# `usdcSize` on TRADE events is fee-inclusive; this is the source of
# truth for P&L on completed BUY → REDEEM cycles.
{:ok, activity} = Polymarket.Data.get_activity(address, limit: 200)
{:ok, trades} = Polymarket.Data.get_trades(address, limit: 50)
```
## Activity normalization usage
`Polymarket.Activity` is pure: it consumes whatever maps you have and does
not fetch from the network. Pair it with the wrappers above:
```elixir
{:ok, activity} = Polymarket.Data.get_activity(address)
{:ok, fills} = PolymarketClob.API.Account.get_trades(client, market: market_id)
# Fee-inclusive cost (matches the wallet debit, not the CLOB fill price).
cost = Polymarket.Activity.cost_for_order(activity, order_id, fills["data"] || [])
# Sum of REDEEM payouts for a resolved market.
payout = Polymarket.Activity.redemption_for_market(activity, condition_id)
# Typed events for downstream consumers.
events = Polymarket.Activity.normalize(activity)
```
All four functions return `nil` when no event matches, distinguishing
"no activity yet" from "events that summed to 0".
## RTDS usage
```elixir
{:ok, _pid} = Polymarket.RTDS.start_link(callback: self())
receive do
{:polymarket_rtds, :trade, payload} ->
IO.inspect(payload, label: "rtds trade")
end
```
The payload is the raw map sent by Polymarket. Apply
`Polymarket.Activity.normalize([payload])` if you need a typed
`Polymarket.Activity.Event` struct.
## Layered architecture
```
polymarket ← this package (Gamma, Data, CTF, pUSD, RTDS, facade)
└── polymarket_clob (CLOB auth, signing, orders, books, prices)
```
## What this package is NOT
- It is not a low-level CLOB client. Use `polymarket_clob` directly for
trading, signing, and book reads.
- It is not a paper trading engine. That is a separate `paper_ex` package.
- It is not the runnable trading bot. That is a separate
`polymarket_bot` application.
## Installation
After publication:
```elixir
def deps do
[
{:polymarket, "~> 0.1.0"}
]
end
```
## License
MIT.