Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
run: uv sync --all-extras

- name: Run mypy
run: uv run mypy polymarket_us
run: uv run mypy polymarket_us tests/types/order_snapshot.py

test:
runs-on: ubuntu-latest
Expand Down
36 changes: 24 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,16 @@ client = PolymarketUS(
)

# Create an order
order = client.orders.create({
"marketSlug": "btc-100k-2025",
"intent": "ORDER_INTENT_BUY_LONG",
"type": "ORDER_TYPE_LIMIT",
"price": {"value": "0.55", "currency": "USD"},
"quantity": 100,
"tif": "TIME_IN_FORCE_GOOD_TILL_CANCEL",
})
order = client.orders.create(
{
"marketSlug": "btc-100k-2025",
"intent": "ORDER_INTENT_BUY_LONG",
"type": "ORDER_TYPE_LIMIT",
"price": {"value": "0.55", "currency": "USD"},
"quantity": 100,
"tif": "TIME_IN_FORCE_GOOD_TILL_CANCEL",
}
)

# Get open orders
open_orders = client.orders.list()
Expand Down Expand Up @@ -92,6 +94,7 @@ import asyncio
import os
from polymarket_us import AsyncPolymarketUS


async def main():
async with AsyncPolymarketUS(
key_id=os.environ["POLYMARKET_KEY_ID"],
Expand All @@ -105,6 +108,7 @@ async def main():
print(f"Found {len(events['events'])} events")
print(f"Found {len(markets['markets'])} markets")


asyncio.run(main())
```

Expand All @@ -116,7 +120,7 @@ The SDK automatically signs requests with your credentials:

```python
client = PolymarketUS(
key_id="your-api-key-id", # UUID
key_id="your-api-key-id", # UUID
secret_key="your-secret-key", # Base64-encoded Ed25519 private key
)
```
Expand Down Expand Up @@ -156,8 +160,8 @@ except APIConnectionError as e:
client = PolymarketUS(
key_id="your-key-id",
secret_key="your-secret-key",
timeout=30.0, # Request timeout in seconds (default: 30.0)
max_retries=2, # Automatic retries for idempotent requests (default: 2)
timeout=30.0, # Request timeout in seconds (default: 30.0)
max_retries=2, # Automatic retries for idempotent requests (default: 2)
)
```

Expand Down Expand Up @@ -186,11 +190,16 @@ except APIError as e:
> **Note**: WebSocket connections are async-only due to their event-driven nature.
> Use `asyncio.run()` when working with the sync client, or use `AsyncPolymarketUS` directly.

`SUBSCRIPTION_TYPE_ORDER` streams updates only. Request a one-shot order snapshot
separately with `SUBSCRIPTION_TYPE_ORDER_SNAPSHOT` and a distinct request ID. A
successful snapshot ends with an `eof: true` frame; failures use the `error` handler.

```python
import asyncio
import os
from polymarket_us import PolymarketUS


async def main():
client = PolymarketUS(
key_id=os.environ["POLYMARKET_KEY_ID"],
Expand All @@ -201,7 +210,8 @@ async def main():
private_ws = client.ws.private()

def on_order_snapshot(data):
print(f"Open orders: {data['orderSubscriptionSnapshot']['orders']}")
snapshot = data["orderSubscriptionSnapshot"]
print(f"Order snapshot: {snapshot['orders']}, eof={snapshot['eof']}")

def on_order_update(data):
print(f"Order execution: {data['orderSubscriptionUpdate']['execution']}")
Expand All @@ -212,6 +222,7 @@ async def main():

await private_ws.connect()
await private_ws.subscribe("order-sub-1", "SUBSCRIPTION_TYPE_ORDER")
await private_ws.subscribe("order-snapshot-1", "SUBSCRIPTION_TYPE_ORDER_SNAPSHOT")
await private_ws.subscribe("pos-sub-1", "SUBSCRIPTION_TYPE_POSITION")
await private_ws.subscribe("balance-sub-1", "SUBSCRIPTION_TYPE_ACCOUNT_BALANCE")

Expand All @@ -231,6 +242,7 @@ async def main():
await private_ws.close()
await markets_ws.close()


asyncio.run(main())
```

Expand Down
3 changes: 2 additions & 1 deletion polymarket_us/websocket/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

PrivateSubscriptionType = Literal[
"SUBSCRIPTION_TYPE_ORDER",
"SUBSCRIPTION_TYPE_ORDER_SNAPSHOT",
"SUBSCRIPTION_TYPE_POSITION",
"SUBSCRIPTION_TYPE_ACCOUNT_BALANCE",
]
Expand Down Expand Up @@ -51,7 +52,7 @@ class OrderSnapshot(TypedDict):
"""Order snapshot message."""

requestId: str
subscriptionType: Literal["SUBSCRIPTION_TYPE_ORDER"]
subscriptionType: Literal["SUBSCRIPTION_TYPE_ORDER_SNAPSHOT"]
orderSubscriptionSnapshot: _OrderSubscriptionSnapshot


Expand Down
65 changes: 65 additions & 0 deletions tests/test_order_snapshot_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""Offline contracts for private order snapshot subscriptions."""

import json
from unittest.mock import AsyncMock, Mock

import pytest

from polymarket_us.errors import WebSocketError
from polymarket_us.websocket.private import PrivateWebSocket
from polymarket_us.websocket.types import OrderSnapshot

TERMINAL: OrderSnapshot = {
"requestId": "snapshot-1",
"subscriptionType": "SUBSCRIPTION_TYPE_ORDER_SNAPSHOT",
"orderSubscriptionSnapshot": {"orders": [], "eof": True},
}


@pytest.fixture
def ws() -> PrivateWebSocket:
# Synthetic key from the public SDK tests; no live connection is made.
return PrivateWebSocket(
key_id="offline", secret_key="nWGxne/9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A="
)


async def test_live_and_snapshot_subscriptions_use_distinct_types(ws: PrivateWebSocket) -> None:
send = AsyncMock()
ws.send = send
await ws.subscribe_orders("live-1")
await ws.subscribe("snapshot-1", "SUBSCRIPTION_TYPE_ORDER_SNAPSHOT")
assert [call.args[0] for call in send.call_args_list] == [
{"subscribe": {"requestId": "live-1", "subscriptionType": "SUBSCRIPTION_TYPE_ORDER"}},
{
"subscribe": {
"requestId": "snapshot-1",
"subscriptionType": "SUBSCRIPTION_TYPE_ORDER_SNAPSHOT",
}
},
]


@pytest.mark.parametrize("failed", [False, True])
def test_terminal_snapshot_dispatch(ws: PrivateWebSocket, failed: bool) -> None:
snapshot = Mock()
error = Mock()
message = Mock()
ws.on("order_snapshot", snapshot)
ws.on("error", error)
ws.on("message", message)
frame = dict(TERMINAL)
if failed:
frame["error"] = "deadline exceeded"
ws._handle_message(json.dumps(frame))
message.assert_called_once_with(frame)
if failed:
snapshot.assert_not_called()
error.assert_called_once()
failure = error.call_args.args[0]
assert isinstance(failure, WebSocketError)
assert failure.request_id == "snapshot-1"
assert str(failure) == "deadline exceeded"
else:
snapshot.assert_called_once_with(frame)
error.assert_not_called()
15 changes: 15 additions & 0 deletions tests/types/order_snapshot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Consumer typing checks included in the existing mypy CI job."""

from polymarket_us.websocket.private import PrivateWebSocket
from polymarket_us.websocket.types import OrderSnapshot


async def request_snapshot(ws: PrivateWebSocket) -> None:
await ws.subscribe("snapshot-1", "SUBSCRIPTION_TYPE_ORDER_SNAPSHOT")


terminal: OrderSnapshot = {
"requestId": "snapshot-1",
"subscriptionType": "SUBSCRIPTION_TYPE_ORDER_SNAPSHOT",
"orderSubscriptionSnapshot": {"orders": [], "eof": True},
}
Loading