From 1708ca897be75d892585b5dcf69565b3876f9876 Mon Sep 17 00:00:00 2001 From: Cesare Naldi <3353250+cesarenaldi@users.noreply.github.com> Date: Wed, 17 Jun 2026 10:05:18 +0200 Subject: [PATCH 1/2] Reapply "Merge pull request #104 from Polymarket/feat/rfq-trade-broadcast" This reverts commit a3eab9f706b14465557d68dcfcb47e22924ae16e. --- src/polymarket/__init__.py | 2 ++ src/polymarket/_internal/rfq.py | 21 +++++++++++++ src/polymarket/rfq.py | 20 +++++++++++- tests/integration/test_rfq.py | 54 ++++++++++++++++++++++++++++++++- 4 files changed, 95 insertions(+), 2 deletions(-) diff --git a/src/polymarket/__init__.py b/src/polymarket/__init__.py index d35baef..75b19cb 100644 --- a/src/polymarket/__init__.py +++ b/src/polymarket/__init__.py @@ -152,6 +152,7 @@ RfqRequestorPublicId, RfqSession, RfqSide, + RfqTradeEvent, ) from polymarket.transactions import ( EoaTransactionHandle, @@ -290,6 +291,7 @@ "RfqRequestorPublicId", "RfqSession", "RfqSide", + "RfqTradeEvent", "SearchResults", "SearchTag", "SecureClient", diff --git a/src/polymarket/_internal/rfq.py b/src/polymarket/_internal/rfq.py index 21382f3..8201e50 100644 --- a/src/polymarket/_internal/rfq.py +++ b/src/polymarket/_internal/rfq.py @@ -59,6 +59,7 @@ RfqRequestedSize, RfqRequestedSizeUnit, RfqSide, + RfqTradeEvent, ) from polymarket.types import EvmAddress, HexString, TransactionHash @@ -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: @@ -634,6 +637,24 @@ def _parse_confirmation_request( ) +def _parse_trade(raw: dict[str, object]) -> RfqTradeEvent: + return RfqTradeEvent( + type="trade", + rfq_id=_expect_str(raw, "rfq_id"), + requestor_public_id=_expect_str(raw, "requestor_public_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")), + tx_hash=TransactionHash(_expect_str(raw, "tx_hash")), + executed_at=_expect_int(raw, "executed_at"), + ) + + def _parse_requested_size(raw: dict[str, object]) -> RfqRequestedSize: return RfqRequestedSize( unit=RfqRequestedSizeUnit(_expect_str(raw, "unit")), diff --git a/src/polymarket/rfq.py b/src/polymarket/rfq.py index d94118c..acce301 100644 --- a/src/polymarket/rfq.py +++ b/src/polymarket/rfq.py @@ -111,6 +111,21 @@ class RfqExecutionUpdateEvent: tx_hash: TransactionHash | None = None +@dataclass(frozen=True, slots=True, kw_only=True) +class RfqTradeEvent: + type: Literal["trade"] + rfq_id: RfqId + requestor_public_id: RfqRequestorPublicId + condition_id: ComboConditionId + leg_position_ids: tuple[PositionId, ...] + direction: RfqDirection + side: RfqSide + price: Decimal + size: Decimal + tx_hash: TransactionHash + executed_at: int + + @dataclass(frozen=True, slots=True, kw_only=True) class RfqQuoteRequestEvent: type: Literal["quote_request"] @@ -166,7 +181,9 @@ async def decline(self) -> RfqConfirmationAck: ) -RfqEvent = RfqQuoteRequestEvent | RfqConfirmationRequestEvent | RfqExecutionUpdateEvent +RfqEvent = ( + RfqQuoteRequestEvent | RfqConfirmationRequestEvent | RfqExecutionUpdateEvent | RfqTradeEvent +) class RfqQuoteRejectedError(PolymarketError): @@ -259,4 +276,5 @@ async def __aexit__( "RfqRequestorPublicId", "RfqSession", "RfqSide", + "RfqTradeEvent", ] diff --git a/tests/integration/test_rfq.py b/tests/integration/test_rfq.py index 02af98d..ce7361a 100644 --- a/tests/integration/test_rfq.py +++ b/tests/integration/test_rfq.py @@ -16,10 +16,12 @@ RfqConfirmationRequestEvent, RfqErrorCode, RfqExecutionStatus, + RfqExecutionUpdateEvent, RfqQuoteRejectedError, RfqQuoteRequestEvent, RfqQuoteSource, RfqRequestedSizeUnit, + RfqTradeEvent, ) pytestmark = pytest.mark.anyio @@ -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, @@ -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 @@ -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.requestor_public_id == "requestor-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 event.tx_hash == 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], @@ -817,6 +853,22 @@ def _manual_quote_frame() -> dict[str, Any]: } +def _trade_message() -> dict[str, object]: + return { + "type": "RFQ_TRADE", + "rfq_id": RFQ_ID, + "requestor_public_id": "requestor-abc", + "condition_id": CONDITION_ID, + "leg_position_ids": [YES_POSITION_ID, NO_POSITION_ID], + "direction": "BUY", + "side": "YES", + "price_e6": "125000", + "size_e6": "800000", + "tx_hash": TX_HASH, + "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: From 17c03746600305d9ad94cc6a6d566221f177143c Mon Sep 17 00:00:00 2001 From: Shanti Chanal Date: Thu, 18 Jun 2026 00:39:24 -0400 Subject: [PATCH 2/2] fix(rfq): align public trade event fields --- src/polymarket/_internal/rfq.py | 3 +-- src/polymarket/rfq.py | 3 +-- tests/integration/test_rfq.py | 7 +++---- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/polymarket/_internal/rfq.py b/src/polymarket/_internal/rfq.py index 8201e50..23b1301 100644 --- a/src/polymarket/_internal/rfq.py +++ b/src/polymarket/_internal/rfq.py @@ -641,7 +641,7 @@ def _parse_trade(raw: dict[str, object]) -> RfqTradeEvent: return RfqTradeEvent( type="trade", rfq_id=_expect_str(raw, "rfq_id"), - requestor_public_id=_expect_str(raw, "requestor_public_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") @@ -650,7 +650,6 @@ def _parse_trade(raw: dict[str, object]) -> RfqTradeEvent: side=RfqSide(_expect_str(raw, "side")), price=_e6_to_decimal(_expect_str(raw, "price_e6")), size=_e6_to_decimal(_expect_str(raw, "size_e6")), - tx_hash=TransactionHash(_expect_str(raw, "tx_hash")), executed_at=_expect_int(raw, "executed_at"), ) diff --git a/src/polymarket/rfq.py b/src/polymarket/rfq.py index acce301..053b283 100644 --- a/src/polymarket/rfq.py +++ b/src/polymarket/rfq.py @@ -115,14 +115,13 @@ class RfqExecutionUpdateEvent: class RfqTradeEvent: type: Literal["trade"] rfq_id: RfqId - requestor_public_id: RfqRequestorPublicId + requester_id: RfqRequestorPublicId condition_id: ComboConditionId leg_position_ids: tuple[PositionId, ...] direction: RfqDirection side: RfqSide price: Decimal size: Decimal - tx_hash: TransactionHash executed_at: int diff --git a/tests/integration/test_rfq.py b/tests/integration/test_rfq.py index ce7361a..c48a181 100644 --- a/tests/integration/test_rfq.py +++ b/tests/integration/test_rfq.py @@ -190,14 +190,14 @@ async def handler(ws: ServerConnection) -> None: assert isinstance(event, RfqTradeEvent) assert event.type == "trade" assert event.rfq_id == RFQ_ID - assert event.requestor_public_id == "requestor-abc" + 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 event.tx_hash == TX_HASH + assert not hasattr(event, "tx_hash") assert event.executed_at == 1780854786039 break @@ -857,14 +857,13 @@ def _trade_message() -> dict[str, object]: return { "type": "RFQ_TRADE", "rfq_id": RFQ_ID, - "requestor_public_id": "requestor-abc", + "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", - "tx_hash": TX_HASH, "executed_at": 1780854786039, }