# 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.
