charli3_dendrite 1.4.12.dev1__tar.gz → 1.4.13.dev0__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 (42) hide show
  1. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/PKG-INFO +1 -1
  2. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/pyproject.toml +2 -2
  3. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/__init__.py +1 -0
  4. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dexs/ob/saturnswap.py +126 -48
  5. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/LICENSE +0 -0
  6. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/README.md +0 -0
  7. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/backend/__init__.py +0 -0
  8. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/backend/backend_base.py +0 -0
  9. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/backend/blockfrost/__init__.py +0 -0
  10. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/backend/blockfrost/models.py +0 -0
  11. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/backend/dbsync/__init__.py +0 -0
  12. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/backend/dbsync/models.py +0 -0
  13. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/backend/ogmios_kupo/__init__.py +0 -0
  14. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/backend/ogmios_kupo/models.py +0 -0
  15. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/backend/utils.py +0 -0
  16. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dataclasses/__init__.py +0 -0
  17. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dataclasses/datums.py +0 -0
  18. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dataclasses/models.py +0 -0
  19. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dexs/__init__.py +0 -0
  20. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dexs/amm/__init__.py +0 -0
  21. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dexs/amm/amm_base.py +0 -0
  22. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dexs/amm/amm_types.py +0 -0
  23. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dexs/amm/cswap.py +0 -0
  24. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dexs/amm/dano.py +0 -0
  25. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dexs/amm/minswap.py +0 -0
  26. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dexs/amm/muesli.py +0 -0
  27. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dexs/amm/spectrum.py +0 -0
  28. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dexs/amm/splash.py +0 -0
  29. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dexs/amm/sundae.py +0 -0
  30. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dexs/amm/vyfi.py +0 -0
  31. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dexs/amm/wingriders.py +0 -0
  32. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dexs/core/__init__.py +0 -0
  33. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dexs/core/base.py +0 -0
  34. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dexs/core/errors.py +0 -0
  35. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dexs/ob/__init__.py +0 -0
  36. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dexs/ob/axo.py +0 -0
  37. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dexs/ob/cardanoswaps.py +0 -0
  38. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dexs/ob/chadswap.py +0 -0
  39. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dexs/ob/djed.py +0 -0
  40. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dexs/ob/geniusyield.py +0 -0
  41. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/dexs/ob/ob_base.py +0 -0
  42. {charli3_dendrite-1.4.12.dev1 → charli3_dendrite-1.4.13.dev0}/src/charli3_dendrite/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: charli3_dendrite
3
- Version: 1.4.12.dev1
3
+ Version: 1.4.13.dev0
4
4
  Summary:
5
5
  License-File: LICENSE
6
6
  Author: Elder Millenial
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "charli3_dendrite"
3
- version = "1.4.12-dev1"
3
+ version = "1.4.13-dev0"
4
4
  description = ""
5
5
  authors = ["Elder Millenial <eldermillenial@protonmail.com>"]
6
6
  readme = "README.md"
@@ -50,7 +50,7 @@ module = ["pycardano", "pycardano.*", "dotenv", "nox", "uplc", "uplc.*"]
50
50
  ignore_missing_imports = true
51
51
 
52
52
  [tool.bumpversion]
53
- current_version = "1.4.12-dev1"
53
+ current_version = "1.4.13-dev0"
54
54
  parse = """(?x)
55
55
  (?P<major>\\d+)\\.
56
56
  (?P<minor>\\d+)\\.
@@ -31,6 +31,7 @@ from charli3_dendrite.dexs.ob.djed import DjedOrderBook
31
31
  from charli3_dendrite.dexs.ob.djed import ShenOrderBook
32
32
  from charli3_dendrite.dexs.ob.geniusyield import GeniusYieldOrderBook
33
33
  from charli3_dendrite.dexs.ob.geniusyield import GeniusYieldOrderState
34
+ from charli3_dendrite.dexs.ob.saturnswap import SaturnSwapLegacyOrderState
34
35
  from charli3_dendrite.dexs.ob.saturnswap import SaturnSwapOrderBook
35
36
  from charli3_dendrite.dexs.ob.saturnswap import SaturnSwapOrderState
36
37
  from charli3_dendrite.utility import Assets
@@ -9,6 +9,7 @@ from dataclasses import dataclass
9
9
  from decimal import Decimal
10
10
  from functools import lru_cache
11
11
  from typing import Any
12
+ from typing import ClassVar
12
13
  from typing import Union
13
14
 
14
15
  from pycardano import Address
@@ -40,12 +41,31 @@ from charli3_dendrite.dexs.ob.ob_base import OrderBookOrder
40
41
  from charli3_dendrite.dexs.ob.ob_base import SellOrderBook
41
42
  from charli3_dendrite.utility import asset_to_value
42
43
 
43
- SATURNSWAP_TAKER_FEE_BPS = 400
44
-
45
- # SaturnSwap charges a 4% on-chain taker fee on every fill that is NOT co-signed
46
- # by its authorized hot-key; co-signed (protocol-routed) fills are exempt via the
47
- # validator's ``fees_paid_or_auth``. When the protocol shares that hot-key, set
48
- # this env var to the signing key and dendrite builds the fee-free authorized fill.
44
+ # SaturnSwap charges an on-chain taker fee on every fill that is NOT co-signed by
45
+ # its authorized hot-key; co-signed (protocol-routed) fills are exempt via the
46
+ # validator's ``fees_paid_or_auth``. In 2026-06 the protocol redeployed the swap
47
+ # contract with the non-auth fee lowered 4% -> 1% (validator ``fee_percent``
48
+ # 400 -> 100); only that integer changed, so the contract kept the same stake
49
+ # credential but gained a new script hash/address. Dendrite tracks both the live
50
+ # 1% contract and the legacy 4% contract so in-flight legacy orders still fill at
51
+ # the correct rate (see SaturnSwapOrderState / SaturnSwapLegacyOrderState).
52
+ SATURNSWAP_TAKER_FEE_BPS = 100
53
+ SATURNSWAP_LEGACY_TAKER_FEE_BPS = 400
54
+
55
+ # Live 1% contract; script hash
56
+ # 73990b71041ceade6f867617f6ce9f187ab710ea2bf1ff8db7d0292f.
57
+ SATURNSWAP_ORDER_ADDRESS = (
58
+ "addr1z9eejzm3qsww4hn0semp0akwnuv84dcsag4lrludklgzjt"
59
+ "675jq4yvpskgayj55xegdp30g5rfynax66r8vgn9fldndsrfnae7"
60
+ )
61
+ # Legacy 4% contract (pre-2026-06 deployment).
62
+ SATURNSWAP_LEGACY_ORDER_ADDRESS = (
63
+ "addr1zyd0sj57d9lpu7cy9g9qdurpazqc9l4eaxk6j59nd2gkh4"
64
+ "275jq4yvpskgayj55xegdp30g5rfynax66r8vgn9fldndsqzf5tn"
65
+ )
66
+
67
+ # When the protocol shares its authorize hot-key, set this env var to the signing
68
+ # key and dendrite builds the fee-free authorized fill on either contract.
49
69
  SATURNSWAP_AUTHORIZE_KEY_ENV = "SATURNSWAP_AUTHORIZE_KEY"
50
70
 
51
71
 
@@ -63,9 +83,10 @@ def saturnswap_authorize_signing_key() -> PaymentSigningKey | None:
63
83
 
64
84
  Set ``SATURNSWAP_AUTHORIZE_KEY`` (the hot-key as ``sk.to_cbor_hex()`` or raw
65
85
  32-byte hex) to build fee-free authorized fills. ``swap_utxo`` then drops the
66
- 4% fee output and adds the key's hash as a required signer. The CALLER must
86
+ taker-fee output and adds the key's hash as a required signer. The CALLER must
67
87
  add this key when signing the transaction (e.g. ``build_and_sign([…, key])``).
68
- Returns ``None`` (default behaviour, 4% fee) when the env var is unset.
88
+ Returns ``None`` (default behaviour, on-chain taker fee) when the env var is
89
+ unset.
69
90
  """
70
91
  raw = os.environ.get(SATURNSWAP_AUTHORIZE_KEY_ENV)
71
92
  return _load_authorize_signing_key(raw) if raw else None
@@ -253,8 +274,14 @@ class SaturnSwapCancelAction(PlutusData):
253
274
  return
254
275
 
255
276
 
256
- class SaturnSwapOrderState(AbstractOrderState):
257
- """SaturnSwap order state for individual orders."""
277
+ class _SaturnSwapOrderStateBase(AbstractOrderState):
278
+ """Shared SaturnSwap order-state logic for both contract versions.
279
+
280
+ Concrete subclasses pin the contract-specific bits — the order script
281
+ ``order_selector()`` address and the non-auth ``TAKER_FEE_BPS`` — and supply
282
+ ``dex()``. The base intentionally leaves ``dex()`` unimplemented so the
283
+ subclass-discovery walk skips it and only registers the concrete leaves.
284
+ """
258
285
 
259
286
  tx_hash: str
260
287
  tx_index: int
@@ -262,29 +289,17 @@ class SaturnSwapOrderState(AbstractOrderState):
262
289
  datum_hash: str
263
290
  inactive: bool = False
264
291
 
292
+ # Non-auth taker fee in basis points; set by each concrete contract class.
293
+ TAKER_FEE_BPS: ClassVar[int]
294
+
265
295
  _batcher: Assets = Assets(lovelace=0)
266
296
  _datum_parsed: PlutusData | None = None
267
297
 
268
- @classmethod
269
- def dex(cls) -> str:
270
- """Return the DEX name."""
271
- return "SaturnSwap"
272
-
273
298
  @classmethod
274
299
  def dex_policy(cls) -> list[str] | None:
275
300
  """SaturnSwap uses parameterized scripts, no global dex NFT."""
276
301
  return None
277
302
 
278
- @classmethod
279
- def order_selector(cls) -> list[str]:
280
- """Return order script addresses."""
281
- return [
282
- (
283
- "addr1zyd0sj57d9lpu7cy9g9qdurpazqc9l4eaxk6j59nd2gkh4"
284
- "275jq4yvpskgayj55xegdp30g5rfynax66r8vgn9fldndsqzf5tn"
285
- ),
286
- ]
287
-
288
303
  @classmethod
289
304
  def pool_selector(cls) -> PoolSelector:
290
305
  """Return pool selector for order UTxOs."""
@@ -360,11 +375,12 @@ class SaturnSwapOrderState(AbstractOrderState):
360
375
  def volume_fee(self) -> int:
361
376
  """Taker fee in basis points (0 when the authorize hot-key co-signs).
362
377
 
363
- SaturnSwap waives the 4% on-chain taker fee for fills co-signed by its
378
+ SaturnSwap waives the on-chain taker fee for fills co-signed by its
364
379
  authorized hot-key, so the fee is 0 when ``SATURNSWAP_AUTHORIZE_KEY`` is
365
- configured (see :func:`saturnswap_authorize_signing_key`).
380
+ configured (see :func:`saturnswap_authorize_signing_key`); otherwise it
381
+ is the contract-specific ``TAKER_FEE_BPS``.
366
382
  """
367
- return 0 if _authorize_vkey_hash() is not None else SATURNSWAP_TAKER_FEE_BPS
383
+ return 0 if _authorize_vkey_hash() is not None else self.TAKER_FEE_BPS
368
384
 
369
385
  @property
370
386
  def swap_forward(self) -> bool:
@@ -450,7 +466,7 @@ class SaturnSwapOrderState(AbstractOrderState):
450
466
  new_amount_sell: int,
451
467
  payment_datum: "SaturnSwapPaymentDatum",
452
468
  ) -> None:
453
- """Add the 4% taker-fee output, or require the authorize hot-key signature.
469
+ """Add the taker-fee output, or require the authorize hot-key signature.
454
470
 
455
471
  SaturnSwap exempts fills co-signed by its authorized hot-key from the
456
472
  on-chain fee (``fees_paid_or_auth``). When ``SATURNSWAP_AUTHORIZE_KEY`` is
@@ -566,7 +582,7 @@ class SaturnSwapOrderState(AbstractOrderState):
566
582
  )
567
583
  tx_builder.add_output(owner_output)
568
584
 
569
- # Fee output (4%) for unauthorized fills, or require the hot-key
585
+ # Taker-fee output for unauthorized fills, or require the hot-key
570
586
  # signature for authorized (fee-free) fills.
571
587
  self._add_fee_or_authorize(
572
588
  tx_builder,
@@ -643,8 +659,60 @@ class SaturnSwapOrderState(AbstractOrderState):
643
659
  return None, self.order_datum
644
660
 
645
661
 
662
+ class SaturnSwapOrderState(_SaturnSwapOrderStateBase):
663
+ """Live SaturnSwap order state — current 1% taker-fee contract.
664
+
665
+ Orders resting at :data:`SATURNSWAP_ORDER_ADDRESS` (script hash
666
+ ``73990b71…``). New maker orders are created here.
667
+ """
668
+
669
+ TAKER_FEE_BPS: ClassVar[int] = SATURNSWAP_TAKER_FEE_BPS
670
+
671
+ @classmethod
672
+ def dex(cls) -> str:
673
+ """Return the DEX name."""
674
+ return "SaturnSwap"
675
+
676
+ @classmethod
677
+ def order_selector(cls) -> list[str]:
678
+ """Return order script addresses (live 1% contract)."""
679
+ return [SATURNSWAP_ORDER_ADDRESS]
680
+
681
+
682
+ class SaturnSwapLegacyOrderState(_SaturnSwapOrderStateBase):
683
+ """Legacy SaturnSwap order state — pre-2026-06 4% taker-fee contract.
684
+
685
+ Orders resting at :data:`SATURNSWAP_LEGACY_ORDER_ADDRESS`. Kept so in-flight
686
+ orders on the old contract still fill at their correct 4% fee.
687
+ """
688
+
689
+ TAKER_FEE_BPS: ClassVar[int] = SATURNSWAP_LEGACY_TAKER_FEE_BPS
690
+
691
+ @classmethod
692
+ def dex(cls) -> str:
693
+ """Return the DEX name."""
694
+ return "SaturnSwap"
695
+
696
+ @classmethod
697
+ def order_selector(cls) -> list[str]:
698
+ """Return order script addresses (legacy 4% contract)."""
699
+ return [SATURNSWAP_LEGACY_ORDER_ADDRESS]
700
+
701
+
702
+ # Concrete SaturnSwap contracts, newest first. The order book walks all of these
703
+ # so both the live 1% contract and the legacy 4% contract are aggregated.
704
+ _SATURNSWAP_ORDER_STATE_CLASSES: list[type[_SaturnSwapOrderStateBase]] = [
705
+ SaturnSwapOrderState,
706
+ SaturnSwapLegacyOrderState,
707
+ ]
708
+
709
+
646
710
  class SaturnSwapOrderBook(AbstractOrderBookState):
647
- """SaturnSwap order book aggregating individual orders."""
711
+ """SaturnSwap order book aggregating individual orders.
712
+
713
+ Aggregates orders from every contract version in
714
+ :data:`_SATURNSWAP_ORDER_STATE_CLASSES` (live 1% + legacy 4%).
715
+ """
648
716
 
649
717
  _deposit: Assets = Assets(lovelace=0)
650
718
 
@@ -652,25 +720,31 @@ class SaturnSwapOrderBook(AbstractOrderBookState):
652
720
  def get_book(
653
721
  cls,
654
722
  assets: Assets,
655
- orders: list[SaturnSwapOrderState] | None = None,
723
+ orders: list[_SaturnSwapOrderStateBase] | None = None,
656
724
  ) -> "SaturnSwapOrderBook":
657
- """Build an order book from provided orders or backend UTxOs."""
725
+ """Build an order book from provided orders or backend UTxOs.
726
+
727
+ When ``orders`` is not supplied, UTxOs are fetched for every contract
728
+ version (live 1% + legacy 4%) and validated with the matching order-state
729
+ class so each order carries the correct taker fee.
730
+ """
658
731
  min_pair_assets = 2
659
732
  utxo_limit = 10_000
660
733
  if orders is None:
661
- selector = SaturnSwapOrderState.pool_selector()
662
- result = get_backend().get_pool_utxos(
663
- limit=utxo_limit,
664
- historical=False,
665
- **selector.model_dump(),
666
- )
667
734
  # Skip invalid/non-order datums that fail deserialization
668
735
  orders = []
669
- for r in result:
670
- try:
671
- orders.append(SaturnSwapOrderState.model_validate(r.model_dump()))
672
- except NotAPoolError:
673
- continue
736
+ for state_cls in _SATURNSWAP_ORDER_STATE_CLASSES:
737
+ selector = state_cls.pool_selector()
738
+ result = get_backend().get_pool_utxos(
739
+ limit=utxo_limit,
740
+ historical=False,
741
+ **selector.model_dump(),
742
+ )
743
+ for r in result:
744
+ try:
745
+ orders.append(state_cls.model_validate(r.model_dump()))
746
+ except NotAPoolError:
747
+ continue
674
748
 
675
749
  buy_orders: list[OrderBookOrder] = []
676
750
  sell_orders: list[OrderBookOrder] = []
@@ -724,13 +798,17 @@ class SaturnSwapOrderBook(AbstractOrderBookState):
724
798
 
725
799
  @classmethod
726
800
  def order_selector(cls) -> list[str]:
727
- """Return order script addresses."""
728
- return SaturnSwapOrderState.order_selector()
801
+ """Return order script addresses for all contract versions."""
802
+ return [
803
+ address
804
+ for state_cls in _SATURNSWAP_ORDER_STATE_CLASSES
805
+ for address in state_cls.order_selector()
806
+ ]
729
807
 
730
808
  @classmethod
731
809
  def pool_selector(cls) -> PoolSelector:
732
- """Return pool selector for order UTxOs."""
733
- return SaturnSwapOrderState.pool_selector()
810
+ """Return pool selector for order UTxOs across all contract versions."""
811
+ return PoolSelector(addresses=cls.order_selector())
734
812
 
735
813
  @classmethod
736
814
  def default_script_class(cls) -> type[PlutusV2Script]: