nado-protocol 0.1.0__py3-none-any.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 (78) hide show
  1. nado_protocol/__init__.py +0 -0
  2. nado_protocol/client/__init__.py +200 -0
  3. nado_protocol/client/apis/__init__.py +26 -0
  4. nado_protocol/client/apis/base.py +42 -0
  5. nado_protocol/client/apis/market/__init__.py +23 -0
  6. nado_protocol/client/apis/market/execute.py +192 -0
  7. nado_protocol/client/apis/market/query.py +310 -0
  8. nado_protocol/client/apis/perp/__init__.py +18 -0
  9. nado_protocol/client/apis/perp/query.py +30 -0
  10. nado_protocol/client/apis/rewards/__init__.py +6 -0
  11. nado_protocol/client/apis/rewards/execute.py +131 -0
  12. nado_protocol/client/apis/rewards/query.py +12 -0
  13. nado_protocol/client/apis/spot/__init__.py +23 -0
  14. nado_protocol/client/apis/spot/base.py +32 -0
  15. nado_protocol/client/apis/spot/execute.py +117 -0
  16. nado_protocol/client/apis/spot/query.py +79 -0
  17. nado_protocol/client/apis/subaccount/__init__.py +24 -0
  18. nado_protocol/client/apis/subaccount/execute.py +54 -0
  19. nado_protocol/client/apis/subaccount/query.py +145 -0
  20. nado_protocol/client/context.py +90 -0
  21. nado_protocol/contracts/__init__.py +377 -0
  22. nado_protocol/contracts/abis/Endpoint.json +636 -0
  23. nado_protocol/contracts/abis/FQuerier.json +1909 -0
  24. nado_protocol/contracts/abis/IClearinghouse.json +876 -0
  25. nado_protocol/contracts/abis/IERC20.json +185 -0
  26. nado_protocol/contracts/abis/IEndpoint.json +250 -0
  27. nado_protocol/contracts/abis/IFoundationRewardsAirdrop.json +76 -0
  28. nado_protocol/contracts/abis/IOffchainBook.json +536 -0
  29. nado_protocol/contracts/abis/IPerpEngine.json +931 -0
  30. nado_protocol/contracts/abis/IProductEngine.json +352 -0
  31. nado_protocol/contracts/abis/ISpotEngine.json +813 -0
  32. nado_protocol/contracts/abis/IStaking.json +288 -0
  33. nado_protocol/contracts/abis/IVrtxAirdrop.json +138 -0
  34. nado_protocol/contracts/abis/MockERC20.json +311 -0
  35. nado_protocol/contracts/deployments/deployment.test.json +18 -0
  36. nado_protocol/contracts/eip712/__init__.py +16 -0
  37. nado_protocol/contracts/eip712/domain.py +36 -0
  38. nado_protocol/contracts/eip712/sign.py +79 -0
  39. nado_protocol/contracts/eip712/types.py +154 -0
  40. nado_protocol/contracts/loader.py +55 -0
  41. nado_protocol/contracts/types.py +141 -0
  42. nado_protocol/engine_client/__init__.py +35 -0
  43. nado_protocol/engine_client/execute.py +416 -0
  44. nado_protocol/engine_client/query.py +481 -0
  45. nado_protocol/engine_client/types/__init__.py +113 -0
  46. nado_protocol/engine_client/types/execute.py +680 -0
  47. nado_protocol/engine_client/types/models.py +247 -0
  48. nado_protocol/engine_client/types/query.py +516 -0
  49. nado_protocol/engine_client/types/stream.py +6 -0
  50. nado_protocol/indexer_client/__init__.py +28 -0
  51. nado_protocol/indexer_client/query.py +466 -0
  52. nado_protocol/indexer_client/types/__init__.py +122 -0
  53. nado_protocol/indexer_client/types/models.py +364 -0
  54. nado_protocol/indexer_client/types/query.py +819 -0
  55. nado_protocol/trigger_client/__init__.py +17 -0
  56. nado_protocol/trigger_client/execute.py +118 -0
  57. nado_protocol/trigger_client/query.py +61 -0
  58. nado_protocol/trigger_client/types/__init__.py +7 -0
  59. nado_protocol/trigger_client/types/execute.py +89 -0
  60. nado_protocol/trigger_client/types/models.py +44 -0
  61. nado_protocol/trigger_client/types/query.py +77 -0
  62. nado_protocol/utils/__init__.py +37 -0
  63. nado_protocol/utils/backend.py +111 -0
  64. nado_protocol/utils/bytes32.py +159 -0
  65. nado_protocol/utils/enum.py +6 -0
  66. nado_protocol/utils/exceptions.py +58 -0
  67. nado_protocol/utils/execute.py +403 -0
  68. nado_protocol/utils/expiration.py +45 -0
  69. nado_protocol/utils/interest.py +66 -0
  70. nado_protocol/utils/math.py +67 -0
  71. nado_protocol/utils/model.py +79 -0
  72. nado_protocol/utils/nonce.py +33 -0
  73. nado_protocol/utils/subaccount.py +18 -0
  74. nado_protocol/utils/time.py +21 -0
  75. nado_protocol-0.1.0.dist-info/METADATA +157 -0
  76. nado_protocol-0.1.0.dist-info/RECORD +78 -0
  77. nado_protocol-0.1.0.dist-info/WHEEL +4 -0
  78. nado_protocol-0.1.0.dist-info/entry_points.txt +11 -0
@@ -0,0 +1,159 @@
1
+ import binascii
2
+ from typing import Optional, Union
3
+ from nado_protocol.utils.subaccount import Subaccount, SubaccountParams
4
+
5
+
6
+ def hex_to_bytes32(input: Union[str, bytes]) -> bytes:
7
+ """Converts a hexadecimal string or bytes to a bytes object of length 32.
8
+
9
+ Args:
10
+ input (str | bytes): The hexadecimal string or bytes to be converted.
11
+
12
+ Returns:
13
+ bytes: The converted bytes object of length 32.
14
+ """
15
+ return hex_to_bytes(input, 32)
16
+
17
+
18
+ def hex_to_bytes12(input: Union[str, bytes]) -> bytes:
19
+ """Converts a hexadecimal string or bytes to a bytes object of length 12.
20
+
21
+ Args:
22
+ input (str | bytes): The hexadecimal string or bytes to be converted.
23
+
24
+ Returns:
25
+ bytes: The converted bytes object of length 12.
26
+ """
27
+ return hex_to_bytes(input, 12)
28
+
29
+
30
+ def hex_to_bytes(input: Union[str, bytes], size: int) -> bytes:
31
+ """Converts a hexadecimal string or bytes to a bytes object of specified size.
32
+
33
+ Args:
34
+ input (str | bytes): The hexadecimal string or bytes to be converted.
35
+
36
+ size (int): The specified size for the output bytes object.
37
+
38
+ Returns:
39
+ bytes: The converted bytes object of the specified size.
40
+ """
41
+ if isinstance(input, bytes):
42
+ return input
43
+ if input.encode() == zero_subaccount():
44
+ return zero_subaccount()
45
+ if input.startswith("0x"):
46
+ input = input[2:]
47
+ data_bytes = bytes.fromhex(input)
48
+ padded_data = data_bytes + b"\x00" * (size - len(data_bytes))
49
+ return padded_data
50
+
51
+
52
+ def str_to_hex(input: str) -> str:
53
+ """Converts a string to its hexadecimal representation.
54
+
55
+ Args:
56
+ input (str): The string to be converted.
57
+
58
+ Returns:
59
+ str: The hexadecimal representation of the input string.
60
+ """
61
+ return binascii.hexlify(input.encode()).decode()
62
+
63
+
64
+ def subaccount_to_bytes32(
65
+ subaccount: Subaccount, name: Optional[Union[str, bytes]] = None
66
+ ) -> bytes:
67
+ """Converts a subaccount representation to a bytes object of length 32.
68
+
69
+ Args:
70
+ subaccount (Subaccount): The subaccount, which can be a string, bytes, or SubaccountParams instance.
71
+
72
+ name (str|bytes, optional): The subaccount name, when provided `subaccount` is expected to be the owner address.
73
+
74
+ Returns:
75
+ (bytes|SubaccountParams): The bytes object of length 32 representing the subaccount.
76
+
77
+ Raises:
78
+ ValueError: If the `subaccount` is a `SubaccountParams` instance and is missing either `subaccount_owner` or `subaccount_name`
79
+
80
+ Note:
81
+ If `name` is provided, `subaccount` must be the owner address, otherwise `subaccount`
82
+ can be the bytes32 or hex representation of the subaccount or a SubaccountParams object.
83
+ """
84
+ if isinstance(subaccount, str):
85
+ if name is None:
86
+ return hex_to_bytes32(subaccount)
87
+ else:
88
+ name = name.hex() if isinstance(name, bytes) else name
89
+ return hex_to_bytes32(subaccount + str_to_hex(name))
90
+ elif isinstance(subaccount, SubaccountParams):
91
+ subaccount_owner = subaccount.dict().get("subaccount_owner")
92
+ subaccount_name = subaccount.dict().get("subaccount_name")
93
+ if subaccount_owner is None or subaccount_name is None:
94
+ raise ValueError("Missing `subaccount_owner` or `subaccount_name`")
95
+ else:
96
+ return hex_to_bytes32(subaccount_owner + str_to_hex(subaccount_name))
97
+ else:
98
+ return subaccount
99
+
100
+
101
+ def subaccount_to_hex(
102
+ subaccount: Subaccount, name: Optional[Union[str, bytes]] = None
103
+ ) -> str:
104
+ """Converts a subaccount representation to its hexadecimal representation.
105
+
106
+ Args:
107
+ subaccount (Subaccount): The subaccount, which can be a string, bytes, or SubaccountParams instance.
108
+
109
+ name (str|bytes, optional): Additional string, if any, to be appended to the subaccount string before conversion. Defaults to None.
110
+
111
+ Returns:
112
+ (str|SubaccountParams): The hexadecimal representation of the subaccount.
113
+ """
114
+ return bytes32_to_hex(subaccount_to_bytes32(subaccount, name))
115
+
116
+
117
+ def subaccount_name_to_bytes12(subaccount_name: str) -> bytes:
118
+ """Converts a subaccount name to a bytes object of length 12.
119
+
120
+ Args:
121
+ subaccount_name (str): The subaccount name to be converted.
122
+
123
+ Returns:
124
+ bytes: A bytes object of length 12 representing the subaccount name.
125
+ """
126
+ return hex_to_bytes12(str_to_hex(subaccount_name))
127
+
128
+
129
+ def bytes32_to_hex(bytes32: bytes) -> str:
130
+ """Converts a bytes object of length 32 to its hexadecimal representation.
131
+
132
+ Args:
133
+ bytes32 (bytes): The bytes object of length 32 to be converted.
134
+
135
+ Returns:
136
+ str: The hexadecimal representation of the input bytes object. If the input is not a bytes object, the function returns the input itself.
137
+ """
138
+ if isinstance(bytes32, bytes):
139
+ return f"0x{bytes32.hex()}"
140
+ else:
141
+ return bytes32
142
+
143
+
144
+ def zero_subaccount() -> bytes:
145
+ """Generates a bytes object of length 32 filled with zero bytes.
146
+
147
+ Returns:
148
+ bytes: A bytes object of length 32 filled with zero bytes.
149
+ """
150
+ return b"\x00" * 32
151
+
152
+
153
+ def zero_address() -> bytes:
154
+ """Generates a bytes object of length 20 filled with zero bytes.
155
+
156
+ Returns:
157
+ bytes: A bytes object of length 20 filled with zero bytes.
158
+ """
159
+ return b"\x00" * 20
@@ -0,0 +1,6 @@
1
+ from enum import Enum
2
+
3
+
4
+ class StrEnum(str, Enum):
5
+ def __str__(self):
6
+ return self.value
@@ -0,0 +1,58 @@
1
+ class ExecuteFailedException(Exception):
2
+ """Raised when the execute status is not 'success'"""
3
+
4
+ def __init__(self, message="Execute failed"):
5
+ self.message = message
6
+ super().__init__(self.message)
7
+
8
+
9
+ class QueryFailedException(Exception):
10
+ """Raised when the query status is not 'success'"""
11
+
12
+ def __init__(self, message="Query failed"):
13
+ self.message = message
14
+ super().__init__(self.message)
15
+
16
+
17
+ class BadStatusCodeException(Exception):
18
+ """Raised when the response status code is not 200"""
19
+
20
+ def __init__(self, message="Bad status code"):
21
+ self.message = message
22
+ super().__init__(self.message)
23
+
24
+
25
+ class MissingSignerException(Exception):
26
+ """Raised when the Signer is required to perform an operation but it's not provided."""
27
+
28
+ def __init__(self, message="Signer not provided"):
29
+ self.message = message
30
+ super().__init__(self.message)
31
+
32
+
33
+ class InvalidProductId(Exception):
34
+ """Raised when product id is invalid."""
35
+
36
+ def __init__(self, message="Invalid product id provided"):
37
+ self.message = message
38
+ super().__init__(self.message)
39
+
40
+
41
+ class InvalidVrtxClaimParams(Exception):
42
+ """Raised when providing invalid VRTX claim parameters."""
43
+
44
+ def __init__(
45
+ self,
46
+ message="Invalid VRTX params. Either `amount` or `claim_all` must be provided",
47
+ ):
48
+ self.message = message
49
+ super().__init__(self.message)
50
+
51
+
52
+ class MissingTriggerClient(Exception):
53
+ def __init__(
54
+ self,
55
+ message="Trigger client not initialized.",
56
+ ):
57
+ self.message = message
58
+ super().__init__(self.message)
@@ -0,0 +1,403 @@
1
+ from abc import abstractmethod
2
+ from copy import deepcopy
3
+ from typing import Optional, Type, Union
4
+ from eth_account.signers.local import LocalAccount
5
+ from pydantic import validator
6
+ from nado_protocol.contracts.eip712.sign import (
7
+ build_eip712_typed_data,
8
+ get_eip712_typed_data_digest,
9
+ sign_eip712_typed_data,
10
+ )
11
+ from nado_protocol.contracts.types import NadoExecuteType
12
+ from nado_protocol.utils.backend import NadoClientOpts
13
+ from nado_protocol.utils.bytes32 import subaccount_to_bytes32, subaccount_to_hex
14
+ from nado_protocol.utils.model import NadoBaseModel
15
+ from nado_protocol.utils.nonce import gen_order_nonce
16
+ from nado_protocol.utils.subaccount import Subaccount, SubaccountParams
17
+
18
+
19
+ class BaseParams(NadoBaseModel):
20
+ """
21
+ Base class for defining request parameters to be sent to the Nado API.
22
+
23
+ Attributes:
24
+ sender (Subaccount): The sender's subaccount identifier.
25
+ nonce (Optional[int]): An optional nonce for the request.
26
+
27
+ Note:
28
+ - The sender attribute is validated and serialized to bytes32 format before sending the request.
29
+ """
30
+
31
+ sender: Subaccount
32
+ nonce: Optional[int]
33
+
34
+ class Config:
35
+ validate_assignment = True
36
+
37
+ @validator("sender")
38
+ def serialize_sender(cls, v: Subaccount) -> Union[bytes, Subaccount]:
39
+ """
40
+ Validates and serializes the sender to bytes32 format.
41
+
42
+ Args:
43
+ v (Subaccount): The sender's subaccount identifier.
44
+
45
+ Returns:
46
+ (bytes|Subaccount): The serialized sender in bytes32 format or the original Subaccount if it cannot be converted to bytes32.
47
+ """
48
+ try:
49
+ return subaccount_to_bytes32(v)
50
+ except ValueError:
51
+ return v
52
+
53
+
54
+ class SignatureParams(NadoBaseModel):
55
+ """
56
+ Class for defining signature parameters in a request sent to the Nado API.
57
+
58
+ Attributes:
59
+ signature (Optional[str]): An optional string representing the signature for the request.
60
+ """
61
+
62
+ signature: Optional[str]
63
+
64
+
65
+ class BaseParamsSigned(BaseParams, SignatureParams):
66
+ """
67
+ Class that combines the base parameters and signature parameters for a signed request
68
+ to the Nado API. Inherits attributes from BaseParams and SignatureParams.
69
+ """
70
+
71
+ pass
72
+
73
+
74
+ class MarketOrderParams(BaseParams):
75
+ """
76
+ Class for defining the parameters of a market order.
77
+
78
+ Attributes:
79
+ amount (int): The amount of the asset to be bought or sold in the order. Positive for a `long` position and negative for a `short`.
80
+
81
+ expiration (int): The unix timestamp at which the order will expire.
82
+
83
+ nonce (Optional[int]): A unique number used to prevent replay attacks.
84
+ """
85
+
86
+ amount: int
87
+ nonce: Optional[int]
88
+
89
+
90
+ class OrderParams(MarketOrderParams):
91
+ """
92
+ Class for defining the parameters of an order.
93
+
94
+ Attributes:
95
+ priceX18 (int): The price of the order with a precision of 18 decimal places.
96
+
97
+ expiration (int): The unix timestamp at which the order will expire.
98
+
99
+ amount (int): The amount of the asset to be bought or sold in the order. Positive for a `long` position and negative for a `short`.
100
+
101
+ nonce (Optional[int]): A unique number used to prevent replay attacks.
102
+ """
103
+
104
+ priceX18: int
105
+ expiration: int
106
+
107
+
108
+ class IsolatedOrderParams(OrderParams):
109
+ """
110
+ Class for defining the parameters of an isolated order.
111
+
112
+ Attributes:
113
+ priceX18 (int): The price of the order with a precision of 18 decimal places.
114
+
115
+ expiration (int): The unix timestamp at which the order will expire.
116
+
117
+ amount (int): The amount of the asset to be bought or sold in the order. Positive for a `long` position and negative for a `short`.
118
+
119
+ nonce (Optional[int]): A unique number used to prevent replay attacks.
120
+
121
+ margin (int): The margin amount for the isolated order.
122
+ """
123
+
124
+ margin: int
125
+
126
+
127
+ class NadoBaseExecute:
128
+ def __init__(self, opts: NadoClientOpts):
129
+ self._opts = opts
130
+
131
+ @abstractmethod
132
+ def tx_nonce(self, _: str) -> int:
133
+ pass
134
+
135
+ @property
136
+ def endpoint_addr(self) -> str:
137
+ if self._opts.endpoint_addr is None:
138
+ raise AttributeError("Endpoint address not set.")
139
+ return self._opts.endpoint_addr
140
+
141
+ @endpoint_addr.setter
142
+ def endpoint_addr(self, addr: str) -> None:
143
+ self._opts.endpoint_addr = addr
144
+
145
+ @property
146
+ def book_addrs(self) -> list[str]:
147
+ if self._opts.book_addrs is None:
148
+ raise AttributeError("Book addresses are not set.")
149
+ return self._opts.book_addrs
150
+
151
+ @book_addrs.setter
152
+ def book_addrs(self, book_addrs: list[str]) -> None:
153
+ self._opts.book_addrs = book_addrs
154
+
155
+ @property
156
+ def chain_id(self) -> int:
157
+ if self._opts.chain_id is None:
158
+ raise AttributeError("Chain ID is not set.")
159
+ return self._opts.chain_id
160
+
161
+ @chain_id.setter
162
+ def chain_id(self, chain_id: Union[int, str]) -> None:
163
+ self._opts.chain_id = int(chain_id)
164
+
165
+ @property
166
+ def signer(self) -> LocalAccount:
167
+ if self._opts.signer is None:
168
+ raise AttributeError("Signer is not set.")
169
+ assert isinstance(self._opts.signer, LocalAccount)
170
+ return self._opts.signer
171
+
172
+ @signer.setter
173
+ def signer(self, signer: LocalAccount) -> None:
174
+ self._opts.signer = signer
175
+
176
+ @property
177
+ def linked_signer(self) -> LocalAccount:
178
+ if self._opts.linked_signer is not None:
179
+ assert isinstance(self._opts.linked_signer, LocalAccount)
180
+ return self._opts.linked_signer
181
+ if self._opts.signer is not None:
182
+ assert isinstance(self._opts.signer, LocalAccount)
183
+ return self.signer
184
+ raise AttributeError("Signer is not set.")
185
+
186
+ @linked_signer.setter
187
+ def linked_signer(self, linked_signer: LocalAccount) -> None:
188
+ if self._opts.signer is None:
189
+ raise AttributeError(
190
+ "Must set a `signer` first before setting `linked_signer`."
191
+ )
192
+ self._opts.linked_signer = linked_signer
193
+
194
+ def book_addr(self, product_id: int) -> str:
195
+ """
196
+ Retrieves the book address corresponding to the provided product ID.
197
+
198
+ Needed for signing order placement executes for different products.
199
+
200
+ Args:
201
+ product_id (int): The ID of the product.
202
+
203
+ Returns:
204
+ str: The book address associated with the given product ID.
205
+
206
+ Raises:
207
+ ValueError: If the provided product_id is greater than or equal to the number of book addresses available.
208
+ """
209
+ if product_id >= len(self.book_addrs):
210
+ raise ValueError(f"Invalid product_id {product_id} provided.")
211
+ return self.book_addrs[product_id]
212
+
213
+ def order_nonce(
214
+ self, recv_time_ms: Optional[int] = None, is_trigger_order: bool = False
215
+ ) -> int:
216
+ """
217
+ Generate the order nonce. Used for oder placements and cancellations.
218
+
219
+ Args:
220
+ recv_time_ms (int, optional): Received time in milliseconds.
221
+
222
+ Returns:
223
+ int: The generated order nonce.
224
+ """
225
+ return gen_order_nonce(recv_time_ms, is_trigger_order=is_trigger_order)
226
+
227
+ def _inject_owner_if_needed(self, params: Type[BaseParams]) -> Type[BaseParams]:
228
+ """
229
+ Inject the owner if needed.
230
+
231
+ Args:
232
+ params (Type[BaseParams]): The parameters.
233
+
234
+ Returns:
235
+ Type[BaseParams]: The parameters with the owner injected if needed.
236
+ """
237
+ if isinstance(params.sender, SubaccountParams):
238
+ params.sender.subaccount_owner = (
239
+ params.sender.subaccount_owner or self.signer.address
240
+ )
241
+ params.sender = params.serialize_sender(params.sender)
242
+ return params
243
+
244
+ def _inject_nonce_if_needed(
245
+ self,
246
+ params: Type[BaseParams],
247
+ use_order_nonce: bool,
248
+ is_trigger_order: bool = False,
249
+ ) -> Type[BaseParams]:
250
+ """
251
+ Inject the nonce if needed.
252
+
253
+ Args:
254
+ params (Type[BaseParams]): The parameters.
255
+
256
+ Returns:
257
+ Type[BaseParams]: The parameters with the nonce injected if needed.
258
+ """
259
+ if params.nonce is not None:
260
+ return params
261
+ params.nonce = (
262
+ self.order_nonce(is_trigger_order=is_trigger_order)
263
+ if use_order_nonce
264
+ else self.tx_nonce(subaccount_to_hex(params.sender))
265
+ )
266
+ return params
267
+
268
+ def prepare_execute_params(
269
+ self, params, use_order_nonce: bool, is_trigger_order: bool = False
270
+ ):
271
+ """
272
+ Prepares the parameters for execution by ensuring that both owner and nonce are correctly set.
273
+
274
+ Args:
275
+ params (Type[BaseParams]): The original parameters.
276
+
277
+ Returns:
278
+ Type[BaseParams]: A copy of the original parameters with owner and nonce injected if needed.
279
+ """
280
+ params = deepcopy(params)
281
+ params = self._inject_owner_if_needed(params)
282
+ params = self._inject_nonce_if_needed(params, use_order_nonce, is_trigger_order)
283
+ return params
284
+
285
+ def _sign(
286
+ self, execute: NadoExecuteType, msg: dict, product_id: Optional[int] = None
287
+ ) -> str:
288
+ """
289
+ Internal method to create an EIP-712 signature for the given operation type and message.
290
+
291
+ Args:
292
+ execute (NadoExecuteType): The Nado execute type to sign.
293
+
294
+ msg (dict): The message to be signed.
295
+
296
+ product_id (int, optional): Required for 'PLACE_ORDER' operation, specifying the product ID.
297
+
298
+ Returns:
299
+ str: The generated EIP-712 signature.
300
+
301
+ Raises:
302
+ ValueError: If the operation type is 'PLACE_ORDER' and no product_id is provided.
303
+
304
+ Notes:
305
+ The contract used for verification varies based on the operation type:
306
+ - For 'PLACE_ORDER', it's derived from the book address associated with the product_id.
307
+ - For other operations, it's the endpoint address.
308
+ """
309
+ is_place_order = (
310
+ execute == NadoExecuteType.PLACE_ORDER
311
+ or execute == NadoExecuteType.PLACE_ISOLATED_ORDER
312
+ )
313
+ if is_place_order and product_id is None:
314
+ raise ValueError(
315
+ "Missing `product_id` to sign place_order or place_isolated_order execute"
316
+ )
317
+ verifying_contract = (
318
+ self.book_addr(product_id)
319
+ if is_place_order and product_id
320
+ else self.endpoint_addr
321
+ )
322
+ return self.sign(
323
+ execute, msg, verifying_contract, self.chain_id, self.linked_signer
324
+ )
325
+
326
+ def build_digest(
327
+ self,
328
+ execute: NadoExecuteType,
329
+ msg: dict,
330
+ verifying_contract: str,
331
+ chain_id: int,
332
+ ) -> str:
333
+ """
334
+ Build an EIP-712 compliant digest from given parameters.
335
+
336
+ Must provide the same input to build an EIP-712 typed data as the one provided for signing via `.sign(...)`
337
+
338
+ Args:
339
+ execute (NadoExecuteType): The Nado execute type to build digest for.
340
+
341
+ msg (dict): The EIP712 message.
342
+
343
+ verifying_contract (str): The contract used for verification.
344
+
345
+ chain_id (int): The network chain ID.
346
+
347
+ Returns:
348
+ str: The digest computed from the provided parameters.
349
+ """
350
+ return get_eip712_typed_data_digest(
351
+ build_eip712_typed_data(execute, msg, verifying_contract, chain_id)
352
+ )
353
+
354
+ def sign(
355
+ self,
356
+ execute: NadoExecuteType,
357
+ msg: dict,
358
+ verifying_contract: str,
359
+ chain_id: int,
360
+ signer: LocalAccount,
361
+ ) -> str:
362
+ """
363
+ Signs the EIP-712 typed data using the provided signer account.
364
+
365
+ Args:
366
+ execute (NadoExecuteType): The type of operation.
367
+
368
+ msg (dict): The message to be signed.
369
+
370
+ verifying_contract (str): The contract used for verification.
371
+
372
+ chain_id (int): The network chain ID.
373
+
374
+ signer (LocalAccount): The account used to sign the data.
375
+
376
+ Returns:
377
+ str: The generated EIP-712 signature.
378
+ """
379
+ return sign_eip712_typed_data(
380
+ typed_data=build_eip712_typed_data(
381
+ execute, msg, verifying_contract, chain_id
382
+ ),
383
+ signer=signer,
384
+ )
385
+
386
+ def get_order_digest(self, order: OrderParams, product_id: int) -> str:
387
+ """
388
+ Generates the order digest for a given order and product ID.
389
+
390
+ Args:
391
+ order (OrderParams): The order parameters.
392
+
393
+ product_id (int): The ID of the product.
394
+
395
+ Returns:
396
+ str: The generated order digest.
397
+ """
398
+ return self.build_digest(
399
+ NadoExecuteType.PLACE_ORDER,
400
+ order.dict(),
401
+ self.book_addr(product_id),
402
+ self.chain_id,
403
+ )
@@ -0,0 +1,45 @@
1
+ from enum import IntEnum
2
+
3
+
4
+ class OrderType(IntEnum):
5
+ DEFAULT = 0
6
+ IOC = 1
7
+ FOK = 2
8
+ POST_ONLY = 3
9
+
10
+
11
+ def get_expiration_timestamp(
12
+ order_type: OrderType, expiration: int, reduce_only: bool = False
13
+ ) -> int:
14
+ """
15
+ Encodes the order type into the expiration timestamp for special order types such as immediate-or-cancel.
16
+
17
+ Args:
18
+ order_type (OrderType): The type of order.
19
+
20
+ expiration (int): The expiration timestamp in UNIX seconds.
21
+
22
+ reduce_only (bool): When True, the order can only reduce the size of an existing position. Works only with IOC & FOK.
23
+
24
+ Returns:
25
+ int: The properly formatted timestamp needed for the specified order type.
26
+ """
27
+ expiration = int(expiration) | (order_type << 62)
28
+ if reduce_only:
29
+ expiration |= 1 << 61
30
+ return expiration
31
+
32
+
33
+ def decode_expiration(expiration: int) -> tuple[OrderType, int]:
34
+ """
35
+ Decodes the expiration timestamp to retrieve the order type and original expiration timestamp.
36
+
37
+ Args:
38
+ expiration (int): The encoded expiration timestamp.
39
+
40
+ Returns:
41
+ Tuple[OrderType, int]: The decoded order type and the original expiration timestamp.
42
+ """
43
+ order_type: OrderType = OrderType(expiration >> 62)
44
+ exp_timestamp = expiration & ((1 << 62) - 1)
45
+ return order_type, exp_timestamp