Convenience module that closes the loop from a raw Polymarket CLOB
/book response to a normalized PaperEx.MarketSnapshot.
This module composes two pieces that already exist independently:
PaperExPolymarket.Market— turns a token id / CLOB market payload into aPaperEx.Instrument.PaperExPolymarket.OrderBook— turns a CLOB book body into aPaperEx.MarketSnapshotfor that instrument.
Network IO is injected, never owned: callers pass either a
pre-fetched book body (from_clob_book/3) or a fetcher
function/MFA tuple (fetch_order_book_snapshot/3). The adapter
package itself never opens a socket.
Why a separate module instead of in polymarket
polymarket is upstream of paper packages; it must not depend on
paper_ex or paper_ex_polymarket. Helpers that return a
PaperEx.MarketSnapshot therefore belong here. A consumer wires
their own polymarket_clob (or other) fetcher into this module's
fetch_order_book_snapshot/3.
Two entry points
from_clob_book/3 — already have a body
Use this when you have already fetched the CLOB /book body
yourself (typically via PolymarketClob.API.MarketData.get_order_book/2):
{:ok, body} = PolymarketClob.API.MarketData.get_order_book(token_id)
{:ok, snap} = PaperExPolymarket.Snapshot.from_clob_book(token_id, body)An already-normalized PaperEx.Instrument can be passed instead of
a token id; the adapter will simply skip the lookup step.
fetch_order_book_snapshot/3 — inject a fetcher
Use this when you want Snapshot to handle the fetch step too.
fetcher is one of:
a 1-arity anonymous function
fn token_id -> {:ok, body} | {:error, term} end- a
{module, function, args}tuple invoked asapply(module, function, args ++ [token_id])
The fetcher is the only piece that talks to the network. Tests
pass a stub function; production wires this through
polymarket_clob. The adapter has zero opinions about which
network library or which credentials the fetcher uses.
Errors
{:error, :polymarket_invalid_market_payload}from theInstrumentstep (unresolvable instrument reference).{:error, :polymarket_invalid_book}from theOrderBookstep (malformed book body).{:error, :polymarket_invalid_fetcher}fromfetch_order_book_snapshot/3when thefetcheris neither a 1-arity function nor a{module, function, args}tuple.- Anything the fetcher returns as
{:error, term}is propagated verbatim.
Summary
Types
Either a 1-arity function or a {module, function, extra_args}
tuple. The token id is appended as the final argument.
A token id or an already-normalized PaperEx.Instrument. Token
ids may be strings or {:token_id, "…"} tuples; instrument
values are passed through unchanged.
Functions
Fetch a CLOB book for instrument_ref via the injected fetcher
and normalize the result.
Build a PaperEx.MarketSnapshot from an already-fetched CLOB book
body for the given instrument reference.
Types
Either a 1-arity function or a {module, function, extra_args}
tuple. The token id is appended as the final argument.
@type instrument_ref() :: String.t() | PaperEx.Instrument.t() | {:token_id, String.t()}
A token id or an already-normalized PaperEx.Instrument. Token
ids may be strings or {:token_id, "…"} tuples; instrument
values are passed through unchanged.
Functions
@spec fetch_order_book_snapshot(fetcher(), instrument_ref(), keyword()) :: {:ok, PaperEx.MarketSnapshot.t()} | {:error, term()}
Fetch a CLOB book for instrument_ref via the injected fetcher
and normalize the result.
Tests pass fn _id -> {:ok, fixture_body} end. Production wires
this through polymarket_clob's order-book endpoint with whatever
client/credentials the caller has assembled.
Returns {:error, term} if the fetcher errors; the error is
propagated unchanged.
@spec from_clob_book(instrument_ref(), map(), keyword()) :: {:ok, PaperEx.MarketSnapshot.t()} | {:error, term()}
Build a PaperEx.MarketSnapshot from an already-fetched CLOB book
body for the given instrument reference.
instrument_ref may be:
- a
PaperEx.Instrument— used verbatim; - a token id string — resolved via
PaperExPolymarket.Market.from_token_id/2; - a
{:token_id, "…"}tuple — equivalent to the bare string.
Options:
:instrument_opts— keyword list forwarded toMarket.from_token_id/2when resolving a token id.