nado-protocol 0.2.7__tar.gz → 0.3.0__tar.gz

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 (81) hide show
  1. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/PKG-INFO +1 -1
  2. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/engine_client/query.py +21 -2
  3. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/engine_client/types/__init__.py +1 -0
  4. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/engine_client/types/query.py +14 -1
  5. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/utils/margin_manager.py +113 -17
  6. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/pyproject.toml +1 -1
  7. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/README.md +0 -0
  8. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/__init__.py +0 -0
  9. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/client/__init__.py +0 -0
  10. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/client/apis/__init__.py +0 -0
  11. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/client/apis/base.py +0 -0
  12. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/client/apis/market/__init__.py +0 -0
  13. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/client/apis/market/execute.py +0 -0
  14. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/client/apis/market/query.py +0 -0
  15. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/client/apis/perp/__init__.py +0 -0
  16. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/client/apis/perp/query.py +0 -0
  17. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/client/apis/rewards/__init__.py +0 -0
  18. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/client/apis/rewards/execute.py +0 -0
  19. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/client/apis/rewards/query.py +0 -0
  20. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/client/apis/spot/__init__.py +0 -0
  21. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/client/apis/spot/base.py +0 -0
  22. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/client/apis/spot/execute.py +0 -0
  23. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/client/apis/spot/query.py +0 -0
  24. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/client/apis/subaccount/__init__.py +0 -0
  25. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/client/apis/subaccount/execute.py +0 -0
  26. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/client/apis/subaccount/query.py +0 -0
  27. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/client/context.py +0 -0
  28. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/contracts/__init__.py +0 -0
  29. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/contracts/abis/Endpoint.json +0 -0
  30. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/contracts/abis/FQuerier.json +0 -0
  31. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/contracts/abis/IAirdrop.json +0 -0
  32. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/contracts/abis/IClearinghouse.json +0 -0
  33. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/contracts/abis/IEndpoint.json +0 -0
  34. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/contracts/abis/IFoundationRewardsAirdrop.json +0 -0
  35. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/contracts/abis/IPerpEngine.json +0 -0
  36. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/contracts/abis/IProductEngine.json +0 -0
  37. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/contracts/abis/ISpotEngine.json +0 -0
  38. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/contracts/abis/IStaking.json +0 -0
  39. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/contracts/abis/MockERC20.json +0 -0
  40. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/contracts/deployments/deployment.mainnet.json +0 -0
  41. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/contracts/deployments/deployment.testing.json +0 -0
  42. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/contracts/deployments/deployment.testnet.json +0 -0
  43. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/contracts/eip712/__init__.py +0 -0
  44. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/contracts/eip712/domain.py +0 -0
  45. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/contracts/eip712/sign.py +0 -0
  46. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/contracts/eip712/types.py +0 -0
  47. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/contracts/loader.py +0 -0
  48. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/contracts/types.py +0 -0
  49. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/engine_client/__init__.py +0 -0
  50. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/engine_client/execute.py +0 -0
  51. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/engine_client/types/execute.py +0 -0
  52. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/engine_client/types/models.py +0 -0
  53. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/engine_client/types/stream.py +0 -0
  54. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/indexer_client/__init__.py +0 -0
  55. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/indexer_client/query.py +0 -0
  56. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/indexer_client/types/__init__.py +0 -0
  57. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/indexer_client/types/models.py +0 -0
  58. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/indexer_client/types/query.py +0 -0
  59. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/trigger_client/__init__.py +0 -0
  60. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/trigger_client/execute.py +0 -0
  61. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/trigger_client/query.py +0 -0
  62. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/trigger_client/types/__init__.py +0 -0
  63. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/trigger_client/types/execute.py +0 -0
  64. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/trigger_client/types/models.py +0 -0
  65. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/trigger_client/types/query.py +0 -0
  66. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/utils/__init__.py +0 -0
  67. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/utils/backend.py +0 -0
  68. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/utils/balance.py +0 -0
  69. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/utils/bytes32.py +0 -0
  70. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/utils/enum.py +0 -0
  71. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/utils/exceptions.py +0 -0
  72. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/utils/execute.py +0 -0
  73. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/utils/expiration.py +0 -0
  74. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/utils/interest.py +0 -0
  75. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/utils/math.py +0 -0
  76. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/utils/model.py +0 -0
  77. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/utils/nonce.py +0 -0
  78. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/utils/order.py +0 -0
  79. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/utils/subaccount.py +0 -0
  80. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/utils/time.py +0 -0
  81. {nado_protocol-0.2.7 → nado_protocol-0.3.0}/nado_protocol/utils/twap.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nado-protocol
3
- Version: 0.2.7
3
+ Version: 0.3.0
4
4
  Summary: Nado Protocol SDK
5
5
  Keywords: nado protocol,nado sdk,nado protocol api
6
6
  Author: Jeury Mejia
@@ -1,3 +1,4 @@
1
+ import json
1
2
  from typing import Optional
2
3
  import requests
3
4
 
@@ -182,7 +183,10 @@ class EngineQueryClient:
182
183
  )
183
184
 
184
185
  def get_subaccount_info(
185
- self, subaccount: str, txs: Optional[list[QuerySubaccountInfoTx]] = None
186
+ self,
187
+ subaccount: str,
188
+ txs: Optional[list[QuerySubaccountInfoTx]] = None,
189
+ pre_state: Optional[bool] = None,
186
190
  ) -> SubaccountInfoData:
187
191
  """
188
192
  Query the engine for the state of a subaccount, including balances.
@@ -193,11 +197,26 @@ class EngineQueryClient:
193
197
  txs (list[QuerySubaccountInfoTx], optional): You can optionally provide a list of txs, to get an estimated view
194
198
  of what the subaccount state would look like if the transactions were applied.
195
199
 
200
+ pre_state (bool, optional): When True and txs are provided, returns the subaccount state before the
201
+ transactions were applied in the pre_state field. Defaults to False.
202
+
196
203
  Returns:
197
204
  SubaccountInfoData: Information about the specified subaccount.
198
205
  """
206
+ txns_str = None
207
+ if txs is not None:
208
+ txns_str = json.dumps([tx.dict() for tx in txs])
209
+
210
+ pre_state_str = None
211
+ if pre_state is not None:
212
+ pre_state_str = str(pre_state).lower()
213
+
199
214
  return ensure_data_type(
200
- self.query(QuerySubaccountInfoParams(subaccount=subaccount, txs=txs)).data,
215
+ self.query(
216
+ QuerySubaccountInfoParams(
217
+ subaccount=subaccount, txns=txns_str, pre_state=pre_state_str
218
+ )
219
+ ).data,
201
220
  SubaccountInfoData,
202
221
  )
203
222
 
@@ -60,6 +60,7 @@ __all__ = [
60
60
  "ContractsData",
61
61
  "NoncesData",
62
62
  "OrderData",
63
+ "PreState",
63
64
  "SubaccountInfoData",
64
65
  "SubaccountOpenOrdersData",
65
66
  "MarketLiquidityData",
@@ -101,7 +101,8 @@ class QuerySubaccountInfoParams(NadoBaseModel):
101
101
 
102
102
  type = EngineQueryType.SUBACCOUNT_INFO.value
103
103
  subaccount: str
104
- txs: Optional[list[QuerySubaccountInfoTx]]
104
+ txns: Optional[str]
105
+ pre_state: Optional[str]
105
106
 
106
107
 
107
108
  class QuerySubaccountOpenOrdersParams(NadoBaseModel):
@@ -299,6 +300,17 @@ class OrderData(NadoBaseModel):
299
300
  placed_at: str
300
301
 
301
302
 
303
+ class PreState(NadoBaseModel):
304
+ """
305
+ Model for subaccount state before simulated transactions were applied.
306
+ """
307
+
308
+ healths: list[SubaccountHealth]
309
+ health_contributions: list[list[str]]
310
+ spot_balances: list[SpotProductBalance]
311
+ perp_balances: list[PerpProductBalance]
312
+
313
+
302
314
  class SubaccountInfoData(NadoBaseModel):
303
315
  """
304
316
  Model for detailed info about a subaccount, including balances.
@@ -314,6 +326,7 @@ class SubaccountInfoData(NadoBaseModel):
314
326
  perp_balances: list[PerpProductBalance]
315
327
  spot_products: list[SpotProduct]
316
328
  perp_products: list[PerpProduct]
329
+ pre_state: Optional[PreState]
317
330
 
318
331
  def parse_subaccount_balance(
319
332
  self, product_id: int
@@ -78,6 +78,8 @@ class CrossPositionMetrics(BaseModel):
78
78
  symbol: str
79
79
  position_size: Decimal
80
80
  notional_value: Decimal
81
+ avg_entry_price: Optional[Decimal] # Average entry price (requires indexer data)
82
+ est_liq_price: Optional[Decimal] # Estimated liquidation price
81
83
  est_pnl: Optional[Decimal] # Estimated PnL (requires indexer data)
82
84
  unsettled: Decimal # Unsettled quote (v_quote_balance)
83
85
  margin_used: Decimal
@@ -520,9 +522,9 @@ class MarginManager:
520
522
  # This represents the unrealized PnL
521
523
  unsettled = self.calculate_perp_balance_value(balance)
522
524
 
523
- # Calculate Est. PnL if indexer data is available
524
- # Formula: (amount × oracle_price) - netEntryUnrealized
525
- # where netEntryUnrealized excludes funding, fees, slippage
525
+ # Calculate metrics (requires indexer data for avg_entry_price and est_pnl)
526
+ avg_entry_price = self._calculate_avg_entry_price(balance)
527
+ est_liq_price = self._calculate_est_liq_price(balance)
526
528
  est_pnl = self._calculate_est_pnl(balance)
527
529
 
528
530
  return CrossPositionMetrics(
@@ -530,6 +532,8 @@ class MarginManager:
530
532
  symbol=f"Product_{balance.product_id}",
531
533
  position_size=balance.amount,
532
534
  notional_value=notional,
535
+ avg_entry_price=avg_entry_price,
536
+ est_liq_price=est_liq_price,
533
537
  est_pnl=est_pnl,
534
538
  unsettled=unsettled,
535
539
  margin_used=margin_used,
@@ -541,6 +545,24 @@ class MarginManager:
541
545
  short_weight_maintenance=balance.short_weight_maintenance,
542
546
  )
543
547
 
548
+ def _get_indexer_event_for_product(self, product_id: int) -> Optional[IndexerEvent]:
549
+ """
550
+ Get indexer event for a specific product (cross margin only).
551
+
552
+ Returns None if indexer data is not available or product not found.
553
+ """
554
+ if not self.indexer_events or product_id == self.QUOTE_PRODUCT_ID:
555
+ return None
556
+
557
+ for event in self.indexer_events:
558
+ if event.product_id != product_id:
559
+ continue
560
+ if event.isolated:
561
+ continue
562
+ return event
563
+
564
+ return None
565
+
544
566
  def _calculate_est_pnl(self, balance: BalanceWithProduct) -> Optional[Decimal]:
545
567
  """
546
568
  Calculate estimated PnL if indexer snapshot is available.
@@ -549,26 +571,90 @@ class MarginManager:
549
571
 
550
572
  Returns None if indexer data is not available.
551
573
  """
552
- if not self.indexer_events or balance.product_id == self.QUOTE_PRODUCT_ID:
574
+ event = self._get_indexer_event_for_product(balance.product_id)
575
+ if event is None:
553
576
  return None
554
577
 
555
- for event in self.indexer_events:
556
- if event.product_id != balance.product_id:
557
- continue
558
- if event.isolated:
559
- continue
578
+ try:
579
+ net_entry_int = int(event.net_entry_unrealized)
580
+ except (TypeError, ValueError):
581
+ return None
560
582
 
561
- try:
562
- net_entry_int = int(event.net_entry_unrealized)
563
- except (TypeError, ValueError):
564
- continue
583
+ net_entry_unrealized = Decimal(net_entry_int) / Decimal(10**18)
584
+ current_value = balance.amount * balance.oracle_price
585
+ return current_value - net_entry_unrealized
586
+
587
+ def _calculate_avg_entry_price(
588
+ self, balance: BalanceWithProduct
589
+ ) -> Optional[Decimal]:
590
+ """
591
+ Calculate average entry price if indexer snapshot is available.
565
592
 
566
- net_entry_unrealized = Decimal(net_entry_int) / Decimal(10**18)
593
+ Formula: abs(netEntryUnrealized / position_amount)
567
594
 
568
- current_value = balance.amount * balance.oracle_price
569
- return current_value - net_entry_unrealized
595
+ Returns None if indexer data is not available or position is zero.
596
+ """
597
+ if balance.amount == 0:
598
+ return None
570
599
 
571
- return None
600
+ event = self._get_indexer_event_for_product(balance.product_id)
601
+ if event is None:
602
+ return None
603
+
604
+ try:
605
+ net_entry_int = int(event.net_entry_unrealized)
606
+ except (TypeError, ValueError):
607
+ return None
608
+
609
+ net_entry_unrealized = Decimal(net_entry_int) / Decimal(10**18)
610
+ return abs(net_entry_unrealized / balance.amount)
611
+
612
+ def _calculate_est_liq_price(
613
+ self, balance: BalanceWithProduct
614
+ ) -> Optional[Decimal]:
615
+ """
616
+ Calculate estimated liquidation price.
617
+
618
+ Formula:
619
+ - If long: oracle_price - (maint_health / amount / long_weight)
620
+ - If short: oracle_price + (maint_health / abs(amount) * short_weight)
621
+
622
+ Returns None if:
623
+ - Position is zero
624
+ - Long liq price <= 0 (prevents -infinity)
625
+ - Short liq price >= 10x oracle price (prevents +infinity)
626
+ """
627
+ if balance.amount == 0:
628
+ return None
629
+
630
+ # Get maintenance health from subaccount info
631
+ maint_health = self._parse_health(self.subaccount_info.healths[1])
632
+
633
+ is_long = balance.amount > 0
634
+
635
+ if is_long:
636
+ # Long: oracle_price - (maint_health / amount / long_weight)
637
+ if balance.long_weight_maintenance == 0:
638
+ return None
639
+
640
+ liq_price = balance.oracle_price - (
641
+ maint_health / balance.amount / balance.long_weight_maintenance
642
+ )
643
+
644
+ # If liquidation price is 0 or less, return None (prevents -infinity)
645
+ if liq_price <= 0:
646
+ return None
647
+ else:
648
+ # Short: oracle_price + (maint_health / abs(amount) * short_weight)
649
+ liq_price = balance.oracle_price + (
650
+ maint_health / abs(balance.amount) * balance.short_weight_maintenance
651
+ )
652
+
653
+ # If liquidation price is 10x oracle price, return None (prevents +infinity)
654
+ if liq_price >= balance.oracle_price * 10:
655
+ return None
656
+
657
+ return liq_price
572
658
 
573
659
  def calculate_isolated_position_metrics(
574
660
  self, iso_pos: IsolatedPosition
@@ -783,6 +869,16 @@ def print_account_summary(summary: AccountSummary) -> None:
783
869
  print(f"│ Position: {cross_pos.position_size:,.3f}")
784
870
  print(f"│ Notional: ${cross_pos.notional_value:,.2f}")
785
871
 
872
+ if cross_pos.avg_entry_price is not None:
873
+ print(f"│ Avg Entry Price: ${cross_pos.avg_entry_price:,.2f}")
874
+ else:
875
+ print(f"│ Avg Entry Price: N/A")
876
+
877
+ if cross_pos.est_liq_price is not None:
878
+ print(f"│ Est. Liq Price: ${cross_pos.est_liq_price:,.2f}")
879
+ else:
880
+ print(f"│ Est. Liq Price: N/A")
881
+
786
882
  if cross_pos.est_pnl is not None:
787
883
  pnl_sign = "+" if cross_pos.est_pnl >= 0 else ""
788
884
  print(f"│ Est. PnL: {pnl_sign}${cross_pos.est_pnl:,.2f}")
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "nado-protocol"
3
- version = "0.2.7"
3
+ version = "0.3.0"
4
4
  description = "Nado Protocol SDK"
5
5
  authors = ["Jeury Mejia <jeury@inkfnd.com>"]
6
6
  homepage = "https://nado.xyz"
File without changes