ddx-python 1.0.4__cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ddx/.gitignore +1 -0
- ddx/__init__.py +58 -0
- ddx/_rust/__init__.pyi +2685 -0
- ddx/_rust/common/__init__.pyi +17 -0
- ddx/_rust/common/accounting.pyi +6 -0
- ddx/_rust/common/enums.pyi +3 -0
- ddx/_rust/common/requests/__init__.pyi +23 -0
- ddx/_rust/common/requests/intents.pyi +19 -0
- ddx/_rust/common/specs.pyi +17 -0
- ddx/_rust/common/state/__init__.pyi +41 -0
- ddx/_rust/common/state/keys.pyi +29 -0
- ddx/_rust/common/transactions.pyi +7 -0
- ddx/_rust/decimal.pyi +3 -0
- ddx/_rust/h256.pyi +3 -0
- ddx/_rust.abi3.so +0 -0
- ddx/app_config/ethereum/addresses.json +526 -0
- ddx/auditor/README.md +32 -0
- ddx/auditor/__init__.py +0 -0
- ddx/auditor/auditor_driver.py +1043 -0
- ddx/auditor/websocket_message.py +54 -0
- ddx/common/__init__.py +0 -0
- ddx/common/epoch_params.py +28 -0
- ddx/common/fill_context.py +141 -0
- ddx/common/logging.py +184 -0
- ddx/common/market_aware_account.py +259 -0
- ddx/common/market_specs.py +64 -0
- ddx/common/trade_mining_params.py +19 -0
- ddx/common/transaction_utils.py +85 -0
- ddx/common/transactions/__init__.py +0 -0
- ddx/common/transactions/advance_epoch.py +91 -0
- ddx/common/transactions/advance_settlement_epoch.py +63 -0
- ddx/common/transactions/all_price_checkpoints.py +84 -0
- ddx/common/transactions/cancel.py +76 -0
- ddx/common/transactions/cancel_all.py +88 -0
- ddx/common/transactions/complete_fill.py +103 -0
- ddx/common/transactions/disaster_recovery.py +96 -0
- ddx/common/transactions/event.py +48 -0
- ddx/common/transactions/fee_distribution.py +119 -0
- ddx/common/transactions/funding.py +292 -0
- ddx/common/transactions/futures_expiry.py +123 -0
- ddx/common/transactions/genesis.py +108 -0
- ddx/common/transactions/inner/__init__.py +0 -0
- ddx/common/transactions/inner/adl_outcome.py +25 -0
- ddx/common/transactions/inner/fill.py +232 -0
- ddx/common/transactions/inner/liquidated_position.py +41 -0
- ddx/common/transactions/inner/liquidation_entry.py +41 -0
- ddx/common/transactions/inner/liquidation_fill.py +118 -0
- ddx/common/transactions/inner/outcome.py +32 -0
- ddx/common/transactions/inner/trade_fill.py +292 -0
- ddx/common/transactions/insurance_fund_update.py +138 -0
- ddx/common/transactions/insurance_fund_withdraw.py +100 -0
- ddx/common/transactions/liquidation.py +353 -0
- ddx/common/transactions/partial_fill.py +125 -0
- ddx/common/transactions/pnl_realization.py +120 -0
- ddx/common/transactions/post.py +72 -0
- ddx/common/transactions/post_order.py +95 -0
- ddx/common/transactions/price_checkpoint.py +97 -0
- ddx/common/transactions/signer_registered.py +62 -0
- ddx/common/transactions/specs_update.py +61 -0
- ddx/common/transactions/strategy_update.py +158 -0
- ddx/common/transactions/tradable_product_update.py +98 -0
- ddx/common/transactions/trade_mining.py +147 -0
- ddx/common/transactions/trader_update.py +131 -0
- ddx/common/transactions/withdraw.py +90 -0
- ddx/common/transactions/withdraw_ddx.py +74 -0
- ddx/common/utils.py +176 -0
- ddx/config.py +17 -0
- ddx/derivadex_client.py +270 -0
- ddx/models/__init__.py +0 -0
- ddx/models/base.py +132 -0
- ddx/py.typed +0 -0
- ddx/realtime_client/__init__.py +2 -0
- ddx/realtime_client/config.py +2 -0
- ddx/realtime_client/models/__init__.py +611 -0
- ddx/realtime_client/realtime_client.py +646 -0
- ddx/rest_client/__init__.py +0 -0
- ddx/rest_client/clients/__init__.py +0 -0
- ddx/rest_client/clients/base_client.py +60 -0
- ddx/rest_client/clients/market_client.py +1243 -0
- ddx/rest_client/clients/on_chain_client.py +439 -0
- ddx/rest_client/clients/signed_client.py +292 -0
- ddx/rest_client/clients/system_client.py +843 -0
- ddx/rest_client/clients/trade_client.py +357 -0
- ddx/rest_client/constants/__init__.py +0 -0
- ddx/rest_client/constants/endpoints.py +66 -0
- ddx/rest_client/contracts/__init__.py +0 -0
- ddx/rest_client/contracts/checkpoint/__init__.py +560 -0
- ddx/rest_client/contracts/ddx/__init__.py +1949 -0
- ddx/rest_client/contracts/dummy_token/__init__.py +1014 -0
- ddx/rest_client/contracts/i_collateral/__init__.py +1414 -0
- ddx/rest_client/contracts/i_stake/__init__.py +696 -0
- ddx/rest_client/exceptions/__init__.py +0 -0
- ddx/rest_client/exceptions/exceptions.py +32 -0
- ddx/rest_client/http/__init__.py +0 -0
- ddx/rest_client/http/http_client.py +336 -0
- ddx/rest_client/models/__init__.py +0 -0
- ddx/rest_client/models/market.py +693 -0
- ddx/rest_client/models/signed.py +61 -0
- ddx/rest_client/models/system.py +311 -0
- ddx/rest_client/models/trade.py +185 -0
- ddx/rest_client/utils/__init__.py +0 -0
- ddx/rest_client/utils/encryption_utils.py +26 -0
- ddx/utils/__init__.py +0 -0
- ddx_python-1.0.4.dist-info/METADATA +63 -0
- ddx_python-1.0.4.dist-info/RECORD +106 -0
- ddx_python-1.0.4.dist-info/WHEEL +5 -0
|
File without changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from typing import Optional, Dict, Any
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class HTTPClientError(Exception):
|
|
5
|
+
"""Base exception for HTTP client errors."""
|
|
6
|
+
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
request: str,
|
|
10
|
+
message: str,
|
|
11
|
+
status_code: int,
|
|
12
|
+
time: str,
|
|
13
|
+
response: Optional[Dict[str, Any]] = None,
|
|
14
|
+
):
|
|
15
|
+
self.request = request
|
|
16
|
+
self.message = message
|
|
17
|
+
self.status_code = status_code
|
|
18
|
+
self.time = time
|
|
19
|
+
self.response = response
|
|
20
|
+
super().__init__(f"{message} (Status: {status_code}, Time: {time})")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class InvalidRequestError(HTTPClientError):
|
|
24
|
+
"""Exception raised for API-specific errors."""
|
|
25
|
+
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class FailedRequestError(HTTPClientError):
|
|
30
|
+
"""Exception raised for HTTP request failures."""
|
|
31
|
+
|
|
32
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Optional, Dict, Any
|
|
5
|
+
import logging
|
|
6
|
+
from httpx import AsyncClient, Limits, RequestError
|
|
7
|
+
|
|
8
|
+
from ddx.rest_client.exceptions.exceptions import (
|
|
9
|
+
InvalidRequestError,
|
|
10
|
+
FailedRequestError,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class HTTPClient:
|
|
15
|
+
"""
|
|
16
|
+
HTTP client for DerivaDEX API.
|
|
17
|
+
|
|
18
|
+
Handles request preparation and response processing.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
timeout: int = 10,
|
|
24
|
+
max_retries: int = 3,
|
|
25
|
+
retry_delay: int = 1,
|
|
26
|
+
max_connections: int = 100,
|
|
27
|
+
max_keepalive: int = 20,
|
|
28
|
+
keepalive_expiry: int = 5,
|
|
29
|
+
logger: Optional[logging.Logger] = None,
|
|
30
|
+
):
|
|
31
|
+
"""
|
|
32
|
+
Initialize the HTTP client.
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
timeout : int
|
|
37
|
+
Request timeout in seconds
|
|
38
|
+
max_retries : int
|
|
39
|
+
Maximum number of retry attempts
|
|
40
|
+
retry_delay : int
|
|
41
|
+
Delay between retries in seconds
|
|
42
|
+
max_connections : int, default=100
|
|
43
|
+
Max concurrent connections allowed
|
|
44
|
+
max_keepalive : int, optional
|
|
45
|
+
Max allowable keep-alive connections, None means always allow
|
|
46
|
+
keepalive_expiry: int, default=5
|
|
47
|
+
Time in seconds to keep HTTP connection alive. None means connection never expires
|
|
48
|
+
logger : Optional[logging.Logger]
|
|
49
|
+
Logger instance
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
self.timeout = timeout
|
|
53
|
+
self.max_retries = max_retries
|
|
54
|
+
self.retry_delay = retry_delay
|
|
55
|
+
self.max_connections = max_connections
|
|
56
|
+
self.max_keepalive = max_keepalive
|
|
57
|
+
self.keepalive_expiry = keepalive_expiry
|
|
58
|
+
|
|
59
|
+
# Setup logging
|
|
60
|
+
self.logger = logger or logging.getLogger(__name__)
|
|
61
|
+
|
|
62
|
+
# Initialize client to None - will be created in __aenter__
|
|
63
|
+
self.client: Optional[AsyncClient] = None
|
|
64
|
+
|
|
65
|
+
# Define retry status codes
|
|
66
|
+
self.retry_codes = {408, 429, 500, 502, 503, 504}
|
|
67
|
+
|
|
68
|
+
async def __aenter__(self):
|
|
69
|
+
"""
|
|
70
|
+
Context manager entry point. Initializes the HTTP client.
|
|
71
|
+
|
|
72
|
+
Returns
|
|
73
|
+
-------
|
|
74
|
+
BaseHTTPClient
|
|
75
|
+
The client instance
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
self.client = AsyncClient(
|
|
79
|
+
timeout=self.timeout,
|
|
80
|
+
follow_redirects=True,
|
|
81
|
+
headers={"Content-Type": "application/json"},
|
|
82
|
+
limits=Limits(
|
|
83
|
+
max_keepalive_connections=self.max_keepalive,
|
|
84
|
+
max_connections=self.max_connections,
|
|
85
|
+
keepalive_expiry=self.keepalive_expiry
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
self.logger.debug("HTTP client initialized")
|
|
89
|
+
|
|
90
|
+
return self
|
|
91
|
+
|
|
92
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
93
|
+
"""
|
|
94
|
+
Context manager exit point. Ensures proper cleanup of resources.
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
exc_type : Type[Exception], optional
|
|
99
|
+
The exception type if an exception was raised
|
|
100
|
+
exc_val : Exception, optional
|
|
101
|
+
The exception value if an exception was raised
|
|
102
|
+
exc_tb : TracebackType, optional
|
|
103
|
+
The traceback if an exception was raised
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
if self.client:
|
|
107
|
+
await self.client.aclose()
|
|
108
|
+
self.client = None
|
|
109
|
+
self.logger.debug("HTTP client closed")
|
|
110
|
+
|
|
111
|
+
async def get(
|
|
112
|
+
self,
|
|
113
|
+
url: str,
|
|
114
|
+
params: Optional[Dict[str, Any]] = None,
|
|
115
|
+
headers: Optional[Dict[str, str]] = None,
|
|
116
|
+
) -> Dict[str, Any]:
|
|
117
|
+
"""
|
|
118
|
+
Send a GET request.
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
url : str
|
|
123
|
+
The URL to send the request to
|
|
124
|
+
params : Optional[Dict[str, Any]]
|
|
125
|
+
Query parameters
|
|
126
|
+
headers : Optional[Dict[str, str]]
|
|
127
|
+
Additional headers
|
|
128
|
+
|
|
129
|
+
Returns
|
|
130
|
+
-------
|
|
131
|
+
Dict[str, Any]
|
|
132
|
+
The response data
|
|
133
|
+
|
|
134
|
+
Raises
|
|
135
|
+
------
|
|
136
|
+
FailedRequestError
|
|
137
|
+
If the request fails after retries
|
|
138
|
+
InvalidRequestError
|
|
139
|
+
If the API returns an error response
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
return await self._request("GET", url, params=params, headers=headers)
|
|
143
|
+
|
|
144
|
+
async def post(
|
|
145
|
+
self,
|
|
146
|
+
url: str,
|
|
147
|
+
data: Any = None,
|
|
148
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
149
|
+
headers: Optional[Dict[str, str]] = None,
|
|
150
|
+
) -> Dict[str, Any]:
|
|
151
|
+
"""
|
|
152
|
+
Send a POST request.
|
|
153
|
+
|
|
154
|
+
Parameters
|
|
155
|
+
----------
|
|
156
|
+
url : str
|
|
157
|
+
The URL to send the request to
|
|
158
|
+
data : Any
|
|
159
|
+
Raw request body
|
|
160
|
+
json_data : Optional[Dict[str, Any]]
|
|
161
|
+
JSON data to serialize
|
|
162
|
+
headers : Optional[Dict[str, str]]
|
|
163
|
+
Additional headers
|
|
164
|
+
|
|
165
|
+
Returns
|
|
166
|
+
-------
|
|
167
|
+
Dict[str, Any]
|
|
168
|
+
The response data
|
|
169
|
+
|
|
170
|
+
Raises
|
|
171
|
+
------
|
|
172
|
+
FailedRequestError
|
|
173
|
+
If the request fails after retries
|
|
174
|
+
InvalidRequestError
|
|
175
|
+
If the API returns an error response
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
return await self._request(
|
|
179
|
+
"POST", url, data=data, json_data=json_data, headers=headers
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
def _parse_json_or_failed(self, response, request_info) -> Dict[str, Any]:
|
|
183
|
+
try:
|
|
184
|
+
return response.json()
|
|
185
|
+
except json.JSONDecodeError:
|
|
186
|
+
raise FailedRequestError(
|
|
187
|
+
request=request_info,
|
|
188
|
+
message="Failed to parse JSON response",
|
|
189
|
+
status_code=response.status_code,
|
|
190
|
+
time=datetime.now().strftime("%H:%M:%S"),
|
|
191
|
+
response={"text": response.text},
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def _raise_from_response(self, response, request_info) -> None:
|
|
195
|
+
try:
|
|
196
|
+
body = response.json()
|
|
197
|
+
except json.JSONDecodeError:
|
|
198
|
+
raise FailedRequestError(
|
|
199
|
+
request=request_info,
|
|
200
|
+
message=f"Request failed with status {response.status_code}",
|
|
201
|
+
status_code=response.status_code,
|
|
202
|
+
time=datetime.now().strftime("%H:%M:%S"),
|
|
203
|
+
response={"text": response.text},
|
|
204
|
+
)
|
|
205
|
+
if isinstance(body, dict):
|
|
206
|
+
reason = body.get("error_reason")
|
|
207
|
+
safety = body.get("safety_failure")
|
|
208
|
+
message = body.get("message") or f"Request failed with status {response.status_code}"
|
|
209
|
+
details = f"{reason}: {message}" if reason else message
|
|
210
|
+
if safety:
|
|
211
|
+
details = f"{details} (safety_failure={safety})"
|
|
212
|
+
exc_cls = FailedRequestError if response.status_code >= 500 else InvalidRequestError
|
|
213
|
+
raise exc_cls(
|
|
214
|
+
request=request_info,
|
|
215
|
+
message=details,
|
|
216
|
+
status_code=response.status_code,
|
|
217
|
+
time=datetime.now().strftime("%H:%M:%S"),
|
|
218
|
+
response=body,
|
|
219
|
+
)
|
|
220
|
+
exc_cls = FailedRequestError if response.status_code >= 500 else InvalidRequestError
|
|
221
|
+
raise exc_cls(
|
|
222
|
+
request=request_info,
|
|
223
|
+
message=f"Request failed with status {response.status_code}",
|
|
224
|
+
status_code=response.status_code,
|
|
225
|
+
time=datetime.now().strftime("%H:%M:%S"),
|
|
226
|
+
response={"text": response.text},
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
async def _request(
|
|
230
|
+
self,
|
|
231
|
+
method: str,
|
|
232
|
+
url: str,
|
|
233
|
+
params: Optional[Dict[str, Any]] = None,
|
|
234
|
+
data: Any = None,
|
|
235
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
236
|
+
headers: Optional[Dict[str, str]] = None,
|
|
237
|
+
) -> Dict[str, Any]:
|
|
238
|
+
"""
|
|
239
|
+
Execute HTTP request with retries and error handling.
|
|
240
|
+
|
|
241
|
+
Parameters
|
|
242
|
+
----------
|
|
243
|
+
method : str
|
|
244
|
+
HTTP method (GET, POST, etc.)
|
|
245
|
+
url : str
|
|
246
|
+
The URL to send the request to
|
|
247
|
+
params : Optional[Dict[str, Any]]
|
|
248
|
+
Query parameters
|
|
249
|
+
data : Any
|
|
250
|
+
Raw request body
|
|
251
|
+
json_data : Optional[Dict[str, Any]]
|
|
252
|
+
JSON data to serialize
|
|
253
|
+
headers : Optional[Dict[str, str]]
|
|
254
|
+
Additional headers
|
|
255
|
+
|
|
256
|
+
Returns
|
|
257
|
+
-------
|
|
258
|
+
Dict[str, Any]
|
|
259
|
+
The response data
|
|
260
|
+
|
|
261
|
+
Raises
|
|
262
|
+
------
|
|
263
|
+
FailedRequestError
|
|
264
|
+
If the request fails after retries
|
|
265
|
+
InvalidRequestError
|
|
266
|
+
If the API returns an error response
|
|
267
|
+
RuntimeError
|
|
268
|
+
If the client is not initialized
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
if self.client is None:
|
|
272
|
+
raise RuntimeError(
|
|
273
|
+
"HTTP client not initialized. Use 'async with' context manager."
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
request_info = (
|
|
277
|
+
f"{method} {url}; params: {params}; json: {json_data}; data: {data}"
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
self.logger.debug(f"Request: {request_info}")
|
|
281
|
+
|
|
282
|
+
retries = 0
|
|
283
|
+
while retries <= self.max_retries:
|
|
284
|
+
try:
|
|
285
|
+
response = await self.client.request(
|
|
286
|
+
method=method,
|
|
287
|
+
url=url,
|
|
288
|
+
params=params,
|
|
289
|
+
content=data,
|
|
290
|
+
json=json_data,
|
|
291
|
+
headers=headers,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# Log response status
|
|
295
|
+
self.logger.debug(f"Response status: {response.status_code}")
|
|
296
|
+
|
|
297
|
+
# Check if the request was successful
|
|
298
|
+
if response.status_code == 200:
|
|
299
|
+
return self._parse_json_or_failed(response, request_info)
|
|
300
|
+
|
|
301
|
+
# Handle retry-able status codes
|
|
302
|
+
if (
|
|
303
|
+
response.status_code in self.retry_codes
|
|
304
|
+
and retries < self.max_retries
|
|
305
|
+
):
|
|
306
|
+
wait_time = self.retry_delay * (2**retries)
|
|
307
|
+
self.logger.warning(
|
|
308
|
+
f"Request failed with status {response.status_code}. "
|
|
309
|
+
f"Retrying in {wait_time}s ({retries+1}/{self.max_retries})"
|
|
310
|
+
)
|
|
311
|
+
await asyncio.sleep(wait_time)
|
|
312
|
+
retries += 1
|
|
313
|
+
continue
|
|
314
|
+
|
|
315
|
+
# Non-retryable error or max retries reached
|
|
316
|
+
self._raise_from_response(response, request_info)
|
|
317
|
+
|
|
318
|
+
except RequestError as e:
|
|
319
|
+
# Network-related errors
|
|
320
|
+
if retries < self.max_retries:
|
|
321
|
+
wait_time = self.retry_delay * (2**retries)
|
|
322
|
+
self.logger.warning(
|
|
323
|
+
f"Request error: {str(e)}. "
|
|
324
|
+
f"Retrying in {wait_time}s ({retries+1}/{self.max_retries})"
|
|
325
|
+
)
|
|
326
|
+
await asyncio.sleep(wait_time)
|
|
327
|
+
retries += 1
|
|
328
|
+
continue
|
|
329
|
+
|
|
330
|
+
raise FailedRequestError(
|
|
331
|
+
request=request_info,
|
|
332
|
+
message=f"Request error: {str(e)}",
|
|
333
|
+
status_code=0,
|
|
334
|
+
time=datetime.now().strftime("%H:%M:%S"),
|
|
335
|
+
response=None,
|
|
336
|
+
)
|
|
File without changes
|