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.
- nado_protocol/client/__init__.py +11 -2
- nado_protocol/client/apis/market/execute.py +10 -26
- nado_protocol/client/apis/market/query.py +2 -2
- nado_protocol/client/apis/rewards/execute.py +27 -27
- nado_protocol/client/apis/rewards/query.py +5 -4
- nado_protocol/client/apis/subaccount/query.py +1 -1
- nado_protocol/client/context.py +0 -2
- nado_protocol/contracts/__init__.py +41 -33
- nado_protocol/contracts/abis/Endpoint.json +151 -228
- nado_protocol/contracts/abis/FQuerier.json +91 -508
- nado_protocol/contracts/abis/IAirdrop.json +76 -0
- nado_protocol/contracts/abis/IClearinghouse.json +277 -390
- nado_protocol/contracts/abis/IEndpoint.json +42 -80
- nado_protocol/contracts/abis/IPerpEngine.json +69 -422
- nado_protocol/contracts/abis/IProductEngine.json +87 -205
- nado_protocol/contracts/abis/ISpotEngine.json +173 -362
- nado_protocol/contracts/abis/MockERC20.json +1 -1
- nado_protocol/contracts/deployments/{deployment.test.json → deployment.testing.json} +2 -5
- nado_protocol/contracts/deployments/deployment.testnet.json +15 -0
- nado_protocol/contracts/eip712/types.py +15 -20
- nado_protocol/contracts/types.py +15 -13
- nado_protocol/engine_client/execute.py +18 -39
- nado_protocol/engine_client/query.py +1 -1
- nado_protocol/engine_client/types/__init__.py +4 -8
- nado_protocol/engine_client/types/execute.py +37 -103
- nado_protocol/engine_client/types/models.py +3 -59
- nado_protocol/engine_client/types/query.py +3 -6
- nado_protocol/indexer_client/query.py +4 -9
- nado_protocol/indexer_client/types/__init__.py +4 -5
- nado_protocol/indexer_client/types/models.py +16 -23
- nado_protocol/indexer_client/types/query.py +12 -11
- nado_protocol/trigger_client/execute.py +1 -1
- nado_protocol/trigger_client/types/execute.py +3 -1
- nado_protocol/utils/__init__.py +18 -1
- nado_protocol/utils/backend.py +5 -2
- nado_protocol/utils/exceptions.py +3 -3
- nado_protocol/utils/execute.py +26 -67
- nado_protocol/utils/expiration.py +7 -28
- nado_protocol/utils/nonce.py +0 -4
- nado_protocol/utils/order.py +356 -0
- {nado_protocol-0.1.0.dist-info → nado_protocol-0.1.2.dist-info}/METADATA +4 -2
- nado_protocol-0.1.2.dist-info/RECORD +78 -0
- {nado_protocol-0.1.0.dist-info → nado_protocol-0.1.2.dist-info}/entry_points.txt +0 -1
- nado_protocol/contracts/abis/IERC20.json +0 -185
- nado_protocol/contracts/abis/IOffchainBook.json +0 -536
- nado_protocol/contracts/abis/IVrtxAirdrop.json +0 -138
- nado_protocol-0.1.0.dist-info/RECORD +0 -78
- {nado_protocol-0.1.0.dist-info → nado_protocol-0.1.2.dist-info}/WHEEL +0 -0
nado_protocol/utils/execute.py
CHANGED
|
@@ -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
|
|
166
|
+
def order_verifying_contract(self, product_id: int) -> str:
|
|
195
167
|
"""
|
|
196
|
-
|
|
168
|
+
Returns the `place_order` verifying contract address for a given product.
|
|
197
169
|
|
|
198
|
-
|
|
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
|
-
|
|
201
|
-
|
|
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
|
-
|
|
207
|
-
|
|
178
|
+
Example:
|
|
179
|
+
product_id = 18 → 0x0000000000000000000000000000000000000012
|
|
208
180
|
"""
|
|
209
|
-
|
|
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
|
|
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(
|
|
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
|
|
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.
|
|
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=
|
|
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.
|
|
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
|
-
|
|
14
|
+
Returns a timestamp that is seconds_from_now in the future.
|
|
16
15
|
|
|
17
|
-
|
|
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
|
-
|
|
19
|
+
seconds_from_now (int): Number of seconds from now for expiration.
|
|
39
20
|
|
|
40
21
|
Returns:
|
|
41
|
-
|
|
22
|
+
int: The expiration timestamp.
|
|
42
23
|
"""
|
|
43
|
-
|
|
44
|
-
exp_timestamp = expiration & ((1 << 62) - 1)
|
|
45
|
-
return order_type, exp_timestamp
|
|
24
|
+
return int(time.time()) + seconds_from_now
|
nado_protocol/utils/nonce.py
CHANGED
|
@@ -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.
|
|
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(
|
|
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))
|