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,377 @@
1
+ import os
2
+ from typing import Optional
3
+ from pydantic import BaseModel
4
+ from web3 import Web3
5
+ from web3.types import TxParams
6
+ from web3.contract import Contract
7
+ from web3.contract.contract import ContractFunction
8
+ from eth_account.signers.local import LocalAccount
9
+ from nado_protocol.contracts.loader import load_abi
10
+ from nado_protocol.contracts.types import DepositCollateralParams, NadoAbiName
11
+ from nado_protocol.utils.bytes32 import (
12
+ hex_to_bytes32,
13
+ str_to_hex,
14
+ subaccount_name_to_bytes12,
15
+ zero_address,
16
+ )
17
+ from nado_protocol.utils.exceptions import InvalidProductId
18
+ from nado_protocol.contracts.types import *
19
+
20
+
21
+ class NadoContractsContext(BaseModel):
22
+ """
23
+ Holds the context for various Nado contracts.
24
+
25
+ Attributes:
26
+ endpoint_addr (str): The endpoint address.
27
+
28
+ querier_addr (str): The querier address.
29
+
30
+ spot_engine_addr (Optional[str]): The spot engine address. This may be None.
31
+
32
+ perp_engine_addr (Optional[str]): The perp engine address. This may be None.
33
+
34
+ clearinghouse_addr (Optional[str]): The clearinghouse address. This may be None.
35
+
36
+ vrtx_airdrop_addr (Optional[str]): The VRTX airdrop address. This may be None.
37
+
38
+ vrtx_staking_addr (Optional[str]): The VRTX staking address. This may be None.
39
+
40
+ foundation_rewards_airdrop_addr (Optional[str]): The Foundation Rewards airdrop address of the corresponding chain (e.g: Arb airdrop for Arbitrum). This may be None.
41
+ """
42
+
43
+ network: Optional[NadoNetwork]
44
+ endpoint_addr: str
45
+ querier_addr: str
46
+ spot_engine_addr: Optional[str]
47
+ perp_engine_addr: Optional[str]
48
+ clearinghouse_addr: Optional[str]
49
+ vrtx_airdrop_addr: Optional[str]
50
+ vrtx_staking_addr: Optional[str]
51
+ foundation_rewards_airdrop_addr: Optional[str]
52
+
53
+
54
+ class NadoContracts:
55
+ """
56
+ Encapsulates the set of Nado contracts required for querying and executing.
57
+ """
58
+
59
+ w3: Web3
60
+ network: Optional[NadoNetwork]
61
+ contracts_context: NadoContractsContext
62
+ querier: Contract
63
+ endpoint: Contract
64
+ clearinghouse: Optional[Contract]
65
+ spot_engine: Optional[Contract]
66
+ perp_engine: Optional[Contract]
67
+ vrtx_airdrop: Optional[Contract]
68
+ vrtx_staking: Optional[Contract]
69
+ foundation_rewards_airdrop: Optional[Contract]
70
+
71
+ def __init__(self, node_url: str, contracts_context: NadoContractsContext):
72
+ """
73
+ Initialize a NadoContracts instance.
74
+
75
+ This will set up the Web3 instance and contract addresses for querying and executing the Nado contracts.
76
+ It will also load and parse the ABI for the given contracts.
77
+
78
+ Args:
79
+ node_url (str): The Ethereum node URL.
80
+
81
+ contracts_context (NadoContractsContext): The Nado contracts context, holding the relevant addresses.
82
+ """
83
+ self.network = contracts_context.network
84
+ self.w3 = Web3(Web3.HTTPProvider(node_url))
85
+
86
+ self.contracts_context = NadoContractsContext.parse_obj(contracts_context)
87
+ self.querier: Contract = self.w3.eth.contract(
88
+ address=contracts_context.querier_addr, abi=load_abi(NadoAbiName.FQUERIER) # type: ignore
89
+ )
90
+ self.endpoint: Contract = self.w3.eth.contract(
91
+ address=self.contracts_context.endpoint_addr,
92
+ abi=load_abi(NadoAbiName.ENDPOINT), # type: ignore
93
+ )
94
+ self.clearinghouse = None
95
+ self.spot_engine = None
96
+ self.perp_engine = None
97
+
98
+ if self.contracts_context.clearinghouse_addr:
99
+ self.clearinghouse: Contract = self.w3.eth.contract(
100
+ address=self.contracts_context.clearinghouse_addr,
101
+ abi=load_abi(NadoAbiName.ICLEARINGHOUSE), # type: ignore
102
+ )
103
+
104
+ if self.contracts_context.spot_engine_addr:
105
+ self.spot_engine: Contract = self.w3.eth.contract(
106
+ address=self.contracts_context.spot_engine_addr,
107
+ abi=load_abi(NadoAbiName.ISPOT_ENGINE), # type: ignore
108
+ )
109
+
110
+ if self.contracts_context.perp_engine_addr:
111
+ self.perp_engine: Contract = self.w3.eth.contract(
112
+ address=self.contracts_context.perp_engine_addr,
113
+ abi=load_abi(NadoAbiName.IPERP_ENGINE), # type: ignore
114
+ )
115
+
116
+ if self.contracts_context.vrtx_staking_addr:
117
+ self.vrtx_staking: Contract = self.w3.eth.contract(
118
+ address=self.contracts_context.vrtx_staking_addr,
119
+ abi=load_abi(NadoAbiName.ISTAKING), # type: ignore
120
+ )
121
+
122
+ if self.contracts_context.vrtx_airdrop_addr:
123
+ self.vrtx_airdrop: Contract = self.w3.eth.contract(
124
+ address=self.contracts_context.vrtx_airdrop_addr,
125
+ abi=load_abi(NadoAbiName.IVRTX_AIRDROP), # type: ignore
126
+ )
127
+
128
+ if self.contracts_context.foundation_rewards_airdrop_addr:
129
+ self.foundation_rewards_airdrop: Contract = self.w3.eth.contract(
130
+ address=self.contracts_context.foundation_rewards_airdrop_addr,
131
+ abi=load_abi(NadoAbiName.IFOUNDATION_REWARDS_AIRDROP), # type: ignore
132
+ )
133
+
134
+ def deposit_collateral(
135
+ self, params: DepositCollateralParams, signer: LocalAccount
136
+ ) -> str:
137
+ """
138
+ Deposits a specified amount of collateral into a spot product.
139
+
140
+ Args:
141
+ params (DepositCollateralParams): The parameters for depositing collateral.
142
+
143
+ signer (LocalAccount): The account that will sign the deposit transaction.
144
+
145
+ Returns:
146
+ str: The transaction hash of the deposit operation.
147
+ """
148
+ params = DepositCollateralParams.parse_obj(params)
149
+ if params.referral_code is not None and params.referral_code.strip():
150
+ return self.execute(
151
+ self.endpoint.functions.depositCollateralWithReferral(
152
+ subaccount_name_to_bytes12(params.subaccount_name),
153
+ params.product_id,
154
+ params.amount,
155
+ params.referral_code,
156
+ ),
157
+ signer,
158
+ )
159
+ else:
160
+ return self.execute(
161
+ self.endpoint.functions.depositCollateral(
162
+ subaccount_name_to_bytes12(params.subaccount_name),
163
+ params.product_id,
164
+ params.amount,
165
+ ),
166
+ signer,
167
+ )
168
+
169
+ def approve_allowance(
170
+ self,
171
+ erc20: Contract,
172
+ amount: int,
173
+ signer: LocalAccount,
174
+ to: Optional[str] = None,
175
+ ):
176
+ """
177
+ Approves a specified amount of allowance for the ERC20 token contract.
178
+
179
+ Args:
180
+ erc20 (Contract): The ERC20 token contract.
181
+
182
+ amount (int): The amount of the ERC20 token to be approved.
183
+
184
+ signer (LocalAccount): The account that will sign the approval transaction.
185
+
186
+ to (Optional[str]): When specified, approves allowance to the provided contract address, otherwise it approves it to Nado's Endpoint.
187
+
188
+ Returns:
189
+ str: The transaction hash of the approval operation.
190
+ """
191
+ to = to or self.endpoint.address
192
+ return self.execute(erc20.functions.approve(to, amount), signer)
193
+
194
+ def claim_vrtx(
195
+ self,
196
+ epoch: int,
197
+ amount_to_claim: int,
198
+ total_claimable_amount: int,
199
+ merkle_proof: list[str],
200
+ signer: LocalAccount,
201
+ ) -> str:
202
+ assert self.vrtx_airdrop is not None
203
+ return self.execute(
204
+ self.vrtx_airdrop.functions.claim(
205
+ epoch, amount_to_claim, total_claimable_amount, merkle_proof
206
+ ),
207
+ signer,
208
+ )
209
+
210
+ def claim_and_stake_vrtx(
211
+ self,
212
+ epoch: int,
213
+ amount_to_claim: int,
214
+ total_claimable_amount: int,
215
+ merkle_proof: list[str],
216
+ signer: LocalAccount,
217
+ ) -> str:
218
+ assert self.vrtx_airdrop is not None
219
+ return self.execute(
220
+ self.vrtx_airdrop.functions.claimAndStake(
221
+ epoch, amount_to_claim, total_claimable_amount, merkle_proof
222
+ ),
223
+ signer,
224
+ )
225
+
226
+ def stake_vrtx(
227
+ self,
228
+ amount: int,
229
+ signer: LocalAccount,
230
+ ) -> str:
231
+ assert self.vrtx_staking is not None
232
+ return self.execute(
233
+ self.vrtx_staking.functions.stake(amount),
234
+ signer,
235
+ )
236
+
237
+ def unstake_vrtx(
238
+ self,
239
+ amount: int,
240
+ signer: LocalAccount,
241
+ ) -> str:
242
+ assert self.vrtx_staking is not None
243
+ return self.execute(
244
+ self.vrtx_staking.functions.withdraw(amount),
245
+ signer,
246
+ )
247
+
248
+ def withdraw_unstaked_vrtx(
249
+ self,
250
+ signer: LocalAccount,
251
+ ) -> str:
252
+ assert self.vrtx_staking is not None
253
+ return self.execute(
254
+ self.vrtx_staking.functions.claimVrtx(),
255
+ signer,
256
+ )
257
+
258
+ def claim_usdc_rewards(
259
+ self,
260
+ signer: LocalAccount,
261
+ ) -> str:
262
+ assert self.vrtx_staking is not None
263
+ return self.execute(
264
+ self.vrtx_staking.functions.claimUsdc(),
265
+ signer,
266
+ )
267
+
268
+ def claim_and_stake_usdc_rewards(
269
+ self,
270
+ signer: LocalAccount,
271
+ ) -> str:
272
+ assert self.vrtx_staking is not None
273
+ return self.execute(
274
+ self.vrtx_staking.functions.claimUsdcAndStake(),
275
+ signer,
276
+ )
277
+
278
+ def claim_foundation_rewards(
279
+ self,
280
+ claim_proofs: list[ClaimFoundationRewardsProofStruct],
281
+ signer: LocalAccount,
282
+ ) -> str:
283
+ assert self.foundation_rewards_airdrop is not None
284
+ proofs = [proof.dict() for proof in claim_proofs]
285
+ return self.execute(
286
+ self.foundation_rewards_airdrop.functions.claim(proofs), signer
287
+ )
288
+
289
+ def _mint_mock_erc20(
290
+ self, erc20: Contract, amount: int, signer: LocalAccount
291
+ ) -> str:
292
+ """
293
+ Mints a specified amount of mock ERC20 tokens for testing purposes.
294
+
295
+ Args:
296
+ erc20 (Contract): The contract instance of the ERC20 token to be minted.
297
+
298
+ amount (int): The amount of tokens to mint.
299
+
300
+ signer (LocalAccount): The account that will sign the minting transaction.
301
+
302
+ Returns:
303
+ str: The transaction hash of the mint operation.
304
+ """
305
+ return self.execute(erc20.functions.mint(signer.address, amount), signer)
306
+
307
+ def get_token_contract_for_product(self, product_id: int) -> Contract:
308
+ """
309
+ Returns the ERC20 token contract for a given product.
310
+
311
+ Args:
312
+ product_id (int): The ID of the product for which to get the ERC20 token contract.
313
+
314
+ Returns:
315
+ Contract: The ERC20 token contract for the specified product.
316
+
317
+ Raises:
318
+ InvalidProductId: If the provided product ID is not valid.
319
+ """
320
+ if self.spot_engine is None:
321
+ raise Exception("SpotEngine contract not initialized")
322
+ product_config = self.spot_engine.functions.getConfig(product_id).call()
323
+ token = product_config[0]
324
+ if token == f"0x{zero_address().hex()}":
325
+ raise InvalidProductId(f"Invalid product id provided: {product_id}")
326
+ return self.w3.eth.contract(
327
+ address=token,
328
+ abi=load_abi(NadoAbiName.MOCK_ERC20),
329
+ )
330
+
331
+ def execute(self, func: ContractFunction, signer: LocalAccount) -> str:
332
+ """
333
+ Executes a smart contract function.
334
+
335
+ This method builds a transaction for a given contract function, signs the transaction with the provided signer's private key,
336
+ sends the raw signed transaction to the network, and waits for the transaction to be mined.
337
+
338
+ Args:
339
+ func (ContractFunction): The contract function to be executed.
340
+
341
+ signer (LocalAccount): The local account object that will sign the transaction. It should contain the private key.
342
+
343
+ Returns:
344
+ str: The hexadecimal representation of the transaction hash.
345
+
346
+ Raises:
347
+ ValueError: If the transaction is invalid, the method will not catch the error.
348
+ TimeExhausted: If the transaction receipt isn't available within the timeout limit set by the Web3 provider.
349
+ """
350
+ tx = func.build_transaction(self._build_tx_params(signer))
351
+ signed_tx = self.w3.eth.account.sign_transaction(tx, private_key=signer.key)
352
+ signed_tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction)
353
+ self.w3.eth.wait_for_transaction_receipt(signed_tx_hash)
354
+ return signed_tx_hash.hex()
355
+
356
+ def _build_tx_params(self, signer: LocalAccount) -> TxParams:
357
+ tx_params: TxParams = {
358
+ "from": signer.address,
359
+ "nonce": self.w3.eth.get_transaction_count(signer.address),
360
+ }
361
+ needs_gas_price = self.network is not None and self.network.value in [
362
+ NadoNetwork.HARDHAT.value
363
+ ]
364
+ if needs_gas_price or os.getenv("CLIENT_MODE") in ["devnet"]:
365
+ tx_params["gasPrice"] = self.w3.eth.gas_price
366
+ return tx_params
367
+
368
+
369
+ __all__ = [
370
+ "NadoContractsContext",
371
+ "NadoContracts",
372
+ "DepositCollateralParams",
373
+ "NadoExecuteType",
374
+ "NadoNetwork",
375
+ "NadoAbiName",
376
+ "NadoDeployment",
377
+ ]