Skip to content
Closed
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: 2 additions & 0 deletions src/polymarket/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@
RfqRequestorPublicId,
RfqSession,
RfqSide,
RfqTradeEvent,
)
from polymarket.transactions import (
EoaTransactionHandle,
Expand Down Expand Up @@ -290,6 +291,7 @@
"RfqRequestorPublicId",
"RfqSession",
"RfqSide",
"RfqTradeEvent",
"SearchResults",
"SearchTag",
"SecureClient",
Expand Down
20 changes: 20 additions & 0 deletions src/polymarket/_internal/rfq.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
RfqRequestedSize,
RfqRequestedSizeUnit,
RfqSide,
RfqTradeEvent,
)
from polymarket.types import EvmAddress, HexString, TransactionHash

Expand Down Expand Up @@ -434,6 +435,8 @@ def _on_message(self, raw: object) -> None:
tx_hash=TransactionHash(tx_hash) if isinstance(tx_hash, str) else None,
)
)
elif message_type == "RFQ_TRADE":
self._push(_parse_trade(message))
elif message_type == "RFQ_ERROR":
self._handle_rfq_error(message)
except BaseException as error:
Expand Down Expand Up @@ -634,6 +637,23 @@ def _parse_confirmation_request(
)


def _parse_trade(raw: dict[str, object]) -> RfqTradeEvent:
return RfqTradeEvent(
type="trade",
rfq_id=_expect_str(raw, "rfq_id"),
requester_id=_expect_str(raw, "requester_id"),
condition_id=_expect_combo_condition_id(raw, "condition_id"),
leg_position_ids=tuple(
PositionId(item) for item in _expect_str_list(raw, "leg_position_ids")
),
direction=RfqDirection(_expect_str(raw, "direction")),
side=RfqSide(_expect_str(raw, "side")),
price=_e6_to_decimal(_expect_str(raw, "price_e6")),
size=_e6_to_decimal(_expect_str(raw, "size_e6")),
executed_at=_expect_int(raw, "executed_at"),
)


def _parse_requested_size(raw: dict[str, object]) -> RfqRequestedSize:
return RfqRequestedSize(
unit=RfqRequestedSizeUnit(_expect_str(raw, "unit")),
Expand Down
19 changes: 18 additions & 1 deletion src/polymarket/rfq.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,20 @@ class RfqExecutionUpdateEvent:
tx_hash: TransactionHash | None = None


@dataclass(frozen=True, slots=True, kw_only=True)
class RfqTradeEvent:
type: Literal["trade"]
rfq_id: RfqId
requester_id: RfqRequestorPublicId
condition_id: ComboConditionId
leg_position_ids: tuple[PositionId, ...]
direction: RfqDirection
side: RfqSide
price: Decimal
size: Decimal
executed_at: int


@dataclass(frozen=True, slots=True, kw_only=True)
class RfqQuoteRequestEvent:
type: Literal["quote_request"]
Expand Down Expand Up @@ -166,7 +180,9 @@ async def decline(self) -> RfqConfirmationAck:
)


RfqEvent = RfqQuoteRequestEvent | RfqConfirmationRequestEvent | RfqExecutionUpdateEvent
RfqEvent = (
RfqQuoteRequestEvent | RfqConfirmationRequestEvent | RfqExecutionUpdateEvent | RfqTradeEvent
)


class RfqQuoteRejectedError(PolymarketError):
Expand Down Expand Up @@ -259,4 +275,5 @@ async def __aexit__(
"RfqRequestorPublicId",
"RfqSession",
"RfqSide",
"RfqTradeEvent",
]
53 changes: 52 additions & 1 deletion tests/integration/test_rfq.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
RfqConfirmationRequestEvent,
RfqErrorCode,
RfqExecutionStatus,
RfqExecutionUpdateEvent,
RfqQuoteRejectedError,
RfqQuoteRequestEvent,
RfqQuoteSource,
RfqRequestedSizeUnit,
RfqTradeEvent,
)

pytestmark = pytest.mark.anyio
Expand Down Expand Up @@ -118,6 +120,7 @@ async def handler(ws: ServerConnection) -> None:
}
)
)
await ws.send(json.dumps(_trade_message()))

async with (
_ws_server(handler) as ws_url,
Expand All @@ -135,7 +138,7 @@ async def handler(ws: ServerConnection) -> None:
ack = await event.confirm()
assert ack.rfq_id == RFQ_ID
assert ack.quote_id == QUOTE_ID
else:
elif isinstance(event, RfqExecutionUpdateEvent):
assert event.status is RfqExecutionStatus.MINED
assert event.tx_hash == TX_HASH
break
Expand Down Expand Up @@ -166,6 +169,39 @@ async def handler(ws: ServerConnection) -> None:
}


@pytest.mark.integration
async def test_rfq_session_receives_confirmed_trade_broadcast(
require_env: Callable[[str], str],
) -> None:
async def handler(ws: ServerConnection) -> None:
async for raw in ws:
assert isinstance(raw, str)
frame = json.loads(raw)
if frame["type"] == "auth":
await ws.send(json.dumps({"type": "auth", "success": True}))
await ws.send(json.dumps(_trade_message()))

async with (
_ws_server(handler) as ws_url,
_rfq_client(require_env, ws_url) as client,
client.open_rfq_session() as session,
):
async for event in session:
assert isinstance(event, RfqTradeEvent)
assert event.type == "trade"
assert event.rfq_id == RFQ_ID
assert event.requester_id == "requester-abc"
assert event.condition_id == CONDITION_ID
assert event.leg_position_ids == (YES_POSITION_ID, NO_POSITION_ID)
assert event.direction == "BUY"
assert event.side == "YES"
assert event.price == Decimal("0.125")
assert event.size == Decimal("0.8")
assert not hasattr(event, "tx_hash")
assert event.executed_at == 1780854786039
break


@pytest.mark.integration
async def test_rfq_session_quotes_explicit_inventory_size(
require_env: Callable[[str], str],
Expand Down Expand Up @@ -817,6 +853,21 @@ def _manual_quote_frame() -> dict[str, Any]:
}


def _trade_message() -> dict[str, object]:
return {
"type": "RFQ_TRADE",
"rfq_id": RFQ_ID,
"requester_id": "requester-abc",
"condition_id": CONDITION_ID,
"leg_position_ids": [YES_POSITION_ID, NO_POSITION_ID],
"direction": "BUY",
"side": "YES",
"price_e6": "125000",
"size_e6": "800000",
"executed_at": 1780854786039,
}


def _first_frame(frames: list[dict[str, Any]], frame_type: str) -> dict[str, Any]:
for frame in frames:
if frame.get("type") == frame_type:
Expand Down
Loading