Skip to content

mdon/polymarket_sdk

Repository files navigation

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

{: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

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:

{: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

{: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:

def deps do
  [
    {:polymarket, "~> 0.1.0"}
  ]
end

License

MIT.

About

Higher-level Elixir SDK for Polymarket — Gamma discovery, Data API, CTF/pUSD on-chain helpers, and the RTDS real-time trade feed. Built on polymarket_clob.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages