charli3_dendrite 1.1.3.dev0__tar.gz → 1.1.3.dev2__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.
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/PKG-INFO +8 -7
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/README.md +7 -6
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/pyproject.toml +2 -2
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/__init__.py +1 -0
- charli3_dendrite-1.1.3.dev2/src/charli3_dendrite/dexs/amm/cswap.py +369 -0
- charli3_dendrite-1.1.3.dev2/src/charli3_dendrite/dexs/ob/djed.py +946 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/LICENSE +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/backend/__init__.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/backend/backend_base.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/backend/blockfrost/__init__.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/backend/blockfrost/models.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/backend/dbsync/__init__.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/backend/dbsync/models.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/backend/ogmios_kupo/__init__.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/backend/ogmios_kupo/models.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/backend/utils.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/dataclasses/__init__.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/dataclasses/datums.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/dataclasses/models.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/dexs/__init__.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/dexs/amm/__init__.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/dexs/amm/amm_base.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/dexs/amm/amm_types.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/dexs/amm/minswap.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/dexs/amm/muesli.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/dexs/amm/spectrum.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/dexs/amm/splash.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/dexs/amm/sundae.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/dexs/amm/vyfi.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/dexs/amm/wingriders.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/dexs/core/__init__.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/dexs/core/base.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/dexs/core/errors.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/dexs/ob/__init__.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/dexs/ob/axo.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/dexs/ob/geniusyield.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/dexs/ob/ob_base.py +0 -0
- {charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/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.
|
|
3
|
+
Version: 1.1.3.dev2
|
|
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,
|
|
39
|
+
- 🔄 Multi-DEX Support: Integrate with CSwap, 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
|
-
-
|
|
67
|
+
- Splash
|
|
68
|
+
- CSwap
|
|
69
|
+
|
|
70
|
+
### Deprecated DEXs
|
|
71
|
+
|
|
72
|
+
- **Axo** ⚠️ - Left Cardano ecosystem. Implementation maintained for reference only and will be removed in future version.
|
|
68
73
|
|
|
69
74
|
### Not Yet Implemented
|
|
70
75
|
|
|
71
76
|
- CardanoSwaps
|
|
72
|
-
- Metadex
|
|
73
|
-
- 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,
|
|
18
|
+
- 🔄 Multi-DEX Support: Integrate with CSwap, 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
|
-
-
|
|
46
|
+
- Splash
|
|
47
|
+
- CSwap
|
|
48
|
+
|
|
49
|
+
### Deprecated DEXs
|
|
50
|
+
|
|
51
|
+
- **Axo** ⚠️ - Left Cardano ecosystem. Implementation maintained for reference only and will be removed in future version.
|
|
47
52
|
|
|
48
53
|
### Not Yet Implemented
|
|
49
54
|
|
|
50
55
|
- CardanoSwaps
|
|
51
|
-
- Metadex
|
|
52
|
-
- 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-
|
|
3
|
+
version = "1.1.3-dev2"
|
|
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-
|
|
52
|
+
current_version = "1.1.3-dev2"
|
|
53
53
|
parse = """(?x)
|
|
54
54
|
(?P<major>\\d+)\\.
|
|
55
55
|
(?P<minor>\\d+)\\.
|
{charli3_dendrite-1.1.3.dev0 → charli3_dendrite-1.1.3.dev2}/src/charli3_dendrite/__init__.py
RENAMED
|
@@ -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
|
+
otype: 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
|
+
otype=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
|