From 9d939f1aabcab683a820375a6caf0112a2d301c9 Mon Sep 17 00:00:00 2001 From: 8ball030 <8baller@station.codes> Date: Thu, 11 Sep 2025 23:14:56 +0100 Subject: [PATCH 1/6] feat:enabled-other-test --- tests/test_rfq.py | 64 ++++++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/tests/test_rfq.py b/tests/test_rfq.py index abbc6fbd..2c206cd5 100644 --- a/tests/test_rfq.py +++ b/tests/test_rfq.py @@ -7,9 +7,8 @@ import pytest from derive_client.data_types import OrderSide - -LEG_1_NAME = 'ETH-20240329-2400-C' -LEG_2_NAME = 'ETH-20240329-2600-C' +from derive_client.data_types.enums import Currency, InstrumentType +from derive_client.derive import DeriveClient @dataclass @@ -26,51 +25,64 @@ class Rfq: leg_2: Leg def to_dict(self): - return {"legs": [asdict(self.leg_1), asdict(self.leg_2)], "subaccount_id": self.subaccount_id} + return { + "legs": sorted([asdict(self.leg_1), asdict(self.leg_2)], key=lambda x: x['instrument_name']), + "subaccount_id": self.subaccount_id, + } -@pytest.mark.skip(reason="This test is not meant to be run in CI") def test_derive_client_create_rfq( - derive_client, + derive_client: DeriveClient, ): """ Test the DeriveClient class. """ subaccount_id = derive_client.subaccount_id - leg_1 = Leg(instrument_name=LEG_1_NAME, amount='1', direction=OrderSide.BUY.value) - leg_2 = Leg(instrument_name=LEG_2_NAME, amount='1', direction=OrderSide.SELL.value) + + markets = derive_client.fetch_instruments(instrument_type=InstrumentType.OPTION, currency=Currency.ETH) + + active_markets = [m for m in markets if m['is_active']] + assert active_markets, "No active markets found" + leg_1_name = active_markets[0]['instrument_name'] + leg_2_name = active_markets[1]['instrument_name'] + + leg_1 = Leg(instrument_name=leg_1_name, amount='1', direction=OrderSide.BUY.value) + leg_2 = Leg(instrument_name=leg_2_name, amount='1', direction=OrderSide.SELL.value) rfq = Rfq(leg_1=leg_1, leg_2=leg_2, subaccount_id=subaccount_id) - assert derive_client.send_rfq(rfq.to_dict()) + result = derive_client.send_rfq(rfq.to_dict()) + assert result['rfq_id'] + assert result['status'] == 'open' + return result + + +def test_poll_rfqs(derive_client: DeriveClient): + """ + Test the DeriveClient class. + """ + rfq_id = test_derive_client_create_rfq(derive_client).get('rfq_id') + quotes = derive_client.poll_rfqs() + rfqs = quotes.get('rfqs', []) + assert rfqs, "RFQs should not be empty" + filtered_rfqs = [r for r in rfqs if r['rfq_id'] == rfq_id] + assert filtered_rfqs, f"RFQ with id {rfq_id} not found" -@pytest.mark.skip(reason="This test is not meant to be run in CI") +@pytest.mark.skip(reason="Skipping quote creation test") def test_derive_client_create_quote( - derive_client, + derive_client: DeriveClient, ): """ Test the DeriveClient class. """ - subaccount_id = derive_client.subaccount_id - leg_1 = Leg(instrument_name=LEG_1_NAME, amount='1', direction=OrderSide.BUY.value) - leg_2 = Leg(instrument_name=LEG_2_NAME, amount='1', direction=OrderSide.SELL.value) - rfq = Rfq(leg_1=leg_1, leg_2=leg_2, subaccount_id=subaccount_id) - res = derive_client.send_rfq(rfq.to_dict()) + rfq = test_derive_client_create_rfq(derive_client) # we now create the quote quote = derive_client.create_quote_object( - rfq_id=res['rfq_id'], - legs=[asdict(leg_1), asdict(leg_2)], + rfq_id=rfq['rfq_id'], + legs=rfq['legs'], direction='sell', ) # we now sign it assert derive_client._sign_quote(quote) - - -def test_poll_rfqs(derive_client): - """ - Test the DeriveClient class. - """ - quotes = derive_client.poll_rfqs() - assert quotes From 15e7a740a60fe201328e138c6d65a5f841bbc786 Mon Sep 17 00:00:00 2001 From: 8ball030 <8baller@station.codes> Date: Thu, 11 Sep 2025 23:22:55 +0100 Subject: [PATCH 2/6] test:poll-rfqs --- tests/test_rfq.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_rfq.py b/tests/test_rfq.py index 2c206cd5..4aa1ceea 100644 --- a/tests/test_rfq.py +++ b/tests/test_rfq.py @@ -21,12 +21,11 @@ class Leg: @dataclass class Rfq: subaccount_id: str - leg_1: Leg - leg_2: Leg + legs: list[Leg] def to_dict(self): return { - "legs": sorted([asdict(self.leg_1), asdict(self.leg_2)], key=lambda x: x['instrument_name']), + "legs": sorted([asdict(leg) for leg in self.legs], key=lambda x: x['instrument_name']), "subaccount_id": self.subaccount_id, } @@ -49,7 +48,7 @@ def test_derive_client_create_rfq( leg_1 = Leg(instrument_name=leg_1_name, amount='1', direction=OrderSide.BUY.value) leg_2 = Leg(instrument_name=leg_2_name, amount='1', direction=OrderSide.SELL.value) - rfq = Rfq(leg_1=leg_1, leg_2=leg_2, subaccount_id=subaccount_id) + rfq = Rfq(legs=[leg_1, leg_2], subaccount_id=subaccount_id) result = derive_client.send_rfq(rfq.to_dict()) assert result['rfq_id'] assert result['status'] == 'open' From 540ea88f1041cf31a976c315b498fbe8f955eb7f Mon Sep 17 00:00:00 2001 From: zarathustra Date: Fri, 12 Sep 2025 14:40:51 +0200 Subject: [PATCH 3/6] tests: test_create_quote --- tests/test_rfq.py | 76 ++++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/tests/test_rfq.py b/tests/test_rfq.py index 4aa1ceea..b1f32a7d 100644 --- a/tests/test_rfq.py +++ b/tests/test_rfq.py @@ -2,40 +2,34 @@ Implement tests for the RFQ class. """ -from dataclasses import asdict, dataclass - -import pytest +from pydantic import BaseModel from derive_client.data_types import OrderSide from derive_client.data_types.enums import Currency, InstrumentType from derive_client.derive import DeriveClient -@dataclass -class Leg: +class Leg(BaseModel): instrument_name: str - amount: str + amount: float direction: str + price: float | None = None -@dataclass -class Rfq: - subaccount_id: str +class Rfq(BaseModel): + subaccount_id: int legs: list[Leg] - def to_dict(self): - return { - "legs": sorted([asdict(leg) for leg in self.legs], key=lambda x: x['instrument_name']), - "subaccount_id": self.subaccount_id, - } + def model_dump(self, *args, **kwargs): + kwargs.setdefault("exclude_none", True) + data = super().model_dump(*args, **kwargs) + data["legs"].sort(key=lambda x: x.get("instrument_name")) + return data -def test_derive_client_create_rfq( +def test_create_rfq( derive_client: DeriveClient, ): - """ - Test the DeriveClient class. - """ subaccount_id = derive_client.subaccount_id @@ -46,20 +40,18 @@ def test_derive_client_create_rfq( leg_1_name = active_markets[0]['instrument_name'] leg_2_name = active_markets[1]['instrument_name'] - leg_1 = Leg(instrument_name=leg_1_name, amount='1', direction=OrderSide.BUY.value) - leg_2 = Leg(instrument_name=leg_2_name, amount='1', direction=OrderSide.SELL.value) + leg_1 = Leg(instrument_name=leg_1_name, amount=1, direction=OrderSide.BUY.value) + leg_2 = Leg(instrument_name=leg_2_name, amount=1, direction=OrderSide.SELL.value) rfq = Rfq(legs=[leg_1, leg_2], subaccount_id=subaccount_id) - result = derive_client.send_rfq(rfq.to_dict()) + result = derive_client.send_rfq(rfq.model_dump()) assert result['rfq_id'] assert result['status'] == 'open' return result def test_poll_rfqs(derive_client: DeriveClient): - """ - Test the DeriveClient class. - """ - rfq_id = test_derive_client_create_rfq(derive_client).get('rfq_id') + + rfq_id = test_create_rfq(derive_client).get('rfq_id') quotes = derive_client.poll_rfqs() rfqs = quotes.get('rfqs', []) assert rfqs, "RFQs should not be empty" @@ -67,21 +59,31 @@ def test_poll_rfqs(derive_client: DeriveClient): assert filtered_rfqs, f"RFQ with id {rfq_id} not found" -@pytest.mark.skip(reason="Skipping quote creation test") -def test_derive_client_create_quote( +def test_create_quote( derive_client: DeriveClient, ): - """ - Test the DeriveClient class. - """ - rfq = test_derive_client_create_rfq(derive_client) + rfq = test_create_rfq(derive_client) + + price = 42 + direction = "sell" + derive_client.subaccount_id = derive_client.subaccount_ids[1] + + legs = [] + for leg in rfq['legs']: + leg = Leg(**leg) + leg.price = price + legs.append(leg) - # we now create the quote - quote = derive_client.create_quote_object( + quote = derive_client.create_quote( rfq_id=rfq['rfq_id'], - legs=rfq['legs'], - direction='sell', + legs=[leg.model_dump() for leg in legs], + direction=direction, ) - # we now sign it - assert derive_client._sign_quote(quote) + assert quote["status"] == "open" + assert quote["direction"] == direction + assert rfq["creation_timestamp"] < quote["creation_timestamp"] + assert len(rfq["legs"]) == len(quote["legs"]) + for rfq_leg, quote_leg in zip(rfq["legs"], quote["legs"]): + assert rfq_leg != quote_leg + assert Leg(**rfq_leg, price=price) == Leg(**quote_leg) From 4d85b255444cb6dfabd5471cad33b2ef9d7f664c Mon Sep 17 00:00:00 2001 From: zarathustra Date: Fri, 12 Sep 2025 14:41:10 +0200 Subject: [PATCH 4/6] feat: create_quote --- derive_client/clients/base_client.py | 51 ++++++++++++++++++++++------ tests/test_rfq.py | 8 ++--- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/derive_client/clients/base_client.py b/derive_client/clients/base_client.py index bc78d1b3..c6d7d574 100644 --- a/derive_client/clients/base_client.py +++ b/derive_client/clients/base_client.py @@ -16,6 +16,8 @@ MakerTransferPositionModuleData, MakerTransferPositionsModuleData, RecipientTransferERC20ModuleData, + RFQQuoteDetails, + RFQQuoteModuleData, SenderTransferERC20ModuleData, TakerTransferPositionModuleData, TakerTransferPositionsModuleData, @@ -616,7 +618,7 @@ def send_quote(self, quote): url = self.endpoints.private.send_quote return self._send_request(url, quote) - def create_quote_object( + def create_quote( self, rfq_id, legs, @@ -624,18 +626,47 @@ def create_quote_object( ): """Create a quote object.""" _, nonce, expiration = self.get_nonce_and_signature_expiry() - return { - "subaccount_id": self.subaccount_id, + + rfq_legs: list[RFQQuoteDetails] = [] + for leg in legs: + ticker = self.fetch_ticker(instrument_name=leg["instrument_name"]) + rfq_quote_details = RFQQuoteDetails( + instrument_name=ticker["instrument_name"], + direction=leg["direction"], + asset_address=ticker["base_asset_address"], + sub_id=int(ticker["base_asset_sub_id"]), + price=leg["price"], + amount=Decimal(leg["amount"]), + ) + rfq_legs.append(rfq_quote_details) + + action = SignedAction( + subaccount_id=self.subaccount_id, + owner=self.wallet, + signer=self.signer.address, + signature_expiry_sec=MAX_INT_32, + nonce=nonce, + module_address=self.config.contracts.RFQ_MODULE, + module_data=RFQQuoteModuleData( + global_direction=direction, + max_fee=Decimal("123"), + legs=rfq_legs, + ), + DOMAIN_SEPARATOR=self.config.DOMAIN_SEPARATOR, + ACTION_TYPEHASH=self.config.ACTION_TYPEHASH, + ) + + action.sign(self.signer.key) + + payload = { + **action.to_json(), + "label": "", + "mmp": False, "rfq_id": rfq_id, - "legs": legs, - "direction": direction, - "max_fee": "10.0", - "nonce": nonce, - "signer": self.signer.address, - "signature_expiry_sec": expiration, - "signature": "filled_in_below", } + return self.send_quote(quote=payload) + def _send_request(self, url, json=None, params=None, headers=None): headers = self._create_signature_headers() if not headers else headers response = requests.post(url, json=json, headers=headers, params=params) diff --git a/tests/test_rfq.py b/tests/test_rfq.py index b1f32a7d..068b0acc 100644 --- a/tests/test_rfq.py +++ b/tests/test_rfq.py @@ -27,9 +27,7 @@ def model_dump(self, *args, **kwargs): return data -def test_create_rfq( - derive_client: DeriveClient, -): +def test_create_rfq(derive_client: DeriveClient): subaccount_id = derive_client.subaccount_id @@ -59,9 +57,7 @@ def test_poll_rfqs(derive_client: DeriveClient): assert filtered_rfqs, f"RFQ with id {rfq_id} not found" -def test_create_quote( - derive_client: DeriveClient, -): +def test_create_quote(derive_client: DeriveClient): rfq = test_create_rfq(derive_client) From e63ffc1a7062df0c2c825e0894cb2da6129f5acc Mon Sep 17 00:00:00 2001 From: 8ball030 <8baller@station.codes> Date: Wed, 17 Sep 2025 14:44:46 +0100 Subject: [PATCH 5/6] added-endpoints-and-tests --- derive_client/clients/base_client.py | 40 ++++++++++++++++++ derive_client/data_types/enums.py | 3 ++ derive_client/endpoints.py | 3 ++ examples/poll_rfq.py | 56 ------------------------- examples/rfqs/create_rfq.py | 62 ++++++++++++++++++++++++++++ examples/rfqs/poll_rfq.py | 51 +++++++++++++++++++++++ tests/conftest.py | 14 ++++++- tests/test_rfq.py | 21 ++++++++++ 8 files changed, 193 insertions(+), 57 deletions(-) delete mode 100644 examples/poll_rfq.py create mode 100644 examples/rfqs/create_rfq.py create mode 100644 examples/rfqs/poll_rfq.py diff --git a/derive_client/clients/base_client.py b/derive_client/clients/base_client.py index c6d7d574..8ee6c533 100644 --- a/derive_client/clients/base_client.py +++ b/derive_client/clients/base_client.py @@ -667,6 +667,46 @@ def create_quote( return self.send_quote(quote=payload) + def cancel_rfq(self, rfq_id: str): + """Cancel an RFQ.""" + url = self.endpoints.private.cancel_rfq + payload = { + "subaccount_id": self.subaccount_id, + "rfq_id": rfq_id, + } + return self._send_request(url, json=payload) + + def cancel_batch_rfqs(self, rfq_id: str = None, label: str = None, nonce: int = None): + """Cancel RFQs in batch.""" + url = self.endpoints.private.cancel_batch_rfqs + payload = { + "subaccount_id": self.subaccount_id, + } + if rfq_id: + payload["rfq_id"] = rfq_id + if label: + payload["label"] = label + if nonce: + payload["nonce"] = nonce + return self._send_request(url, json=payload) + + def poll_quotes(self, + rfq_id: str = None, + quote_id: str = None, + status: RfqStatus = None): + + url = self.endpoints.private.poll_quotes + payload = { + "subaccount_id": self.subaccount_id, + } + if rfq_id: + payload["rfq_id"] = rfq_id + if quote_id: + payload["quote_id"] = quote_id + if status: + payload["status"] = status.value + return self._send_request(url, json=payload) + def _send_request(self, url, json=None, params=None, headers=None): headers = self._create_signature_headers() if not headers else headers response = requests.post(url, json=json, headers=headers, params=params) diff --git a/derive_client/data_types/enums.py b/derive_client/data_types/enums.py index c4e9b644..69f34f4c 100644 --- a/derive_client/data_types/enums.py +++ b/derive_client/data_types/enums.py @@ -243,6 +243,9 @@ class RfqStatus(Enum): """RFQ statuses.""" OPEN = "open" + FILLED = "filled" + CANCELLED = "cancelled" + EXPIRED = "expired" class EthereumJSONRPCErrorCode(IntEnum): diff --git a/derive_client/endpoints.py b/derive_client/endpoints.py index da02fb98..499404d1 100644 --- a/derive_client/endpoints.py +++ b/derive_client/endpoints.py @@ -46,6 +46,9 @@ def __init__(self, base_url: str): set_mmp_config = Endpoint("private", "set_mmp_config") send_rfq = Endpoint("private", "send_rfq") poll_rfqs = Endpoint("private", "poll_rfqs") + poll_quotes = Endpoint("private", "poll_quotes") + cancel_rfq = Endpoint("private", "cancel_rfq") + cancel_batch_rfqs = Endpoint("private", "cancel_batch_rfqs") send_quote = Endpoint("private", "send_quote") deposit = Endpoint("private", "deposit") withdraw = Endpoint("private", "withdraw") diff --git a/examples/poll_rfq.py b/examples/poll_rfq.py deleted file mode 100644 index c5dc3e36..00000000 --- a/examples/poll_rfq.py +++ /dev/null @@ -1,56 +0,0 @@ -""" -Example of how to poll RFQ (Request for Quote) status and handle transfers between subaccount and funding account. -""" - -import os -from pathlib import Path -from time import sleep - -import click -from dotenv import load_dotenv - -from derive_client import DeriveClient -from derive_client.data_types import Environment - - -@click.command() -@click.option('--signer-key-path', required=True, help='Path to signer key file') -@click.option('--derive-sc-wallet', required=True, help='Derive SC wallet address') -def main(signer_key_path, derive_sc_wallet): - """ - A command-line interface to poll RFQs (Request for Quotes) using the DeriveClient. - - Example usage: - python examples/poll_rfq.py \ - --signer-key-path ./ethereum_private_key.txt \ - --derive-sc-wallet 0xYourDeriveSCWalletAddress - """ - key_file = Path(signer_key_path) - if not key_file.exists(): - click.echo(f"Signer key file not found: {signer_key_path}") - return - - load_dotenv() - subaccount_id = os.environ.get("SUBACCOUNT_ID") - if not subaccount_id: - click.echo("SUBACCOUNT_ID not found in environment variables.") - return - - client = DeriveClient( - private_key=key_file.read_text(), - wallet=derive_sc_wallet, - env=Environment.PROD, - subaccount_id=subaccount_id, - ) - - while True: - print("Polling RFQs...") - quotes = client.poll_rfqs() - sleep(5) # Sleep for a while before polling again - print(f"Found {len(quotes)} RFQs.") - for quote in quotes: - print(f"RFQ ID: {quote}, Status: {quote}") - - -if __name__ == "__main__": - main() diff --git a/examples/rfqs/create_rfq.py b/examples/rfqs/create_rfq.py new file mode 100644 index 00000000..88f80c97 --- /dev/null +++ b/examples/rfqs/create_rfq.py @@ -0,0 +1,62 @@ +""" +Create an rfq using the REST API. +""" + +import json +from datetime import datetime + +from derive_client import DeriveClient +from derive_client.data_types import Environment +from derive_client.data_types.enums import InstrumentType, OrderSide, UnderlyingCurrency +from tests.conftest import TEST_PRIVATE_KEY, TEST_WALLET +from tests.test_rfq import Leg, Rfq + +SLEEP_TIME = 1 + + +def main(): + """ + Sample of polling for RFQs and printing their status. + """ + + client: DeriveClient = DeriveClient( + private_key=TEST_PRIVATE_KEY, + wallet=TEST_WALLET, + env=Environment.TEST, + ) + + # we get an option market + markets = client.fetch_instruments( + instrument_type=InstrumentType.OPTION, + currency=UnderlyingCurrency.ETH, + expired=False, + ) + + sorted_markets = sorted(markets, key=lambda m: (m['option_details']['expiry'])) + + zero_day_markets = list( + filter(lambda m: (m['option_details']['expiry'] - datetime.utcnow().timestamp()) < (3600 * 24), sorted_markets) + ) + print("Zero day markets:") + selected_market = zero_day_markets[0] + print(json.dumps(selected_market, indent=2)) + print(f"Found {len(zero_day_markets)} zero day markets") + expiry_time = selected_market['option_details']['expiry'] + current_time = datetime.utcnow().timestamp() + print( + f"Expiry time: {expiry_time}, current time: {current_time}, time to expiry: {(expiry_time - current_time) / 3600:.2f} hours" + ) + + # we create an rfq for this market + + leg = Leg(instrument_name=selected_market['instrument_name'], amount=1, direction=OrderSide.BUY) + request = Rfq( + legs=[leg], + subaccount_id=client.subaccount_id, + ) + rfq = client.send_rfq(request.model_dump()) + print("RFQ created with id:", rfq['rfq_id']) + + +if __name__ == "__main__": + main() diff --git a/examples/rfqs/poll_rfq.py b/examples/rfqs/poll_rfq.py new file mode 100644 index 00000000..089f5255 --- /dev/null +++ b/examples/rfqs/poll_rfq.py @@ -0,0 +1,51 @@ +""" +Example of how to poll RFQ (Request for Quote) status and handle transfers between subaccount and funding account. +""" + +from time import sleep + +from derive_client import DeriveClient +from derive_client.data_types import Environment +from tests.conftest import TEST_PRIVATE_KEY, TEST_WALLET + +SLEEP_TIME = 1 + + +def main(): + """ + Sample of polling for RFQs and printing their status. + """ + + client = DeriveClient( + private_key=TEST_PRIVATE_KEY, + wallet=TEST_WALLET, + env=Environment.TEST, + ) + + processed_rfqs = set() + + while True: + quotes = client.poll_rfqs() + sleep(SLEEP_TIME) # Sleep for a while before polling again + raw_rfqs = quotes.get('rfqs', []) + rfqs = {rfq['rfq_id']: rfq for rfq in raw_rfqs} + + if not rfqs: + print("No RFQs found, exiting.") + break + for rfq_id in rfqs: + if rfq_id in processed_rfqs: + continue + rfq = rfqs[rfq_id] + print(f"RFQ ID: {rfq_id} Status: {rfq['status']}, Legs: {len(rfq['legs'])}") + processed_rfqs.add(rfq_id) + for rfq in processed_rfqs.copy(): + if rfq not in rfqs: + print(f"RFQ ID {rfq} no longer present in polled RFQs, removing from processed list.") + processed_rfqs.remove(rfq) + + + + +if __name__ == "__main__": + main() diff --git a/tests/conftest.py b/tests/conftest.py index 3c848807..995070d8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,7 @@ Conftest for derive tests """ +import time from unittest.mock import MagicMock import pytest @@ -9,6 +10,7 @@ from derive_client.clients import AsyncClient from derive_client.data_types import Environment from derive_client.derive import DeriveClient +from derive_client.exceptions import DeriveJSONRPCException from derive_client.utils import get_logger TEST_WALLET = "0x8772185a1516f0d61fC1c2524926BfC69F95d698" @@ -31,7 +33,17 @@ def derive_client(): wallet=TEST_WALLET, private_key=TEST_PRIVATE_KEY, env=Environment.TEST, logger=get_logger() ) yield derive_client - derive_client.cancel_all() + while True: + try: + derive_client.cancel_all() + derive_client.cancel_batch_rfqs() + break + except DeriveJSONRPCException as e: + if "Retry after" in e.data: + wait_ms = int(e.data.split(" ")[2]) + time.sleep(wait_ms / 1000) + continue + raise e @pytest.fixture diff --git a/tests/test_rfq.py b/tests/test_rfq.py index 068b0acc..741d365e 100644 --- a/tests/test_rfq.py +++ b/tests/test_rfq.py @@ -47,6 +47,18 @@ def test_create_rfq(derive_client: DeriveClient): return result +def test_cancel_rfq(derive_client: DeriveClient): + rfq = test_create_rfq(derive_client) + result = derive_client.cancel_rfq(rfq_id=rfq['rfq_id']) + assert result == 'ok' + + +def test_cancel_all_rfqs(derive_client: DeriveClient): + result = derive_client.cancel_batch_rfqs() + cancelled_ids = result.get('cancelled_ids', []) + assert isinstance(cancelled_ids, list) + + def test_poll_rfqs(derive_client: DeriveClient): rfq_id = test_create_rfq(derive_client).get('rfq_id') @@ -83,3 +95,12 @@ def test_create_quote(derive_client: DeriveClient): for rfq_leg, quote_leg in zip(rfq["legs"], quote["legs"]): assert rfq_leg != quote_leg assert Leg(**rfq_leg, price=price) == Leg(**quote_leg) + return rfq + +def test_poll_quotes(derive_client: DeriveClient): + + rfq = test_create_quote(derive_client) + derive_client.subaccount_id = derive_client.subaccount_ids[0] + quotes = derive_client.poll_quotes(rfq_id=rfq['rfq_id']) + polled_rfqs = quotes.get('quotes', []) + assert polled_rfqs, "Polled RFQs should not be empty" From 9fbb46666e7e1f0209989080609afc1ce50c632d Mon Sep 17 00:00:00 2001 From: 8ball030 <8baller@station.codes> Date: Thu, 18 Sep 2025 12:31:30 +0100 Subject: [PATCH 6/6] feat:rfq-examples --- derive_client/clients/base_client.py | 6 +----- examples/rfqs/poll_rfq.py | 2 -- tests/test_rfq.py | 5 +---- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/derive_client/clients/base_client.py b/derive_client/clients/base_client.py index 8ee6c533..57c296c8 100644 --- a/derive_client/clients/base_client.py +++ b/derive_client/clients/base_client.py @@ -690,11 +690,7 @@ def cancel_batch_rfqs(self, rfq_id: str = None, label: str = None, nonce: int = payload["nonce"] = nonce return self._send_request(url, json=payload) - def poll_quotes(self, - rfq_id: str = None, - quote_id: str = None, - status: RfqStatus = None): - + def poll_quotes(self, rfq_id: str = None, quote_id: str = None, status: RfqStatus = None): url = self.endpoints.private.poll_quotes payload = { "subaccount_id": self.subaccount_id, diff --git a/examples/rfqs/poll_rfq.py b/examples/rfqs/poll_rfq.py index 089f5255..1672f8ad 100644 --- a/examples/rfqs/poll_rfq.py +++ b/examples/rfqs/poll_rfq.py @@ -45,7 +45,5 @@ def main(): processed_rfqs.remove(rfq) - - if __name__ == "__main__": main() diff --git a/tests/test_rfq.py b/tests/test_rfq.py index 741d365e..1921a8e9 100644 --- a/tests/test_rfq.py +++ b/tests/test_rfq.py @@ -28,7 +28,6 @@ def model_dump(self, *args, **kwargs): def test_create_rfq(derive_client: DeriveClient): - subaccount_id = derive_client.subaccount_id markets = derive_client.fetch_instruments(instrument_type=InstrumentType.OPTION, currency=Currency.ETH) @@ -60,7 +59,6 @@ def test_cancel_all_rfqs(derive_client: DeriveClient): def test_poll_rfqs(derive_client: DeriveClient): - rfq_id = test_create_rfq(derive_client).get('rfq_id') quotes = derive_client.poll_rfqs() rfqs = quotes.get('rfqs', []) @@ -70,7 +68,6 @@ def test_poll_rfqs(derive_client: DeriveClient): def test_create_quote(derive_client: DeriveClient): - rfq = test_create_rfq(derive_client) price = 42 @@ -97,8 +94,8 @@ def test_create_quote(derive_client: DeriveClient): assert Leg(**rfq_leg, price=price) == Leg(**quote_leg) return rfq -def test_poll_quotes(derive_client: DeriveClient): +def test_poll_quotes(derive_client: DeriveClient): rfq = test_create_quote(derive_client) derive_client.subaccount_id = derive_client.subaccount_ids[0] quotes = derive_client.poll_quotes(rfq_id=rfq['rfq_id'])