nado-protocol 0.1.0__py3-none-any.whl → 0.1.2__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 (48) hide show
  1. nado_protocol/client/__init__.py +11 -2
  2. nado_protocol/client/apis/market/execute.py +10 -26
  3. nado_protocol/client/apis/market/query.py +2 -2
  4. nado_protocol/client/apis/rewards/execute.py +27 -27
  5. nado_protocol/client/apis/rewards/query.py +5 -4
  6. nado_protocol/client/apis/subaccount/query.py +1 -1
  7. nado_protocol/client/context.py +0 -2
  8. nado_protocol/contracts/__init__.py +41 -33
  9. nado_protocol/contracts/abis/Endpoint.json +151 -228
  10. nado_protocol/contracts/abis/FQuerier.json +91 -508
  11. nado_protocol/contracts/abis/IAirdrop.json +76 -0
  12. nado_protocol/contracts/abis/IClearinghouse.json +277 -390
  13. nado_protocol/contracts/abis/IEndpoint.json +42 -80
  14. nado_protocol/contracts/abis/IPerpEngine.json +69 -422
  15. nado_protocol/contracts/abis/IProductEngine.json +87 -205
  16. nado_protocol/contracts/abis/ISpotEngine.json +173 -362
  17. nado_protocol/contracts/abis/MockERC20.json +1 -1
  18. nado_protocol/contracts/deployments/{deployment.test.json → deployment.testing.json} +2 -5
  19. nado_protocol/contracts/deployments/deployment.testnet.json +15 -0
  20. nado_protocol/contracts/eip712/types.py +15 -20
  21. nado_protocol/contracts/types.py +15 -13
  22. nado_protocol/engine_client/execute.py +18 -39
  23. nado_protocol/engine_client/query.py +1 -1
  24. nado_protocol/engine_client/types/__init__.py +4 -8
  25. nado_protocol/engine_client/types/execute.py +37 -103
  26. nado_protocol/engine_client/types/models.py +3 -59
  27. nado_protocol/engine_client/types/query.py +3 -6
  28. nado_protocol/indexer_client/query.py +4 -9
  29. nado_protocol/indexer_client/types/__init__.py +4 -5
  30. nado_protocol/indexer_client/types/models.py +16 -23
  31. nado_protocol/indexer_client/types/query.py +12 -11
  32. nado_protocol/trigger_client/execute.py +1 -1
  33. nado_protocol/trigger_client/types/execute.py +3 -1
  34. nado_protocol/utils/__init__.py +18 -1
  35. nado_protocol/utils/backend.py +5 -2
  36. nado_protocol/utils/exceptions.py +3 -3
  37. nado_protocol/utils/execute.py +26 -67
  38. nado_protocol/utils/expiration.py +7 -28
  39. nado_protocol/utils/nonce.py +0 -4
  40. nado_protocol/utils/order.py +356 -0
  41. {nado_protocol-0.1.0.dist-info → nado_protocol-0.1.2.dist-info}/METADATA +4 -2
  42. nado_protocol-0.1.2.dist-info/RECORD +78 -0
  43. {nado_protocol-0.1.0.dist-info → nado_protocol-0.1.2.dist-info}/entry_points.txt +0 -1
  44. nado_protocol/contracts/abis/IERC20.json +0 -185
  45. nado_protocol/contracts/abis/IOffchainBook.json +0 -536
  46. nado_protocol/contracts/abis/IVrtxAirdrop.json +0 -138
  47. nado_protocol-0.1.0.dist-info/RECORD +0 -78
  48. {nado_protocol-0.1.0.dist-info → nado_protocol-0.1.2.dist-info}/WHEEL +0 -0
@@ -13,6 +13,7 @@ from nado_protocol.utils.backend import NadoClientOpts
13
13
  from nado_protocol.utils.bytes32 import subaccount_to_bytes32, subaccount_to_hex
14
14
  from nado_protocol.utils.model import NadoBaseModel
15
15
  from nado_protocol.utils.nonce import gen_order_nonce
16
+ from nado_protocol.utils.order import gen_order_verifying_contract
16
17
  from nado_protocol.utils.subaccount import Subaccount, SubaccountParams
17
18
 
18
19
 
@@ -78,13 +79,10 @@ class MarketOrderParams(BaseParams):
78
79
  Attributes:
79
80
  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
 
81
- expiration (int): The unix timestamp at which the order will expire.
82
-
83
82
  nonce (Optional[int]): A unique number used to prevent replay attacks.
84
83
  """
85
84
 
86
85
  amount: int
87
- nonce: Optional[int]
88
86
 
89
87
 
90
88
  class OrderParams(MarketOrderParams):
@@ -99,29 +97,13 @@ class OrderParams(MarketOrderParams):
99
97
  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
98
 
101
99
  nonce (Optional[int]): A unique number used to prevent replay attacks.
100
+
101
+ appendix (int): Additional data or instructions related to the order. Use to encode order type and other related data.
102
102
  """
103
103
 
104
104
  priceX18: int
105
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
106
+ appendix: int
125
107
 
126
108
 
127
109
  class NadoBaseExecute:
@@ -142,16 +124,6 @@ class NadoBaseExecute:
142
124
  def endpoint_addr(self, addr: str) -> None:
143
125
  self._opts.endpoint_addr = addr
144
126
 
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
127
  @property
156
128
  def chain_id(self) -> int:
157
129
  if self._opts.chain_id is None:
@@ -191,28 +163,24 @@ class NadoBaseExecute:
191
163
  )
192
164
  self._opts.linked_signer = linked_signer
193
165
 
194
- def book_addr(self, product_id: int) -> str:
166
+ def order_verifying_contract(self, product_id: int) -> str:
195
167
  """
196
- Retrieves the book address corresponding to the provided product ID.
168
+ Returns the `place_order` verifying contract address for a given product.
197
169
 
198
- Needed for signing order placement executes for different products.
170
+ Note:
171
+ Previously, `place_order` verifying contracts were accessed via `book_addrs[product_id]`.
172
+ Virtual books have now been removed, and the verifying contract is derived
173
+ directly from the product id.
199
174
 
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.
175
+ The address is computed as `address(product_id)`, i.e. the 20-byte
176
+ big-endian representation of the product id, zero-padded on the left.
205
177
 
206
- Raises:
207
- ValueError: If the provided product_id is greater than or equal to the number of book addresses available.
178
+ Example:
179
+ product_id = 18 → 0x0000000000000000000000000000000000000012
208
180
  """
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]
181
+ return gen_order_verifying_contract(product_id)
212
182
 
213
- def order_nonce(
214
- self, recv_time_ms: Optional[int] = None, is_trigger_order: bool = False
215
- ) -> int:
183
+ def order_nonce(self, recv_time_ms: Optional[int] = None) -> int:
216
184
  """
217
185
  Generate the order nonce. Used for oder placements and cancellations.
218
186
 
@@ -222,7 +190,7 @@ class NadoBaseExecute:
222
190
  Returns:
223
191
  int: The generated order nonce.
224
192
  """
225
- return gen_order_nonce(recv_time_ms, is_trigger_order=is_trigger_order)
193
+ return gen_order_nonce(recv_time_ms)
226
194
 
227
195
  def _inject_owner_if_needed(self, params: Type[BaseParams]) -> Type[BaseParams]:
228
196
  """
@@ -245,7 +213,6 @@ class NadoBaseExecute:
245
213
  self,
246
214
  params: Type[BaseParams],
247
215
  use_order_nonce: bool,
248
- is_trigger_order: bool = False,
249
216
  ) -> Type[BaseParams]:
250
217
  """
251
218
  Inject the nonce if needed.
@@ -259,15 +226,13 @@ class NadoBaseExecute:
259
226
  if params.nonce is not None:
260
227
  return params
261
228
  params.nonce = (
262
- self.order_nonce(is_trigger_order=is_trigger_order)
229
+ self.order_nonce()
263
230
  if use_order_nonce
264
231
  else self.tx_nonce(subaccount_to_hex(params.sender))
265
232
  )
266
233
  return params
267
234
 
268
- def prepare_execute_params(
269
- self, params, use_order_nonce: bool, is_trigger_order: bool = False
270
- ):
235
+ def prepare_execute_params(self, params, use_order_nonce: bool):
271
236
  """
272
237
  Prepares the parameters for execution by ensuring that both owner and nonce are correctly set.
273
238
 
@@ -279,7 +244,7 @@ class NadoBaseExecute:
279
244
  """
280
245
  params = deepcopy(params)
281
246
  params = self._inject_owner_if_needed(params)
282
- params = self._inject_nonce_if_needed(params, use_order_nonce, is_trigger_order)
247
+ params = self._inject_nonce_if_needed(params, use_order_nonce)
283
248
  return params
284
249
 
285
250
  def _sign(
@@ -306,16 +271,11 @@ class NadoBaseExecute:
306
271
  - For 'PLACE_ORDER', it's derived from the book address associated with the product_id.
307
272
  - For other operations, it's the endpoint address.
308
273
  """
309
- is_place_order = (
310
- execute == NadoExecuteType.PLACE_ORDER
311
- or execute == NadoExecuteType.PLACE_ISOLATED_ORDER
312
- )
274
+ is_place_order = execute == NadoExecuteType.PLACE_ORDER
313
275
  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
- )
276
+ raise ValueError("Missing `product_id` to sign place_order execute")
317
277
  verifying_contract = (
318
- self.book_addr(product_id)
278
+ self.order_verifying_contract(product_id)
319
279
  if is_place_order and product_id
320
280
  else self.endpoint_addr
321
281
  )
@@ -376,10 +336,9 @@ class NadoBaseExecute:
376
336
  Returns:
377
337
  str: The generated EIP-712 signature.
378
338
  """
339
+ typed_data = build_eip712_typed_data(execute, msg, verifying_contract, chain_id)
379
340
  return sign_eip712_typed_data(
380
- typed_data=build_eip712_typed_data(
381
- execute, msg, verifying_contract, chain_id
382
- ),
341
+ typed_data=typed_data,
383
342
  signer=signer,
384
343
  )
385
344
 
@@ -398,6 +357,6 @@ class NadoBaseExecute:
398
357
  return self.build_digest(
399
358
  NadoExecuteType.PLACE_ORDER,
400
359
  order.dict(),
401
- self.book_addr(product_id),
360
+ self.order_verifying_contract(product_id),
402
361
  self.chain_id,
403
362
  )
@@ -1,4 +1,5 @@
1
1
  from enum import IntEnum
2
+ import time
2
3
 
3
4
 
4
5
  class OrderType(IntEnum):
@@ -8,38 +9,16 @@ class OrderType(IntEnum):
8
9
  POST_ONLY = 3
9
10
 
10
11
 
11
- def get_expiration_timestamp(
12
- order_type: OrderType, expiration: int, reduce_only: bool = False
13
- ) -> int:
12
+ def get_expiration_timestamp(seconds_from_now: int) -> int:
14
13
  """
15
- Encodes the order type into the expiration timestamp for special order types such as immediate-or-cancel.
14
+ Returns a timestamp that is seconds_from_now in the future.
16
15
 
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.
16
+ Order types and reduce-only flags should now be set using build_appendix().
36
17
 
37
18
  Args:
38
- expiration (int): The encoded expiration timestamp.
19
+ seconds_from_now (int): Number of seconds from now for expiration.
39
20
 
40
21
  Returns:
41
- Tuple[OrderType, int]: The decoded order type and the original expiration timestamp.
22
+ int: The expiration timestamp.
42
23
  """
43
- order_type: OrderType = OrderType(expiration >> 62)
44
- exp_timestamp = expiration & ((1 << 62) - 1)
45
- return order_type, exp_timestamp
24
+ return int(time.time()) + seconds_from_now
@@ -6,7 +6,6 @@ import random
6
6
  def gen_order_nonce(
7
7
  recv_time_ms: Optional[int] = None,
8
8
  random_int: Optional[int] = None,
9
- is_trigger_order: bool = False,
10
9
  ) -> int:
11
10
  """
12
11
  Generates an order nonce based on a received timestamp and a random integer.
@@ -27,7 +26,4 @@ def gen_order_nonce(
27
26
  random_int = random.randint(0, 999)
28
27
 
29
28
  nonce = (recv_time_ms << 20) + random_int
30
-
31
- if is_trigger_order:
32
- nonce = nonce | (1 << 63)
33
29
  return nonce
@@ -0,0 +1,356 @@
1
+ from typing import Optional
2
+ from enum import IntEnum
3
+ from nado_protocol.utils.expiration import OrderType
4
+
5
+
6
+ # Order appendix version
7
+ APPENDIX_VERSION = 0
8
+
9
+
10
+ class AppendixBitFields:
11
+ # | value | reserved | trigger | reduce only | order type| isolated | version |
12
+ # | 96 bits | 18 bits | 2 bits | 1 bit | 2 bits | 1 bit | 8 bits |
13
+ # | 127..32 | 31..14 | 13..12 | 11 | 10..9 | 8 | 7..0 |
14
+
15
+ # Bit positions (from LSB to MSB)
16
+ VERSION_BITS = 8 # bits 7..0
17
+ ISOLATED_BITS = 1 # bit 8
18
+ ORDER_TYPE_BITS = 2 # bits 10..9
19
+ REDUCE_ONLY_BITS = 1 # bit 11
20
+ TRIGGER_TYPE_BITS = 2 # bits 13..12
21
+ RESERVED_BITS = 18 # bits 31..14
22
+ VALUE_BITS = 96 # bits 127..32 (for isolated margin or TWAP data)
23
+
24
+ # Bit masks
25
+ VERSION_MASK = (1 << VERSION_BITS) - 1
26
+ ISOLATED_MASK = (1 << ISOLATED_BITS) - 1
27
+ ORDER_TYPE_MASK = (1 << ORDER_TYPE_BITS) - 1
28
+ REDUCE_ONLY_MASK = (1 << REDUCE_ONLY_BITS) - 1
29
+ TRIGGER_TYPE_MASK = (1 << TRIGGER_TYPE_BITS) - 1
30
+ RESERVED_MASK = (1 << RESERVED_BITS) - 1
31
+ VALUE_MASK = (1 << VALUE_BITS) - 1
32
+
33
+ # Bit shift positions
34
+ VERSION_SHIFT = 0
35
+ ISOLATED_SHIFT = 8
36
+ ORDER_TYPE_SHIFT = 9
37
+ REDUCE_ONLY_SHIFT = 11
38
+ TRIGGER_TYPE_SHIFT = 12
39
+ RESERVED_SHIFT = 14
40
+ VALUE_SHIFT = 32
41
+
42
+
43
+ class OrderAppendixTriggerType(IntEnum):
44
+ """
45
+ Enumeration for trigger order types encoded in the appendix.
46
+ """
47
+
48
+ PRICE = 1
49
+ TWAP = 2
50
+ TWAP_CUSTOM_AMOUNTS = 3
51
+
52
+
53
+ class TWAPBitFields:
54
+ """Bit field definitions for TWAP value packing within the 96-bit value field."""
55
+
56
+ # Bit layout (MSB → LSB): | times (32 bits) | slippage_x6 (32 bits) | reserved (32 bits) |
57
+ TIMES_BITS = 32
58
+ SLIPPAGE_BITS = 32
59
+ RESERVED_BITS = 32
60
+
61
+ # Bit masks
62
+ TIMES_MASK = (1 << TIMES_BITS) - 1
63
+ SLIPPAGE_MASK = (1 << SLIPPAGE_BITS) - 1
64
+ RESERVED_MASK = (1 << RESERVED_BITS) - 1
65
+
66
+ # Bit shift positions (within the 96-bit value field)
67
+ RESERVED_SHIFT = 0
68
+ SLIPPAGE_SHIFT = 32
69
+ TIMES_SHIFT = 64
70
+
71
+ # Slippage scaling factor (6 decimal places)
72
+ SLIPPAGE_SCALE = 1_000_000
73
+
74
+
75
+ def pack_twap_appendix_value(times: int, slippage_frac: float) -> int:
76
+ """
77
+ Packs TWAP order fields into a 96-bit integer for the appendix.
78
+
79
+ Bit layout (MSB → LSB):
80
+ | times | slippage_x6 | reserved |
81
+ |-----------|-------------|----------|
82
+ | 95..64 | 63..32 | 31..0 |
83
+ | 32 bits | 32 bits | 32 bits |
84
+ """
85
+ slippage_x6 = int(slippage_frac * TWAPBitFields.SLIPPAGE_SCALE)
86
+ reserved = 0 # reserved 32-bit field (currently unused)
87
+
88
+ return (
89
+ ((times & TWAPBitFields.TIMES_MASK) << TWAPBitFields.TIMES_SHIFT)
90
+ | ((slippage_x6 & TWAPBitFields.SLIPPAGE_MASK) << TWAPBitFields.SLIPPAGE_SHIFT)
91
+ | ((reserved & TWAPBitFields.RESERVED_MASK) << TWAPBitFields.RESERVED_SHIFT)
92
+ )
93
+
94
+
95
+ def unpack_twap_appendix_value(value: int) -> tuple[int, float]:
96
+ """
97
+ Unpacks a 96-bit integer into TWAP order fields.
98
+
99
+ Args:
100
+ value (int): The 96-bit value to unpack.
101
+
102
+ Returns:
103
+ tuple[int, float]: Number of TWAP executions and slippage fraction.
104
+ """
105
+ times = (value >> TWAPBitFields.TIMES_SHIFT) & TWAPBitFields.TIMES_MASK
106
+ slippage_x6 = (value >> TWAPBitFields.SLIPPAGE_SHIFT) & TWAPBitFields.SLIPPAGE_MASK
107
+ slippage_frac = slippage_x6 / TWAPBitFields.SLIPPAGE_SCALE
108
+
109
+ return int(times), slippage_frac
110
+
111
+
112
+ def build_appendix(
113
+ order_type: OrderType,
114
+ isolated: bool = False,
115
+ reduce_only: bool = False,
116
+ trigger_type: Optional[OrderAppendixTriggerType] = None,
117
+ isolated_margin: Optional[int] = None,
118
+ twap_times: Optional[int] = None,
119
+ twap_slippage_frac: Optional[float] = None,
120
+ _version: Optional[int] = APPENDIX_VERSION,
121
+ ) -> int:
122
+ """
123
+ Builds an appendix value with the specified parameters.
124
+
125
+ Args:
126
+ order_type (OrderType): The order execution type. Required.
127
+ isolated (bool): Whether this order is for an isolated position. Defaults to False.
128
+ reduce_only (bool): Whether this is a reduce-only order. Defaults to False.
129
+ trigger_type (Optional[OrderAppendixTriggerType]): Trigger type. Defaults to None (no trigger).
130
+ isolated_margin (Optional[int]): Margin amount for isolated position if isolated is True.
131
+ twap_times (Optional[int]): Number of TWAP executions (required for TWAP trigger type).
132
+ twap_slippage_frac (Optional[float]): TWAP slippage fraction (required for TWAP trigger type).
133
+
134
+ Returns:
135
+ int: The built appendix value with version set to APPENDIX_VERSION.
136
+
137
+ Raises:
138
+ ValueError: If parameters are invalid or incompatible.
139
+ """
140
+ if isolated_margin is not None and not isolated:
141
+ raise ValueError("isolated_margin can only be set when isolated=True")
142
+
143
+ if (
144
+ isolated
145
+ and trigger_type is not None
146
+ and trigger_type
147
+ in [OrderAppendixTriggerType.TWAP, OrderAppendixTriggerType.TWAP_CUSTOM_AMOUNTS]
148
+ ):
149
+ raise ValueError("An order cannot be both isolated and a TWAP order")
150
+
151
+ if trigger_type is not None and trigger_type in [
152
+ OrderAppendixTriggerType.TWAP,
153
+ OrderAppendixTriggerType.TWAP_CUSTOM_AMOUNTS,
154
+ ]:
155
+ if twap_times is None or twap_slippage_frac is None:
156
+ raise ValueError(
157
+ "twap_times and twap_slippage_frac are required for TWAP orders"
158
+ )
159
+
160
+ appendix = 0
161
+
162
+ version = _version if _version is not None else APPENDIX_VERSION
163
+
164
+ # Version (bits 7..0)
165
+ appendix |= (
166
+ version & AppendixBitFields.VERSION_MASK
167
+ ) << AppendixBitFields.VERSION_SHIFT
168
+
169
+ # Isolated bit (bit 8)
170
+ if isolated:
171
+ appendix |= 1 << AppendixBitFields.ISOLATED_SHIFT
172
+
173
+ # Order type (bits 10..9) - 2 bits only
174
+ appendix |= (
175
+ int(order_type) & AppendixBitFields.ORDER_TYPE_MASK
176
+ ) << AppendixBitFields.ORDER_TYPE_SHIFT
177
+
178
+ # Reduce only bit (bit 11)
179
+ if reduce_only:
180
+ appendix |= 1 << AppendixBitFields.REDUCE_ONLY_SHIFT
181
+
182
+ # Trigger type (bits 13..12) - default to 0 if None
183
+ trigger_value = 0 if trigger_type is None else int(trigger_type)
184
+ appendix |= (
185
+ trigger_value & AppendixBitFields.TRIGGER_TYPE_MASK
186
+ ) << AppendixBitFields.TRIGGER_TYPE_SHIFT
187
+
188
+ # Handle upper bits (127..32) based on order type
189
+ if isolated and isolated_margin is not None:
190
+ # Isolated margin (bits 127..32)
191
+ appendix |= (
192
+ isolated_margin & AppendixBitFields.VALUE_MASK
193
+ ) << AppendixBitFields.VALUE_SHIFT
194
+ elif trigger_type is not None and trigger_type in [
195
+ OrderAppendixTriggerType.TWAP,
196
+ OrderAppendixTriggerType.TWAP_CUSTOM_AMOUNTS,
197
+ ]:
198
+ # TWAP value (bits 127..32) - 96 bits
199
+ # These are guaranteed to be non-None due to validation above
200
+ assert twap_times is not None
201
+ assert twap_slippage_frac is not None
202
+ twap_value = pack_twap_appendix_value(twap_times, twap_slippage_frac)
203
+ appendix |= (
204
+ twap_value & AppendixBitFields.VALUE_MASK
205
+ ) << AppendixBitFields.VALUE_SHIFT
206
+
207
+ return appendix
208
+
209
+
210
+ def gen_order_verifying_contract(product_id: int) -> str:
211
+ """
212
+ Generates the order verifying contract address based on the product ID.
213
+
214
+ Args:
215
+ product_id (int): The product ID for which to generate the verifying contract address.
216
+
217
+ Returns:
218
+ str: The generated order verifying contract address in hexadecimal format.
219
+ """
220
+ be_bytes = product_id.to_bytes(20, byteorder="big", signed=False)
221
+ return "0x" + be_bytes.hex()
222
+
223
+
224
+ def order_reduce_only(appendix: int) -> bool:
225
+ """
226
+ Checks if the order is reduce-only based on the appendix value.
227
+
228
+ Args:
229
+ appendix (int): The order appendix value.
230
+
231
+ Returns:
232
+ bool: True if the order is reduce-only, False otherwise.
233
+ """
234
+ return (
235
+ appendix >> AppendixBitFields.REDUCE_ONLY_SHIFT
236
+ & AppendixBitFields.REDUCE_ONLY_MASK
237
+ ) == 1
238
+
239
+
240
+ def order_is_trigger_order(appendix: int) -> bool:
241
+ """
242
+ Checks if the order is a trigger order based on the appendix value.
243
+
244
+ Args:
245
+ appendix (int): The order appendix value.
246
+
247
+ Returns:
248
+ bool: True if the order is a trigger order, False otherwise.
249
+ """
250
+ return (
251
+ appendix >> AppendixBitFields.TRIGGER_TYPE_SHIFT
252
+ & AppendixBitFields.TRIGGER_TYPE_MASK
253
+ ) > 0
254
+
255
+
256
+ def order_is_isolated(appendix: int) -> bool:
257
+ """
258
+ Checks if the order is for an isolated position based on the appendix value.
259
+
260
+ Args:
261
+ appendix (int): The order appendix value.
262
+
263
+ Returns:
264
+ bool: True if the order is for an isolated position, False otherwise.
265
+ """
266
+ return (
267
+ appendix >> AppendixBitFields.ISOLATED_SHIFT & AppendixBitFields.ISOLATED_MASK
268
+ ) == 1
269
+
270
+
271
+ def order_isolated_margin(appendix: int) -> Optional[int]:
272
+ """
273
+ Extracts the isolated margin amount from the appendix value.
274
+
275
+ Args:
276
+ appendix (int): The order appendix value.
277
+
278
+ Returns:
279
+ Optional[int]: The isolated margin amount if the order is isolated, None otherwise.
280
+ """
281
+ if order_is_isolated(appendix):
282
+ return (
283
+ appendix >> AppendixBitFields.VALUE_SHIFT
284
+ ) & AppendixBitFields.VALUE_MASK
285
+ return None
286
+
287
+
288
+ def order_version(appendix: int) -> int:
289
+ """
290
+ Extracts the version from the appendix value.
291
+
292
+ Args:
293
+ appendix (int): The order appendix value.
294
+
295
+ Returns:
296
+ int: The version number (bits 7..0).
297
+ """
298
+ return (
299
+ appendix >> AppendixBitFields.VERSION_SHIFT
300
+ ) & AppendixBitFields.VERSION_MASK
301
+
302
+
303
+ def order_trigger_type(appendix: int) -> Optional[OrderAppendixTriggerType]:
304
+ """
305
+ Extracts the trigger type from the appendix value.
306
+
307
+ Args:
308
+ appendix (int): The order appendix value.
309
+
310
+ Returns:
311
+ Optional[OrderAppendixTriggerType]: The trigger type, or None if no trigger is set.
312
+ """
313
+ trigger_bits = (
314
+ appendix >> AppendixBitFields.TRIGGER_TYPE_SHIFT
315
+ ) & AppendixBitFields.TRIGGER_TYPE_MASK
316
+ if trigger_bits == 0:
317
+ return None
318
+ return OrderAppendixTriggerType(trigger_bits)
319
+
320
+
321
+ def order_twap_data(appendix: int) -> Optional[tuple[int, float]]:
322
+ """
323
+ Extracts TWAP data from the appendix value if it's a TWAP order.
324
+
325
+ Args:
326
+ appendix (int): The order appendix value.
327
+
328
+ Returns:
329
+ Optional[tuple[int, float]]: Tuple of (times, slippage_frac) if TWAP, None otherwise.
330
+ """
331
+ trigger_type = order_trigger_type(appendix)
332
+ if trigger_type is not None and trigger_type in [
333
+ OrderAppendixTriggerType.TWAP,
334
+ OrderAppendixTriggerType.TWAP_CUSTOM_AMOUNTS,
335
+ ]:
336
+ twap_value = (
337
+ appendix >> AppendixBitFields.VALUE_SHIFT
338
+ ) & AppendixBitFields.VALUE_MASK
339
+ return unpack_twap_appendix_value(twap_value)
340
+ return None
341
+
342
+
343
+ def order_execution_type(appendix: int) -> OrderType:
344
+ """
345
+ Extracts the order execution type from the appendix value.
346
+
347
+ Args:
348
+ appendix (int): The order appendix value.
349
+
350
+ Returns:
351
+ OrderType: The order execution type.
352
+ """
353
+ order_type_bits = (
354
+ appendix >> AppendixBitFields.ORDER_TYPE_SHIFT
355
+ ) & AppendixBitFields.ORDER_TYPE_MASK
356
+ return OrderType(order_type_bits)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nado-protocol
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Nado Protocol SDK
5
5
  Keywords: nado protocol,nado sdk,nado protocol api
6
6
  Author: Jeury Mejia
@@ -54,6 +54,7 @@ from nado_protocol.engine_client.types.execute import (
54
54
  from nado_protocol.utils.expiration import OrderType, get_expiration_timestamp
55
55
  from nado_protocol.utils.math import to_pow_10, to_x18
56
56
  from nado_protocol.utils.nonce import gen_order_nonce
57
+ from nado_protocol.utils.order import build_appendix
57
58
  ```
58
59
 
59
60
  ### Create the NadoClient providing your private key:
@@ -95,8 +96,9 @@ order = OrderParams(
95
96
  ),
96
97
  priceX18=to_x18(20000),
97
98
  amount=to_pow_10(1, 17),
98
- expiration=get_expiration_timestamp(OrderType.POST_ONLY, int(time.time()) + 40),
99
+ expiration=get_expiration_timestamp(40),
99
100
  nonce=gen_order_nonce(),
101
+ appendix=build_appendix(order_type=OrderType.POST_ONLY)
100
102
  )
101
103
  res = client.market.place_order({"product_id": product_id, "order": order})
102
104
  print("order result:", res.json(indent=2))