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.
Files changed (106) hide show
  1. ddx/.gitignore +1 -0
  2. ddx/__init__.py +58 -0
  3. ddx/_rust/__init__.pyi +2685 -0
  4. ddx/_rust/common/__init__.pyi +17 -0
  5. ddx/_rust/common/accounting.pyi +6 -0
  6. ddx/_rust/common/enums.pyi +3 -0
  7. ddx/_rust/common/requests/__init__.pyi +23 -0
  8. ddx/_rust/common/requests/intents.pyi +19 -0
  9. ddx/_rust/common/specs.pyi +17 -0
  10. ddx/_rust/common/state/__init__.pyi +41 -0
  11. ddx/_rust/common/state/keys.pyi +29 -0
  12. ddx/_rust/common/transactions.pyi +7 -0
  13. ddx/_rust/decimal.pyi +3 -0
  14. ddx/_rust/h256.pyi +3 -0
  15. ddx/_rust.abi3.so +0 -0
  16. ddx/app_config/ethereum/addresses.json +526 -0
  17. ddx/auditor/README.md +32 -0
  18. ddx/auditor/__init__.py +0 -0
  19. ddx/auditor/auditor_driver.py +1043 -0
  20. ddx/auditor/websocket_message.py +54 -0
  21. ddx/common/__init__.py +0 -0
  22. ddx/common/epoch_params.py +28 -0
  23. ddx/common/fill_context.py +141 -0
  24. ddx/common/logging.py +184 -0
  25. ddx/common/market_aware_account.py +259 -0
  26. ddx/common/market_specs.py +64 -0
  27. ddx/common/trade_mining_params.py +19 -0
  28. ddx/common/transaction_utils.py +85 -0
  29. ddx/common/transactions/__init__.py +0 -0
  30. ddx/common/transactions/advance_epoch.py +91 -0
  31. ddx/common/transactions/advance_settlement_epoch.py +63 -0
  32. ddx/common/transactions/all_price_checkpoints.py +84 -0
  33. ddx/common/transactions/cancel.py +76 -0
  34. ddx/common/transactions/cancel_all.py +88 -0
  35. ddx/common/transactions/complete_fill.py +103 -0
  36. ddx/common/transactions/disaster_recovery.py +96 -0
  37. ddx/common/transactions/event.py +48 -0
  38. ddx/common/transactions/fee_distribution.py +119 -0
  39. ddx/common/transactions/funding.py +292 -0
  40. ddx/common/transactions/futures_expiry.py +123 -0
  41. ddx/common/transactions/genesis.py +108 -0
  42. ddx/common/transactions/inner/__init__.py +0 -0
  43. ddx/common/transactions/inner/adl_outcome.py +25 -0
  44. ddx/common/transactions/inner/fill.py +232 -0
  45. ddx/common/transactions/inner/liquidated_position.py +41 -0
  46. ddx/common/transactions/inner/liquidation_entry.py +41 -0
  47. ddx/common/transactions/inner/liquidation_fill.py +118 -0
  48. ddx/common/transactions/inner/outcome.py +32 -0
  49. ddx/common/transactions/inner/trade_fill.py +292 -0
  50. ddx/common/transactions/insurance_fund_update.py +138 -0
  51. ddx/common/transactions/insurance_fund_withdraw.py +100 -0
  52. ddx/common/transactions/liquidation.py +353 -0
  53. ddx/common/transactions/partial_fill.py +125 -0
  54. ddx/common/transactions/pnl_realization.py +120 -0
  55. ddx/common/transactions/post.py +72 -0
  56. ddx/common/transactions/post_order.py +95 -0
  57. ddx/common/transactions/price_checkpoint.py +97 -0
  58. ddx/common/transactions/signer_registered.py +62 -0
  59. ddx/common/transactions/specs_update.py +61 -0
  60. ddx/common/transactions/strategy_update.py +158 -0
  61. ddx/common/transactions/tradable_product_update.py +98 -0
  62. ddx/common/transactions/trade_mining.py +147 -0
  63. ddx/common/transactions/trader_update.py +131 -0
  64. ddx/common/transactions/withdraw.py +90 -0
  65. ddx/common/transactions/withdraw_ddx.py +74 -0
  66. ddx/common/utils.py +176 -0
  67. ddx/config.py +17 -0
  68. ddx/derivadex_client.py +270 -0
  69. ddx/models/__init__.py +0 -0
  70. ddx/models/base.py +132 -0
  71. ddx/py.typed +0 -0
  72. ddx/realtime_client/__init__.py +2 -0
  73. ddx/realtime_client/config.py +2 -0
  74. ddx/realtime_client/models/__init__.py +611 -0
  75. ddx/realtime_client/realtime_client.py +646 -0
  76. ddx/rest_client/__init__.py +0 -0
  77. ddx/rest_client/clients/__init__.py +0 -0
  78. ddx/rest_client/clients/base_client.py +60 -0
  79. ddx/rest_client/clients/market_client.py +1243 -0
  80. ddx/rest_client/clients/on_chain_client.py +439 -0
  81. ddx/rest_client/clients/signed_client.py +292 -0
  82. ddx/rest_client/clients/system_client.py +843 -0
  83. ddx/rest_client/clients/trade_client.py +357 -0
  84. ddx/rest_client/constants/__init__.py +0 -0
  85. ddx/rest_client/constants/endpoints.py +66 -0
  86. ddx/rest_client/contracts/__init__.py +0 -0
  87. ddx/rest_client/contracts/checkpoint/__init__.py +560 -0
  88. ddx/rest_client/contracts/ddx/__init__.py +1949 -0
  89. ddx/rest_client/contracts/dummy_token/__init__.py +1014 -0
  90. ddx/rest_client/contracts/i_collateral/__init__.py +1414 -0
  91. ddx/rest_client/contracts/i_stake/__init__.py +696 -0
  92. ddx/rest_client/exceptions/__init__.py +0 -0
  93. ddx/rest_client/exceptions/exceptions.py +32 -0
  94. ddx/rest_client/http/__init__.py +0 -0
  95. ddx/rest_client/http/http_client.py +336 -0
  96. ddx/rest_client/models/__init__.py +0 -0
  97. ddx/rest_client/models/market.py +693 -0
  98. ddx/rest_client/models/signed.py +61 -0
  99. ddx/rest_client/models/system.py +311 -0
  100. ddx/rest_client/models/trade.py +185 -0
  101. ddx/rest_client/utils/__init__.py +0 -0
  102. ddx/rest_client/utils/encryption_utils.py +26 -0
  103. ddx/utils/__init__.py +0 -0
  104. ddx_python-1.0.4.dist-info/METADATA +63 -0
  105. ddx_python-1.0.4.dist-info/RECORD +106 -0
  106. ddx_python-1.0.4.dist-info/WHEEL +5 -0
@@ -0,0 +1,439 @@
1
+ import asyncio
2
+ import logging
3
+ from typing import Optional
4
+ from eth_account.signers.local import LocalAccount
5
+ from web3 import Web3
6
+ from web3.types import TxReceipt
7
+ from zero_ex.contract_wrappers import TxParams
8
+ from eth_abi.utils.padding import zpad32_right
9
+
10
+ from ddx._rust.decimal import Decimal
11
+ from ddx._rust.common.state.keys import StrategyKey
12
+ from ddx.common.utils import to_base_unit_amount_list, to_base_unit_amount
13
+ from ddx.rest_client.clients.base_client import BaseClient
14
+ from ddx.rest_client.contracts.i_collateral import ICollateral
15
+ from ddx.rest_client.contracts.ddx import DDX
16
+ from ddx.rest_client.contracts.dummy_token import DummyToken
17
+ from ddx.rest_client.contracts.checkpoint import Checkpoint
18
+ from ddx.rest_client.contracts.i_stake import IStake
19
+ from ddx.rest_client.constants.endpoints import OnChain
20
+ from ddx.rest_client.http.http_client import HTTPClient
21
+
22
+ COLLATERAL_DECIMALS = 6
23
+ DDX_DECIMALS = 18
24
+ DEFAULT_GAS_LIMIT = 500_000
25
+
26
+
27
+ class OnChainClient(BaseClient):
28
+ """
29
+ On-chain operations for depositing, withdrawing, and other activities.
30
+
31
+ Parameters
32
+ ----------
33
+ http : HTTPClient
34
+ The HTTP client instance to use for requests
35
+ base_url : str
36
+ The base URL for trading endpoints
37
+ web3_account : Any
38
+ The web3 account for signing requests
39
+ verifying_contract : str
40
+ The contract address
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ http: HTTPClient,
46
+ base_url: str,
47
+ web3_account: LocalAccount,
48
+ web3: Web3,
49
+ verifying_contract: str,
50
+ ):
51
+ super().__init__(http, base_url)
52
+ self._web3_account = web3_account
53
+ self._web3 = web3
54
+ self._verifying_contract = verifying_contract
55
+ self._logger = logging.getLogger(__name__)
56
+
57
+ def _send_transaction(
58
+ self,
59
+ contract_method,
60
+ method_params: list,
61
+ nonce: Optional[int] = None,
62
+ gas: int = 500_000,
63
+ local_account: Optional[LocalAccount] = None,
64
+ ) -> TxReceipt:
65
+ """
66
+ Helper to build, sign and send a transaction.
67
+
68
+ Parameters
69
+ ----------
70
+ contract_method : ContractMethod
71
+ The contract method object
72
+ method_params : list
73
+ Parameters to pass to the contract method
74
+ nonce : Optional[int]
75
+ Custom nonce or None for auto
76
+ gas : int
77
+ Gas limit for transaction
78
+ local_account : Optional[LocalAccount]
79
+ Local account to sign with, defaults to client's account
80
+
81
+ Returns
82
+ -------
83
+ TxReceipt
84
+ Transaction receipt
85
+ """
86
+
87
+ account = local_account or self._web3_account
88
+
89
+ if nonce is None:
90
+ nonce = self._web3.eth.get_transaction_count(account.address)
91
+
92
+ tx = contract_method.build_transaction(
93
+ *method_params,
94
+ tx_params=TxParams(from_=account.address, nonce=nonce, gas=gas),
95
+ )
96
+ signed_tx = self._web3.eth.account.sign_transaction(tx, private_key=account.key)
97
+ tx_hash = self._web3.eth.send_raw_transaction(signed_tx.rawTransaction)
98
+ receipt = self._web3.eth.wait_for_transaction_receipt(tx_hash, poll_latency=0.5)
99
+
100
+ return receipt
101
+
102
+ def approve(
103
+ self,
104
+ collateral_address: str,
105
+ amount: Decimal,
106
+ nonce: Optional[int] = None,
107
+ local_account: Optional[LocalAccount] = None,
108
+ ) -> TxReceipt:
109
+ """
110
+ Approve ERC-20 collateral for transfer to the DerivaDEX contract.
111
+
112
+ Parameters
113
+ ----------
114
+ collateral_address : str
115
+ The token contract address to approve
116
+ amount : Decimal
117
+ Amount to approve for transfer
118
+ nonce : Optional[int]
119
+ Custom nonce for the transaction, if None uses next available
120
+ local_account : Optional[LocalAccount]
121
+ Local account to sign with, defaults to client's account
122
+
123
+ Returns
124
+ -------
125
+ TxReceipt
126
+ Transaction receipt
127
+ """
128
+
129
+ if not self._verifying_contract:
130
+ raise TypeError("Verifying contract address is not set")
131
+ dummy_token = DummyToken(self._web3, collateral_address)
132
+ approve_amount = to_base_unit_amount(amount, COLLATERAL_DECIMALS)
133
+
134
+ return self._send_transaction(
135
+ dummy_token.approve,
136
+ method_params=[self._verifying_contract, approve_amount],
137
+ nonce=nonce,
138
+ local_account=local_account,
139
+ )
140
+
141
+ async def deposit(
142
+ self,
143
+ collateral_address: str,
144
+ strategy_id: str,
145
+ amount: Decimal,
146
+ nonce: Optional[int] = None,
147
+ local_account: Optional[LocalAccount] = None,
148
+ ) -> TxReceipt:
149
+ """
150
+ Deposit ERC-20 collateral into a strategy.
151
+
152
+ Parameters
153
+ ----------
154
+ collateral_address : str
155
+ The token contract address to deposit
156
+ strategy_id : str
157
+ Strategy identifier to deposit into
158
+ amount : Decimal
159
+ Amount to deposit
160
+ nonce : Optional[int]
161
+ Custom nonce for the transaction, if None uses next available
162
+ local_account : Optional[LocalAccount]
163
+ Local account to sign with, defaults to client's account
164
+
165
+ Returns
166
+ -------
167
+ TxReceipt
168
+ Transaction receipt
169
+ """
170
+
171
+ account = local_account or self._web3_account
172
+ kyc_auth = await self._http.get(
173
+ self._build_url(OnChain.KYC_AUTH), params={"trader": account.address}
174
+ )
175
+
176
+ encoded_strategy = zpad32_right(
177
+ len(strategy_id).to_bytes(1, byteorder="little")
178
+ + strategy_id.encode("utf8")
179
+ )
180
+ deposit_amount = to_base_unit_amount(amount, COLLATERAL_DECIMALS)
181
+ i_collateral_contract = ICollateral(self._web3, self._verifying_contract)
182
+
183
+ return self._send_transaction(
184
+ i_collateral_contract.deposit,
185
+ method_params=[
186
+ collateral_address,
187
+ encoded_strategy,
188
+ deposit_amount,
189
+ kyc_auth["kycAuth"]["expiryBlock"],
190
+ kyc_auth["signature"],
191
+ ],
192
+ nonce=nonce,
193
+ gas=DEFAULT_GAS_LIMIT,
194
+ local_account=local_account,
195
+ )
196
+
197
+ def approve_ddx(
198
+ self,
199
+ ddx_address: str,
200
+ amount: Decimal,
201
+ nonce: Optional[int] = None,
202
+ local_account: Optional[LocalAccount] = None,
203
+ ) -> TxReceipt:
204
+ """
205
+ Approve DDX for transfer to the DerivaDEX contract.
206
+
207
+ Parameters
208
+ ----------
209
+ ddx_address : str
210
+ The DDX token contract address
211
+ amount : Decimal
212
+ Amount to approve for transfer
213
+ nonce : Optional[int]
214
+ Custom nonce for the transaction, if None uses next available
215
+ local_account : Optional[LocalAccount]
216
+ Local account to sign with, defaults to client's account
217
+
218
+ Returns
219
+ -------
220
+ TxReceipt
221
+ Transaction receipt
222
+ """
223
+
224
+ if not self._verifying_contract:
225
+ raise TypeError("Verifying contract address is not set")
226
+ ddx_contract = DDX(self._web3, ddx_address)
227
+ approve_amount = to_base_unit_amount(amount, DDX_DECIMALS)
228
+
229
+ return self._send_transaction(
230
+ ddx_contract.approve,
231
+ method_params=[self._verifying_contract, approve_amount],
232
+ nonce=nonce,
233
+ local_account=local_account,
234
+ )
235
+
236
+ async def deposit_ddx(
237
+ self,
238
+ amount: Decimal,
239
+ nonce: Optional[int] = None,
240
+ local_account: Optional[LocalAccount] = None,
241
+ ) -> TxReceipt:
242
+ """
243
+ Deposit DDX to exchange.
244
+
245
+ Parameters
246
+ ----------
247
+ amount : Decimal
248
+ Amount of DDX to deposit
249
+ nonce : Optional[int]
250
+ Custom nonce for the transaction, if None uses next available
251
+ local_account : Optional[LocalAccount]
252
+ Local account to sign with, defaults to client's account
253
+
254
+ Returns
255
+ -------
256
+ TxReceipt
257
+ Transaction receipt
258
+ """
259
+
260
+ account = local_account or self._web3_account
261
+ kyc_auth = await self._http.get(
262
+ self._build_url(OnChain.KYC_AUTH), params={"trader": account.address}
263
+ )
264
+
265
+ deposit_amount = to_base_unit_amount(amount, DDX_DECIMALS)
266
+ i_stake_contract = IStake(self._web3, self._verifying_contract)
267
+
268
+ return self._send_transaction(
269
+ i_stake_contract.deposit_ddx,
270
+ method_params=[
271
+ deposit_amount,
272
+ kyc_auth["kycAuth"]["expiryBlock"],
273
+ kyc_auth["signature"],
274
+ ],
275
+ nonce=nonce,
276
+ gas=DEFAULT_GAS_LIMIT,
277
+ local_account=local_account,
278
+ )
279
+
280
+ async def withdraw(
281
+ self,
282
+ collateral_address: str,
283
+ strategy_id: str,
284
+ amount: Decimal,
285
+ nonce: Optional[int] = None,
286
+ local_account: Optional[LocalAccount] = None,
287
+ ) -> TxReceipt:
288
+ """
289
+ Withdraw collateral from a strategy.
290
+
291
+ Parameters
292
+ ----------
293
+ collateral_address : str
294
+ The token contract address to withdraw
295
+ strategy_id : str
296
+ Strategy identifier to withdraw from
297
+ amount : Decimal
298
+ Amount to deposit
299
+ nonce : Optional[int]
300
+ Custom nonce for the transaction, if None uses next available
301
+ local_account : Optional[LocalAccount]
302
+ Local account to sign with, defaults to client's account
303
+
304
+ Returns
305
+ -------
306
+ TxReceipt
307
+ Transaction receipt
308
+ """
309
+
310
+ account = local_account or self._web3_account
311
+
312
+ # Get checkpointed epoch id
313
+ checkpoint_contract = Checkpoint(self._web3, self._verifying_contract)
314
+ checkpointed_epoch_id = checkpoint_contract.get_latest_checkpoint.call()[3]
315
+
316
+ # Get withdrawal proof
317
+ strategy_key = StrategyKey(
318
+ f"0x00{account.address[2:]}",
319
+ StrategyKey.generate_strategy_id_hash(strategy_id),
320
+ )
321
+
322
+ proof_response = await self._http.get(
323
+ self._build_url(OnChain.PROOF),
324
+ params={
325
+ "key": str(strategy_key.encode_key()),
326
+ "epochId": checkpointed_epoch_id,
327
+ },
328
+ )
329
+
330
+ collateral_address = self._web3.to_checksum_address(collateral_address)
331
+
332
+ # Prepare strategy data
333
+ checkpointed_strategy = {
334
+ "maxLeverage": 3,
335
+ "frozen": False,
336
+ "availCollateral": {
337
+ "tokens": [
338
+ self._web3.to_checksum_address(key)
339
+ for key in list(
340
+ proof_response["item"]["Strategy"]["availCollateral"].keys()
341
+ )
342
+ ],
343
+ "amounts": to_base_unit_amount_list(
344
+ [
345
+ Decimal(val)
346
+ for val in list(
347
+ proof_response["item"]["Strategy"][
348
+ "availCollateral"
349
+ ].values()
350
+ )
351
+ ],
352
+ 6,
353
+ ),
354
+ },
355
+ "lockedCollateral": {
356
+ "tokens": [
357
+ self._web3.to_checksum_address(key)
358
+ for key in list(
359
+ proof_response["item"]["Strategy"]["lockedCollateral"].keys()
360
+ )
361
+ ],
362
+ "amounts": to_base_unit_amount_list(
363
+ [
364
+ Decimal(val)
365
+ for val in list(
366
+ proof_response["item"]["Strategy"][
367
+ "lockedCollateral"
368
+ ].values()
369
+ )
370
+ ],
371
+ 6,
372
+ ),
373
+ },
374
+ }
375
+
376
+ withdraw_data = {
377
+ "tokens": [collateral_address],
378
+ "amounts": [int(amount * Decimal("1e6"))],
379
+ }
380
+
381
+ # Execute withdrawal
382
+ i_collateral_contract = ICollateral(self._web3, self._verifying_contract)
383
+ return self._send_transaction(
384
+ i_collateral_contract.withdraw,
385
+ method_params=[
386
+ StrategyKey.generate_strategy_id_hash(strategy_id),
387
+ withdraw_data,
388
+ checkpointed_strategy,
389
+ f'0x{bytes(proof_response["proof"]).hex()}',
390
+ ],
391
+ nonce=nonce,
392
+ local_account=local_account,
393
+ )
394
+
395
+ # TODO: Add and fuzz withdraw_ddx method
396
+ # TODO: Add and fuzz checkpoint method
397
+
398
+ async def wait_for_confirmations(
399
+ self,
400
+ tx_receipt: TxReceipt,
401
+ confirmations: int = 6,
402
+ check_interval: float = 1.0,
403
+ ) -> None:
404
+ """
405
+ Wait for required number of block confirmations.
406
+
407
+ Parameters
408
+ ----------
409
+ tx_receipt : TxReceipt
410
+ Transaction receipt containing block number
411
+ confirmations : int, default=6
412
+ Number of block confirmations to wait for
413
+ check_interval : float, default=1.0
414
+ How often to check for new blocks in seconds
415
+
416
+ Raises
417
+ ------
418
+ TimeoutError
419
+ If confirmations don't arrive within reasonable time
420
+ """
421
+
422
+ tx_block_number = tx_receipt.blockNumber
423
+ timeout = (confirmations * 2) * 12
424
+ start_time = asyncio.get_event_loop().time()
425
+
426
+ while True:
427
+ current_block = self._web3.eth.block_number
428
+ confirmed_blocks = current_block - tx_block_number
429
+
430
+ if confirmed_blocks >= confirmations:
431
+ return
432
+
433
+ if asyncio.get_event_loop().time() - start_time > timeout:
434
+ raise TimeoutError(
435
+ f"Timeout waiting for {confirmations} confirmations. "
436
+ f"Got {confirmed_blocks} after {timeout} seconds"
437
+ )
438
+
439
+ await asyncio.sleep(check_interval)