charli3_dendrite 1.1.3.dev0__tar.gz → 1.1.3.dev1__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 (37) hide show
  1. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/PKG-INFO +7 -6
  2. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/README.md +6 -5
  3. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/pyproject.toml +2 -2
  4. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/__init__.py +1 -0
  5. charli3_dendrite-1.1.3.dev1/src/charli3_dendrite/dexs/amm/cswap.py +369 -0
  6. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/LICENSE +0 -0
  7. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/backend/__init__.py +0 -0
  8. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/backend/backend_base.py +0 -0
  9. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/backend/blockfrost/__init__.py +0 -0
  10. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/backend/blockfrost/models.py +0 -0
  11. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/backend/dbsync/__init__.py +0 -0
  12. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/backend/dbsync/models.py +0 -0
  13. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/backend/ogmios_kupo/__init__.py +0 -0
  14. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/backend/ogmios_kupo/models.py +0 -0
  15. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/backend/utils.py +0 -0
  16. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/dataclasses/__init__.py +0 -0
  17. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/dataclasses/datums.py +0 -0
  18. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/dataclasses/models.py +0 -0
  19. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/dexs/__init__.py +0 -0
  20. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/dexs/amm/__init__.py +0 -0
  21. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/dexs/amm/amm_base.py +0 -0
  22. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/dexs/amm/amm_types.py +0 -0
  23. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/dexs/amm/minswap.py +0 -0
  24. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/dexs/amm/muesli.py +0 -0
  25. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/dexs/amm/spectrum.py +0 -0
  26. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/dexs/amm/splash.py +0 -0
  27. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/dexs/amm/sundae.py +0 -0
  28. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/dexs/amm/vyfi.py +0 -0
  29. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/dexs/amm/wingriders.py +0 -0
  30. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/dexs/core/__init__.py +0 -0
  31. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/dexs/core/base.py +0 -0
  32. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/dexs/core/errors.py +0 -0
  33. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/dexs/ob/__init__.py +0 -0
  34. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/dexs/ob/axo.py +0 -0
  35. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/dexs/ob/geniusyield.py +0 -0
  36. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/dexs/ob/ob_base.py +0 -0
  37. {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev1}/src/charli3_dendrite/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: charli3_dendrite
3
- Version: 1.1.3.dev0
3
+ Version: 1.1.3.dev1
4
4
  Summary:
5
5
  Author: Elder Millenial
6
6
  Author-email: eldermillenial@protonmail.com
@@ -36,7 +36,7 @@ Charli3 Dendrite is a powerful Python SDK designed for seamless interaction with
36
36
 
37
37
  ## Key Features
38
38
 
39
- - 🔄 Multi-DEX Support: Integrate with Minswap, MuesliSwap, Spectrum, SundaeSwap, VyFi, GeniusYield, Axo, and WingRiders
39
+ - 🔄 Multi-DEX Support: Integrate with Minswap, MuesliSwap, Spectrum, SundaeSwap, VyFi, GeniusYield, Splash, and WingRiders
40
40
  - 💧 Liquidity Pool Data: Fetch and analyze pool information across different DEXs
41
41
  - 💱 Swap Operations: Execute token swaps with ease
42
42
  - 🧩 Flexible Asset Handling: Manage various asset types and pool states efficiently
@@ -64,17 +64,18 @@ Charli3 Dendrite currently supports the following Cardano DEXs:
64
64
  - VyFi
65
65
  - WingRiders
66
66
  - GeniusYield
67
- - Axo
67
+ - Splash
68
+
69
+ ### Deprecated DEXs
70
+
71
+ - **Axo** ⚠️ - Left Cardano ecosystem. Implementation maintained for reference only and will be removed in future version.
68
72
 
69
73
  ### Not Yet Implemented
70
74
 
71
75
  - CardanoSwaps
72
- - Metadex
73
76
  - CSwap
74
- - TeddySwap
75
77
  - Cerra
76
78
  - SaturnSwap
77
- - Splash
78
79
 
79
80
  Each DEX is implemented as a separate module within the `charli3_dendrite.dexs.amm` package.
80
81
 
@@ -15,7 +15,7 @@ Charli3 Dendrite is a powerful Python SDK designed for seamless interaction with
15
15
 
16
16
  ## Key Features
17
17
 
18
- - 🔄 Multi-DEX Support: Integrate with Minswap, MuesliSwap, Spectrum, SundaeSwap, VyFi, GeniusYield, Axo, and WingRiders
18
+ - 🔄 Multi-DEX Support: Integrate with Minswap, MuesliSwap, Spectrum, SundaeSwap, VyFi, GeniusYield, Splash, and WingRiders
19
19
  - 💧 Liquidity Pool Data: Fetch and analyze pool information across different DEXs
20
20
  - 💱 Swap Operations: Execute token swaps with ease
21
21
  - 🧩 Flexible Asset Handling: Manage various asset types and pool states efficiently
@@ -43,17 +43,18 @@ Charli3 Dendrite currently supports the following Cardano DEXs:
43
43
  - VyFi
44
44
  - WingRiders
45
45
  - GeniusYield
46
- - Axo
46
+ - Splash
47
+
48
+ ### Deprecated DEXs
49
+
50
+ - **Axo** ⚠️ - Left Cardano ecosystem. Implementation maintained for reference only and will be removed in future version.
47
51
 
48
52
  ### Not Yet Implemented
49
53
 
50
54
  - CardanoSwaps
51
- - Metadex
52
55
  - CSwap
53
- - TeddySwap
54
56
  - Cerra
55
57
  - SaturnSwap
56
- - Splash
57
58
 
58
59
  Each DEX is implemented as a separate module within the `charli3_dendrite.dexs.amm` package.
59
60
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "charli3_dendrite"
3
- version = "1.1.3-dev0"
3
+ version = "1.1.3-dev1"
4
4
  description = ""
5
5
  authors = ["Elder Millenial <eldermillenial@protonmail.com>"]
6
6
  readme = "README.md"
@@ -49,7 +49,7 @@ module = ["pycardano", "pycardano.*", "dotenv", "nox"]
49
49
  ignore_missing_imports = true
50
50
 
51
51
  [tool.bumpversion]
52
- current_version = "1.1.3-dev0"
52
+ current_version = "1.1.3-dev1"
53
53
  parse = """(?x)
54
54
  (?P<major>\\d+)\\.
55
55
  (?P<minor>\\d+)\\.
@@ -1,4 +1,5 @@
1
1
  # noqa
2
+ from charli3_dendrite.dexs.amm.cswap import CSwapCPPState
2
3
  from charli3_dendrite.dexs.amm.minswap import MinswapCPPState
3
4
  from charli3_dendrite.dexs.amm.minswap import MinswapDJEDiUSDStableState
4
5
  from charli3_dendrite.dexs.amm.minswap import MinswapDJEDUSDCStableState
@@ -0,0 +1,369 @@
1
+ """CSwap DEX Module."""
2
+
3
+ from dataclasses import dataclass
4
+ from dataclasses import field
5
+ from typing import Any
6
+ from typing import ClassVar
7
+ from typing import List
8
+ from typing import Union
9
+
10
+ from pycardano import Address
11
+ from pycardano import PlutusData
12
+ from pycardano import PlutusV1Script
13
+ from pycardano import PlutusV2Script
14
+ from pycardano import VerificationKeyHash
15
+
16
+ from charli3_dendrite.dataclasses.datums import OrderDatum
17
+ from charli3_dendrite.dataclasses.datums import PlutusFullAddress
18
+ from charli3_dendrite.dataclasses.datums import PoolDatum
19
+ from charli3_dendrite.dataclasses.models import Assets
20
+ from charli3_dendrite.dataclasses.models import OrderType
21
+ from charli3_dendrite.dataclasses.models import PoolSelector
22
+ from charli3_dendrite.dexs.amm.amm_types import AbstractConstantProductPoolState
23
+ from charli3_dendrite.dexs.core.errors import NotAPoolError
24
+
25
+
26
+ @dataclass
27
+ class CSwapOrderSwapType(PlutusData):
28
+ """CSwap order type (Swap only)."""
29
+
30
+ CONSTR_ID = 0
31
+
32
+
33
+ @dataclass
34
+ class CSwapOrderZapInType(PlutusData):
35
+ """CSwap order type (Swap only)."""
36
+
37
+ CONSTR_ID = 1
38
+
39
+
40
+ @dataclass
41
+ class CSwapOrderZapOutType(PlutusData):
42
+ """CSwap order type (Swap only)."""
43
+
44
+ CONSTR_ID = 2
45
+
46
+
47
+ @dataclass
48
+ class CSwapOrderDatum(OrderDatum):
49
+ """CSwap order datum with ADA-only pair restriction."""
50
+
51
+ CONSTR_ID = 0
52
+
53
+ address: PlutusFullAddress # Field 0: Complex address structure
54
+ target_assets: List[List[Union[bytes, int]]]
55
+ input_assets: List[List[Union[bytes, int]]]
56
+ order_type: Union[CSwapOrderSwapType | CSwapOrderZapInType | CSwapOrderZapOutType]
57
+ slippage: int = 50
58
+ platform_fee: int = 15
59
+
60
+ @classmethod
61
+ def create_datum(
62
+ cls,
63
+ address_source: Address,
64
+ in_assets: Assets,
65
+ out_assets: Assets,
66
+ batcher_fee: Assets,
67
+ deposit: Assets,
68
+ address_target: Address | None = None,
69
+ datum_target: PlutusData | None = None,
70
+ ) -> "CSwapOrderDatum":
71
+ """Create a CSwap order datum."""
72
+ # Validate ADA-only restriction
73
+ merged_assets = in_assets + out_assets
74
+ if "lovelace" not in merged_assets:
75
+ raise ValueError("CSwap only supports ADA pairs - one token must be ADA")
76
+
77
+ full_address = PlutusFullAddress.from_address(address_source)
78
+
79
+ # Create target assets list (what we want to receive)
80
+ target_assets = []
81
+ for unit in out_assets:
82
+ if unit == "lovelace":
83
+ target_assets.append([b"", b"", out_assets[unit]])
84
+ else:
85
+ policy = bytes.fromhex(unit[:56])
86
+ name = bytes.fromhex(unit[56:])
87
+ target_assets.append([policy, name, out_assets[unit]])
88
+
89
+ # Add minimum ADA requirement (2 ADA minimum)
90
+ if "lovelace" not in out_assets:
91
+ target_assets.append([b"", b"", 2000000])
92
+
93
+ # Create input assets list (always zero quantity for input)
94
+ input_assets = []
95
+ for unit in in_assets:
96
+ if unit == "lovelace":
97
+ input_assets.append([b"", b"", 0])
98
+ else:
99
+ policy = bytes.fromhex(unit[:56])
100
+ name = bytes.fromhex(unit[56:])
101
+ input_assets.append([policy, name, 0])
102
+
103
+ return cls(
104
+ address=full_address,
105
+ target_assets=target_assets,
106
+ input_assets=input_assets,
107
+ order_type=CSwapOrderSwapType(),
108
+ slippage=50, # 0.5% default slippage
109
+ platform_fee=15, # 0.15% platform fee
110
+ )
111
+
112
+ def address_source(self) -> Address:
113
+ """Get the source address."""
114
+ return self.address.to_address()
115
+
116
+ def requested_amount(self) -> Assets:
117
+ """Get the requested amount."""
118
+ requested = {}
119
+ for target in self.target_assets:
120
+ policy = target[0].hex() if target[0] else ""
121
+ name = target[1].hex() if target[1] else ""
122
+ unit = policy + name if policy + name else "lovelace"
123
+ quantity = target[2]
124
+ if unit != "lovelace" or quantity > 2000000: # Skip minimum ADA requirement
125
+ requested[unit] = quantity
126
+ return Assets(requested)
127
+
128
+ def order_type(self) -> OrderType:
129
+ """Get the order type."""
130
+ return OrderType.swap
131
+
132
+
133
+ @dataclass
134
+ class CSwapPoolDatum(PoolDatum):
135
+ """CSwap pool datum with LP token tracking."""
136
+
137
+ CONSTR_ID = 0
138
+
139
+ # Fields MUST be in exact order per cswap.md specification
140
+ total_lp_tokens: int # Field 0: total lp tokens issued
141
+ pool_fee: int # Field 1: pool fee per 10K (85 = 0.85%)
142
+ quote_policy: bytes # Field 2: quote policy id - ADA (empty)
143
+ quote_name: bytes # Field 3: quote asset name - ADA (empty)
144
+ base_policy: bytes # Field 4: base policy id - token policy
145
+ base_name: bytes # Field 5: base asset name - token name
146
+ lp_token_policy: bytes # Field 6: lp token policy id
147
+ lp_token_name: bytes # Field 7: lp token asset name
148
+
149
+ def pool_pair(self) -> Assets | None:
150
+ """Return the pool pair assets."""
151
+ quote_unit = "lovelace"
152
+ base_unit = (self.base_policy + self.base_name).hex()
153
+ if not base_unit:
154
+ base_unit = "lovelace"
155
+
156
+ # For CSwap, we can't determine quantities from datum alone
157
+ # This will be filled by post_init
158
+ return Assets(**{quote_unit: 0, base_unit: 0})
159
+
160
+
161
+ class CSwapCPPState(AbstractConstantProductPoolState):
162
+ """CSwap CPP state with beacon token validation."""
163
+
164
+ fee: int = 85 # Pool fee per 10K (0.85%)
165
+ _batcher = Assets(lovelace=690000) # 0.69 ADA batcher fee
166
+ _deposit = Assets(lovelace=2000000) # 2 ADA deposit
167
+ _stake_address: ClassVar[Address] = Address.decode(
168
+ "addr1z8d9k3aw6w24eyfjacy809h68dv2rwnpw0arrfau98jk6nhv88awp8sgxk65d6kry0mar3rd0dlkfljz7dv64eu39vfs38yd9p"
169
+ )
170
+
171
+ @classmethod
172
+ def dex(cls) -> str:
173
+ """Get the DEX name."""
174
+ return "CSwap"
175
+
176
+ @classmethod
177
+ def order_selector(cls) -> list[str]:
178
+ """Get the order selector."""
179
+ return [cls._stake_address.encode()]
180
+
181
+ @classmethod
182
+ def pool_selector(cls) -> PoolSelector:
183
+ """Get the pool selector."""
184
+ return PoolSelector(
185
+ addresses=[
186
+ "addr1z8ke0c9p89rjfwmuh98jpt8ky74uy5mffjft3zlcld9h7ml3lmln3mwk0y3zsh3gs3dzqlwa9rjzrxawkwm4udw9axhs6fuu6e"
187
+ ]
188
+ )
189
+
190
+ @property
191
+ def swap_forward(self) -> bool:
192
+ """Check if swap forwarding is enabled."""
193
+ return False
194
+
195
+ @property
196
+ def stake_address(self) -> Address:
197
+ """Get the stake address."""
198
+ return self._stake_address
199
+
200
+ @classmethod
201
+ def order_datum_class(cls) -> type[CSwapOrderDatum]:
202
+ """Get the order datum class."""
203
+ return CSwapOrderDatum
204
+
205
+ @classmethod
206
+ def pool_datum_class(cls) -> type[CSwapPoolDatum]:
207
+ """Get the pool datum class."""
208
+ return CSwapPoolDatum
209
+
210
+ @property
211
+ def pool_id(self) -> str:
212
+ """A unique identifier for the pool."""
213
+ return f"cswap-{self.unit_a}-{self.unit_b}"
214
+
215
+ @classmethod
216
+ def extract_pool_nft(cls, values: dict[str, Any]) -> Assets | None:
217
+ """Extract the CSwap pool NFT from the UTXO.
218
+
219
+ CSwap uses a pool NFT system similar to Splash and Spectrum. The pool NFT:
220
+ - Has name "c" (single character, hex: 63)
221
+ - Has quantity of exactly 1
222
+ - Policy ID varies between pools
223
+
224
+ Args:
225
+ values: The pool UTXO inputs.
226
+
227
+ Returns:
228
+ Assets: The pool NFT or None if not found.
229
+ """
230
+ assets = values["assets"]
231
+
232
+ # If the pool NFT is already extracted, validate it
233
+ if "pool_nft" in values:
234
+ pool_nft = Assets(**dict(values["pool_nft"].items()))
235
+ if pool_nft.quantity() != 1:
236
+ raise NotAPoolError("CSwap pool NFT must have quantity of exactly 1")
237
+
238
+ # Check if token name is "c" (hex: 63)
239
+ unit = pool_nft.unit()
240
+ if len(unit) < 56 or unit[56:] != "63": # "c" in hex
241
+ raise NotAPoolError("CSwap pool NFT must have name 'c'")
242
+
243
+ return pool_nft
244
+
245
+ # Search for pool NFT with name "c"
246
+ pool_nft = None
247
+ for asset_unit in assets:
248
+ # Skip lovelace
249
+ if asset_unit == "lovelace":
250
+ continue
251
+
252
+ # Check if token name is "c" (hex: 63)
253
+ if len(asset_unit) >= 56 and asset_unit[56:] == "63":
254
+ quantity = assets[asset_unit]
255
+ if quantity == 1:
256
+ pool_nft = Assets(root={asset_unit: assets.root.pop(asset_unit)})
257
+ break
258
+
259
+ if pool_nft is None:
260
+ raise NotAPoolError(
261
+ "CSwap pool must contain exactly one pool NFT with name 'c'"
262
+ )
263
+
264
+ values["pool_nft"] = pool_nft
265
+ return pool_nft
266
+
267
+ def get_amount_out(
268
+ self,
269
+ asset: Assets,
270
+ precise: bool = True,
271
+ ) -> tuple[Assets, float]:
272
+ """Get the output asset amount given an input asset amount.
273
+
274
+ Validates ADA-only restriction before processing.
275
+ """
276
+ # Validate ADA-only restriction
277
+ if "lovelace" not in [self.unit_a, self.unit_b]:
278
+ raise ValueError("CSwap only supports ADA pairs - one token must be ADA")
279
+
280
+ if asset.unit() not in [self.unit_a, self.unit_b]:
281
+ raise ValueError(f"Asset {asset.unit()} not valid for this pool")
282
+
283
+ if len(asset) != 1:
284
+ raise ValueError("Only one asset can be provided for swap calculation")
285
+
286
+ # Ensure one of the pair assets is ADA
287
+ merged_test = Assets(**{asset.unit(): 1, "lovelace": 1})
288
+ if not any(unit in [self.unit_a, self.unit_b] for unit in merged_test):
289
+ raise ValueError("CSwap only supports ADA pairs")
290
+
291
+ return super().get_amount_out(asset, precise)
292
+
293
+ def get_amount_in(
294
+ self,
295
+ asset: Assets,
296
+ precise: bool = True,
297
+ ) -> tuple[Assets, float]:
298
+ """Get the input asset amount given a desired output asset amount.
299
+
300
+ Validates ADA-only restriction before processing.
301
+ """
302
+ # Validate ADA-only restriction
303
+ if "lovelace" not in [self.unit_a, self.unit_b]:
304
+ raise ValueError("CSwap only supports ADA pairs - one token must be ADA")
305
+
306
+ if asset.unit() not in [self.unit_a, self.unit_b]:
307
+ raise ValueError(f"Asset {asset.unit()} not valid for this pool")
308
+
309
+ if len(asset) != 1:
310
+ raise ValueError("Only one asset can be provided for swap calculation")
311
+
312
+ # Ensure one of the pair assets is ADA
313
+ merged_test = Assets(**{asset.unit(): 1, "lovelace": 1})
314
+ if not any(unit in [self.unit_a, self.unit_b] for unit in merged_test):
315
+ raise ValueError("CSwap only supports ADA pairs")
316
+
317
+ return super().get_amount_in(asset, precise)
318
+
319
+ @classmethod
320
+ def skip_init(cls, values: dict[str, Any]) -> bool:
321
+ """Skip initialization if pool NFT is already present.
322
+
323
+ Args:
324
+ values: The pool UTXO inputs.
325
+
326
+ Returns:
327
+ bool: True if initialization should be skipped, False otherwise.
328
+ """
329
+ if "pool_nft" in values:
330
+ # Pool NFT already extracted, just validate assets format
331
+ if not isinstance(values["assets"], Assets):
332
+ values["assets"] = Assets.model_validate(values["assets"])
333
+
334
+ return True
335
+ else:
336
+ return False
337
+
338
+ @classmethod
339
+ def post_init(cls, values: dict[str, Any]) -> dict[str, Any]:
340
+ """Post initialization for CSwap pools."""
341
+ super().post_init(values)
342
+
343
+ assets = values["assets"]
344
+
345
+ # Validate this is an ADA pair
346
+ asset_units = list(assets.root.keys())
347
+ if "lovelace" not in asset_units:
348
+ raise NotAPoolError("CSwap pools must contain ADA (lovelace)")
349
+
350
+ # Subtract 2 ADA pool maintenance from lovelace reserves
351
+ # CSwap pools require 2 ADA minimum to maintain the pool
352
+ if len(assets) == 2:
353
+ assets.root["lovelace"] -= 2000000 # 2 ADA maintenance
354
+
355
+ # Parse datum if available
356
+ if "datum_cbor" in values:
357
+ try:
358
+ datum = CSwapPoolDatum.from_cbor(values["datum_cbor"])
359
+ values["fee"] = datum.pool_fee
360
+ except Exception:
361
+ # If datum parsing fails, use default fee
362
+ values["fee"] = 85
363
+
364
+ return values
365
+
366
+ @classmethod
367
+ def default_script_class(cls) -> type[PlutusV1Script] | type[PlutusV2Script]:
368
+ """Get default script class as Plutus V2."""
369
+ return PlutusV2Script