charli3_dendrite 1.1.1.dev3__tar.gz → 1.1.1.dev5__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.1.dev3 → charli3_dendrite-1.1.1.dev5}/PKG-INFO +1 -2
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/pyproject.toml +1 -2
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/backend/ogmios_kupo/__init__.py +23 -14
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/dexs/amm/sundae.py +0 -1
- charli3_dendrite-1.1.1.dev5/src/charli3_dendrite/dexs/amm/vyfi.py +415 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/dexs/ob/axo.py +131 -63
- charli3_dendrite-1.1.1.dev3/src/charli3_dendrite/dexs/amm/vyfi.py +0 -343
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/LICENSE +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/README.md +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/__init__.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/backend/__init__.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/backend/backend_base.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/backend/blockfrost/__init__.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/backend/blockfrost/models.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/backend/dbsync/__init__.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/backend/dbsync/models.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/backend/ogmios_kupo/models.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/backend/utils.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/dataclasses/__init__.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/dataclasses/datums.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/dataclasses/models.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/dexs/__init__.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/dexs/amm/__init__.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/dexs/amm/amm_base.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/dexs/amm/amm_types.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/dexs/amm/minswap.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/dexs/amm/muesli.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/dexs/amm/spectrum.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/dexs/amm/wingriders.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/dexs/core/__init__.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/dexs/core/base.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/dexs/core/errors.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/dexs/ob/__init__.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/dexs/ob/geniusyield.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/dexs/ob/ob_base.py +0 -0
- {charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/utility.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: charli3_dendrite
|
|
3
|
-
Version: 1.1.1.
|
|
3
|
+
Version: 1.1.1.dev5
|
|
4
4
|
Summary:
|
|
5
5
|
Author: Elder Millenial
|
|
6
6
|
Author-email: eldermillenial@protonmail.com
|
|
@@ -10,7 +10,6 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.11
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
12
|
Requires-Dist: blockfrost-python (>=0.6.0,<0.7.0)
|
|
13
|
-
Requires-Dist: ogmios (==1.2.1)
|
|
14
13
|
Requires-Dist: psycopg[binary,pool] (>=3.1.13,<4.0.0)
|
|
15
14
|
Requires-Dist: pycardano (==0.11.1)
|
|
16
15
|
Requires-Dist: pydantic (>=2.5.2,<3.0.0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "charli3_dendrite"
|
|
3
|
-
version = "1.1.1-
|
|
3
|
+
version = "1.1.1-dev5"
|
|
4
4
|
description = ""
|
|
5
5
|
authors = ["Elder Millenial <eldermillenial@protonmail.com>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -14,7 +14,6 @@ psycopg = { extras = ["binary", "pool"], version = "^3.1.13" }
|
|
|
14
14
|
python-dotenv = "0.21.1"
|
|
15
15
|
pycardano = "0.11.1"
|
|
16
16
|
blockfrost-python = "^0.6.0"
|
|
17
|
-
ogmios = "1.2.1"
|
|
18
17
|
|
|
19
18
|
|
|
20
19
|
[tool.poetry.group.dev.dependencies]
|
|
@@ -6,8 +6,8 @@ from typing import Optional
|
|
|
6
6
|
from typing import Union
|
|
7
7
|
|
|
8
8
|
import requests
|
|
9
|
-
from ogmios import OgmiosChainContext # type: ignore
|
|
10
9
|
from pycardano import Address # type: ignore
|
|
10
|
+
from pycardano import KupoOgmiosV6ChainContext
|
|
11
11
|
from pycardano import Network # type: ignore
|
|
12
12
|
|
|
13
13
|
from charli3_dendrite.backend.backend_base import AbstractBackend
|
|
@@ -52,12 +52,14 @@ class OgmiosKupoBackend(AbstractBackend):
|
|
|
52
52
|
"""
|
|
53
53
|
_, ws_string = ogmios_url.split("ws://")
|
|
54
54
|
self.ws_url, self.port = ws_string.split(":")
|
|
55
|
-
self.ogmios_context =
|
|
55
|
+
self.ogmios_context = KupoOgmiosV6ChainContext(
|
|
56
56
|
host=self.ws_url,
|
|
57
57
|
port=int(self.port),
|
|
58
|
+
secure=False,
|
|
59
|
+
refetch_chain_tip_interval=None,
|
|
58
60
|
network=network,
|
|
61
|
+
kupo_url=kupo_url,
|
|
59
62
|
)
|
|
60
|
-
self.kupo_url = kupo_url
|
|
61
63
|
|
|
62
64
|
def _kupo_request(
|
|
63
65
|
self,
|
|
@@ -76,7 +78,7 @@ class OgmiosKupoBackend(AbstractBackend):
|
|
|
76
78
|
Raises:
|
|
77
79
|
requests.exceptions.RequestException: If the request fails.
|
|
78
80
|
"""
|
|
79
|
-
url = f"{self.
|
|
81
|
+
url = f"{self.ogmios_context._kupo_url}/{endpoint}"
|
|
80
82
|
response = requests.get(url, params=params, timeout=10)
|
|
81
83
|
response.raise_for_status()
|
|
82
84
|
return KupoGenericResponse.model_validate(response.json())
|
|
@@ -122,8 +124,8 @@ class OgmiosKupoBackend(AbstractBackend):
|
|
|
122
124
|
"""Get pool UTXOs based on assets and addresses.
|
|
123
125
|
|
|
124
126
|
Args:
|
|
127
|
+
addresses (list[str]): List of addresses to query.
|
|
125
128
|
assets (Optional[list[str]]): List of asset IDs to filter by.
|
|
126
|
-
addresses (Optional[list[str]]): List of addresses to query.
|
|
127
129
|
limit (int): Maximum number of UTXOs to return.
|
|
128
130
|
page (int): Page number for pagination.
|
|
129
131
|
historical (bool): Whether to include historical data.
|
|
@@ -132,7 +134,7 @@ class OgmiosKupoBackend(AbstractBackend):
|
|
|
132
134
|
PoolStateList: List of pool states.
|
|
133
135
|
"""
|
|
134
136
|
pool_states = []
|
|
135
|
-
if addresses
|
|
137
|
+
if not addresses:
|
|
136
138
|
return PoolStateList(root=[])
|
|
137
139
|
|
|
138
140
|
for address in addresses:
|
|
@@ -140,15 +142,20 @@ class OgmiosKupoBackend(AbstractBackend):
|
|
|
140
142
|
"limit": limit,
|
|
141
143
|
"offset": page * limit,
|
|
142
144
|
}
|
|
145
|
+
payment_cred = self.get_payment_credential(address)
|
|
143
146
|
if assets:
|
|
144
|
-
|
|
147
|
+
last_asset = assets[-1]
|
|
148
|
+
params["policy_id"] = last_asset[:POLICY_ID_LENGTH]
|
|
145
149
|
params["asset_name"] = (
|
|
146
|
-
|
|
147
|
-
if len(
|
|
150
|
+
last_asset[POLICY_ID_LENGTH:]
|
|
151
|
+
if len(last_asset) > POLICY_ID_LENGTH
|
|
148
152
|
else None
|
|
149
153
|
)
|
|
150
154
|
|
|
151
|
-
matches = self._kupo_request(
|
|
155
|
+
matches = self._kupo_request(
|
|
156
|
+
f"matches/{payment_cred}/*?unspent",
|
|
157
|
+
params=params,
|
|
158
|
+
)
|
|
152
159
|
if isinstance(matches.root, list):
|
|
153
160
|
for match in matches.root:
|
|
154
161
|
pool_state = self._pool_state_from_kupo(match)
|
|
@@ -181,15 +188,17 @@ class OgmiosKupoBackend(AbstractBackend):
|
|
|
181
188
|
"transaction_id": tx_hash,
|
|
182
189
|
"order": "most_recent_first",
|
|
183
190
|
}
|
|
191
|
+
payment_cred = self.get_payment_credential(address)
|
|
184
192
|
if assets:
|
|
185
|
-
|
|
193
|
+
last_asset = assets[-1]
|
|
194
|
+
params["policy_id"] = last_asset[:POLICY_ID_LENGTH]
|
|
186
195
|
params["asset_name"] = (
|
|
187
|
-
|
|
188
|
-
if len(
|
|
196
|
+
last_asset[POLICY_ID_LENGTH:]
|
|
197
|
+
if len(last_asset) > POLICY_ID_LENGTH
|
|
189
198
|
else None
|
|
190
199
|
)
|
|
191
200
|
|
|
192
|
-
matches = self._kupo_request(f"matches/{
|
|
201
|
+
matches = self._kupo_request(f"matches/{payment_cred}/*", params=params)
|
|
193
202
|
if isinstance(matches.root, list):
|
|
194
203
|
pool_states = []
|
|
195
204
|
if matches.root:
|
{charli3_dendrite-1.1.1.dev3 → charli3_dendrite-1.1.1.dev5}/src/charli3_dendrite/dexs/amm/sundae.py
RENAMED
|
@@ -698,7 +698,6 @@ class SundaeSwapV3CPPState(AbstractConstantProductPoolState):
|
|
|
698
698
|
)
|
|
699
699
|
|
|
700
700
|
datum = SundaeV3Settings.from_cbor(settings.datum_cbor)
|
|
701
|
-
print(datum.simple_fee, datum.base_fee)
|
|
702
701
|
cls._batcher_fee = Assets(lovelace=datum.simple_fee + datum.base_fee)
|
|
703
702
|
|
|
704
703
|
def swap_datum(
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
"""VyFi DEX Module."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import time
|
|
7
|
+
from collections import defaultdict
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Any
|
|
10
|
+
from typing import ClassVar
|
|
11
|
+
from typing import Optional
|
|
12
|
+
from typing import Union
|
|
13
|
+
|
|
14
|
+
import requests
|
|
15
|
+
from pycardano import Address
|
|
16
|
+
from pycardano import PlutusData
|
|
17
|
+
from pycardano import VerificationKeyHash
|
|
18
|
+
from pydantic import BaseModel
|
|
19
|
+
from pydantic import Field
|
|
20
|
+
|
|
21
|
+
from charli3_dendrite.dataclasses.datums import OrderDatum
|
|
22
|
+
from charli3_dendrite.dataclasses.datums import PoolDatum
|
|
23
|
+
from charli3_dendrite.dataclasses.models import OrderType
|
|
24
|
+
from charli3_dendrite.dataclasses.models import PoolSelector
|
|
25
|
+
from charli3_dendrite.dexs.amm.amm_types import AbstractConstantProductPoolState
|
|
26
|
+
from charli3_dendrite.dexs.core.errors import NoAssetsError
|
|
27
|
+
from charli3_dendrite.dexs.core.errors import NotAPoolError
|
|
28
|
+
from charli3_dendrite.utility import Assets
|
|
29
|
+
|
|
30
|
+
POOL_REFRESH_INTERVAL = 3600
|
|
31
|
+
ADDRESS_HASH_LENGTH = 28
|
|
32
|
+
POLICY_ID_LENGTH = 56
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class VyFiPoolDatum(PoolDatum):
|
|
37
|
+
"""VyFi pool datum."""
|
|
38
|
+
|
|
39
|
+
token_a_fees: int
|
|
40
|
+
token_b_fees: int
|
|
41
|
+
lp_tokens: int
|
|
42
|
+
|
|
43
|
+
def pool_pair(self) -> Optional[Assets]:
|
|
44
|
+
"""Return the pool pair assets."""
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class Deposit(PlutusData):
|
|
50
|
+
"""Deposit assets into the pool."""
|
|
51
|
+
|
|
52
|
+
CONSTR_ID = 0
|
|
53
|
+
min_lp_receive: int
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class WithdrawPair(PlutusData):
|
|
58
|
+
"""Withdraw pair of assets."""
|
|
59
|
+
|
|
60
|
+
CONSTR_ID = 0
|
|
61
|
+
min_amount_a: int
|
|
62
|
+
min_amount_b: int
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class Withdraw(PlutusData):
|
|
67
|
+
"""Withdraw assets from the pool."""
|
|
68
|
+
|
|
69
|
+
CONSTR_ID = 1
|
|
70
|
+
min_lp_receive: WithdrawPair
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class LPFlushA(PlutusData):
|
|
75
|
+
"""Flush LP tokens from A."""
|
|
76
|
+
|
|
77
|
+
CONSTR_ID = 2
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class AtoB(PlutusData):
|
|
82
|
+
"""A to B swap direction."""
|
|
83
|
+
|
|
84
|
+
CONSTR_ID = 3
|
|
85
|
+
min_receive: int
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass
|
|
89
|
+
class BtoA(PlutusData):
|
|
90
|
+
"""B to A swap direction."""
|
|
91
|
+
|
|
92
|
+
CONSTR_ID = 4
|
|
93
|
+
min_receive: int
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class ZapInA(PlutusData):
|
|
98
|
+
"""Zap in A."""
|
|
99
|
+
|
|
100
|
+
CONSTR_ID = 5
|
|
101
|
+
min_lp_receive: int
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclass
|
|
105
|
+
class ZapInB(PlutusData):
|
|
106
|
+
"""Zap in B."""
|
|
107
|
+
|
|
108
|
+
CONSTR_ID = 6
|
|
109
|
+
min_lp_receive: int
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class VyFiOrderDatum(OrderDatum):
|
|
114
|
+
"""VyFi order datum."""
|
|
115
|
+
|
|
116
|
+
address: bytes
|
|
117
|
+
order: Union[AtoB, BtoA, Deposit, LPFlushA, Withdraw, ZapInA, ZapInB]
|
|
118
|
+
|
|
119
|
+
@classmethod
|
|
120
|
+
def create_datum(
|
|
121
|
+
cls,
|
|
122
|
+
address_source: Address,
|
|
123
|
+
in_assets: Assets,
|
|
124
|
+
out_assets: Assets,
|
|
125
|
+
batcher_fee: Assets, # noqa: ARG003
|
|
126
|
+
deposit: Assets, # noqa: ARG003
|
|
127
|
+
address_target: Optional[Address] = None, # noqa: ARG003
|
|
128
|
+
datum_target: Optional[PlutusData] = None, # noqa: ARG003
|
|
129
|
+
) -> VyFiOrderDatum:
|
|
130
|
+
"""Create a new order datum."""
|
|
131
|
+
address_hash = (
|
|
132
|
+
address_source.payment_part.to_primitive()
|
|
133
|
+
+ address_source.staking_part.to_primitive()
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
merged = in_assets + out_assets
|
|
137
|
+
if in_assets.unit() == merged.unit():
|
|
138
|
+
order = AtoB(min_receive=out_assets.quantity())
|
|
139
|
+
else:
|
|
140
|
+
order = BtoA(min_receive=out_assets.quantity())
|
|
141
|
+
|
|
142
|
+
return cls(address=address_hash, order=order)
|
|
143
|
+
|
|
144
|
+
def address_source(self) -> Address:
|
|
145
|
+
"""Get the source address."""
|
|
146
|
+
payment_part = VerificationKeyHash.from_primitive(
|
|
147
|
+
self.address[:ADDRESS_HASH_LENGTH],
|
|
148
|
+
)
|
|
149
|
+
staking_part = (
|
|
150
|
+
VerificationKeyHash.from_primitive(self.address[ADDRESS_HASH_LENGTH:])
|
|
151
|
+
if len(self.address) > ADDRESS_HASH_LENGTH
|
|
152
|
+
else None
|
|
153
|
+
)
|
|
154
|
+
return Address(payment_part=payment_part, staking_part=staking_part)
|
|
155
|
+
|
|
156
|
+
def requested_amount(self) -> Assets:
|
|
157
|
+
"""Get the requested amount."""
|
|
158
|
+
if isinstance(self.order, BtoA):
|
|
159
|
+
return Assets({"asset_a": self.order.min_receive})
|
|
160
|
+
if isinstance(self.order, AtoB):
|
|
161
|
+
return Assets({"asset_b": self.order.min_receive})
|
|
162
|
+
if isinstance(self.order, (ZapInA, ZapInB, Deposit)):
|
|
163
|
+
return Assets({"lp": self.order.min_lp_receive})
|
|
164
|
+
if isinstance(self.order, Withdraw):
|
|
165
|
+
return Assets(
|
|
166
|
+
{
|
|
167
|
+
"asset_a": self.order.min_lp_receive.min_amount_a,
|
|
168
|
+
"asset_b": self.order.min_lp_receive.min_amount_b,
|
|
169
|
+
},
|
|
170
|
+
)
|
|
171
|
+
return Assets()
|
|
172
|
+
|
|
173
|
+
def order_type(self) -> Optional[OrderType]:
|
|
174
|
+
"""Get the order type."""
|
|
175
|
+
if isinstance(self.order, (BtoA, AtoB, ZapInA, ZapInB)):
|
|
176
|
+
return OrderType.swap
|
|
177
|
+
if isinstance(self.order, Deposit):
|
|
178
|
+
return OrderType.deposit
|
|
179
|
+
if isinstance(self.order, Withdraw):
|
|
180
|
+
return OrderType.withdraw
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class VyFiTokenDefinition(BaseModel):
|
|
185
|
+
"""VyFi token definition."""
|
|
186
|
+
|
|
187
|
+
token_name: str = Field(alias="tokenName")
|
|
188
|
+
currency_symbol: str = Field(alias="currencySymbol")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class VyFiFees(BaseModel):
|
|
192
|
+
"""VyFi fees."""
|
|
193
|
+
|
|
194
|
+
bar_fee: int = Field(alias="barFee")
|
|
195
|
+
process_fee: int = Field(alias="processFee")
|
|
196
|
+
liq_fee: int = Field(alias="liqFee")
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class VyFiPoolTokens(BaseModel):
|
|
200
|
+
"""VyFi pool tokens."""
|
|
201
|
+
|
|
202
|
+
a_asset: VyFiTokenDefinition = Field(alias="aAsset")
|
|
203
|
+
b_asset: VyFiTokenDefinition = Field(alias="bAsset")
|
|
204
|
+
main_nft: VyFiTokenDefinition = Field(alias="mainNFT")
|
|
205
|
+
operator_token: VyFiTokenDefinition = Field(alias="operatorToken")
|
|
206
|
+
lp_token_name: dict[str, str] = Field(alias="lpTokenName")
|
|
207
|
+
fees_settings: VyFiFees = Field(alias="feesSettings")
|
|
208
|
+
stake_key: Optional[str] = Field(alias="stakeKey")
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class VyFiPoolDefinition(BaseModel):
|
|
212
|
+
"""VyFi pool definition."""
|
|
213
|
+
|
|
214
|
+
units_pair: str = Field(alias="unitsPair")
|
|
215
|
+
pool_validator_utxo_address: str = Field(alias="poolValidatorUtxoAddress")
|
|
216
|
+
lp_policy_id_asset_id: str = Field(alias="lpPolicyId-assetId")
|
|
217
|
+
json_: VyFiPoolTokens = Field(alias="json")
|
|
218
|
+
pair: str
|
|
219
|
+
is_live: bool = Field(alias="isLive")
|
|
220
|
+
order_validator_utxo_address: str = Field(alias="orderValidatorUtxoAddress")
|
|
221
|
+
|
|
222
|
+
def __hash__(self) -> int:
|
|
223
|
+
"""Make VyFiPoolDefinition hashable."""
|
|
224
|
+
return hash(
|
|
225
|
+
(
|
|
226
|
+
self.units_pair,
|
|
227
|
+
self.pool_validator_utxo_address,
|
|
228
|
+
self.order_validator_utxo_address,
|
|
229
|
+
),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class VyFiCPPState(AbstractConstantProductPoolState):
|
|
234
|
+
"""VyFi CPP state."""
|
|
235
|
+
|
|
236
|
+
_batcher = Assets(lovelace=1900000)
|
|
237
|
+
_deposit = Assets(lovelace=2000000)
|
|
238
|
+
_pools: ClassVar[Optional[dict[str, VyFiPoolDefinition]]] = None
|
|
239
|
+
_pools_refresh: ClassVar[float] = 0.0
|
|
240
|
+
lp_fee: int = 0
|
|
241
|
+
bar_fee: int = 0
|
|
242
|
+
|
|
243
|
+
@classmethod
|
|
244
|
+
def dex(cls) -> str:
|
|
245
|
+
"""Get the DEX name."""
|
|
246
|
+
return "VyFi"
|
|
247
|
+
|
|
248
|
+
@classmethod
|
|
249
|
+
def pools(cls) -> dict[str, VyFiPoolDefinition]:
|
|
250
|
+
"""Get the pools."""
|
|
251
|
+
if (
|
|
252
|
+
cls._pools is None
|
|
253
|
+
or (time.time() - cls._pools_refresh) > POOL_REFRESH_INTERVAL
|
|
254
|
+
):
|
|
255
|
+
cls._refresh_pools()
|
|
256
|
+
return cls._pools or {}
|
|
257
|
+
|
|
258
|
+
@classmethod
|
|
259
|
+
def order_selector(cls) -> list[str]:
|
|
260
|
+
"""Get order selector addresses."""
|
|
261
|
+
return [p.order_validator_utxo_address for p in cls.pools().values()]
|
|
262
|
+
|
|
263
|
+
@classmethod
|
|
264
|
+
def pool_selector(cls, assets: Optional[list[str]] = None) -> PoolSelector:
|
|
265
|
+
"""Get a PoolSelector for VyFi pools, optionally filtered by assets."""
|
|
266
|
+
asset_to_pool = cls._create_asset_to_pool_mapping()
|
|
267
|
+
relevant_pools = cls._filter_relevant_pools(asset_to_pool, assets)
|
|
268
|
+
addresses = [pool.pool_validator_utxo_address for pool in relevant_pools]
|
|
269
|
+
return PoolSelector(addresses=addresses)
|
|
270
|
+
|
|
271
|
+
@classmethod
|
|
272
|
+
def _create_asset_to_pool_mapping(
|
|
273
|
+
cls,
|
|
274
|
+
) -> defaultdict[str, list[VyFiPoolDefinition]]:
|
|
275
|
+
"""Create a mapping of assets to pools."""
|
|
276
|
+
asset_to_pool: defaultdict[str, list[VyFiPoolDefinition]] = defaultdict(list)
|
|
277
|
+
for pool in cls.pools().values():
|
|
278
|
+
asset_a = cls._encode_asset(
|
|
279
|
+
pool.json_.a_asset.currency_symbol,
|
|
280
|
+
pool.json_.a_asset.token_name,
|
|
281
|
+
)
|
|
282
|
+
asset_b = cls._encode_asset(
|
|
283
|
+
pool.json_.b_asset.currency_symbol,
|
|
284
|
+
pool.json_.b_asset.token_name,
|
|
285
|
+
)
|
|
286
|
+
asset_to_pool[asset_a].append(pool)
|
|
287
|
+
asset_to_pool[asset_b].append(pool)
|
|
288
|
+
return asset_to_pool
|
|
289
|
+
|
|
290
|
+
@classmethod
|
|
291
|
+
def _filter_relevant_pools(
|
|
292
|
+
cls,
|
|
293
|
+
asset_to_pool: defaultdict[str, list[VyFiPoolDefinition]],
|
|
294
|
+
assets: Optional[list[str]],
|
|
295
|
+
) -> set[VyFiPoolDefinition]:
|
|
296
|
+
"""Filter relevant pools based on assets."""
|
|
297
|
+
if assets:
|
|
298
|
+
relevant_pools = set()
|
|
299
|
+
for asset in assets:
|
|
300
|
+
relevant_pools.update(asset_to_pool.get(asset, []))
|
|
301
|
+
else:
|
|
302
|
+
relevant_pools = set(cls.pools().values())
|
|
303
|
+
return relevant_pools
|
|
304
|
+
|
|
305
|
+
@staticmethod
|
|
306
|
+
def _encode_asset(policy_id: str, asset_name: str) -> str:
|
|
307
|
+
"""Encode an asset by combining policy ID and hex-encoded asset name."""
|
|
308
|
+
encoded_name = asset_name.encode("utf-8").hex()
|
|
309
|
+
return policy_id + encoded_name
|
|
310
|
+
|
|
311
|
+
@staticmethod
|
|
312
|
+
def _decode_asset(encoded_asset: str) -> tuple[str, str]:
|
|
313
|
+
"""Decode an encoded asset into policy ID and asset name."""
|
|
314
|
+
policy_id = encoded_asset[:POLICY_ID_LENGTH]
|
|
315
|
+
asset_name = bytes.fromhex(encoded_asset[POLICY_ID_LENGTH:]).decode("utf-8")
|
|
316
|
+
return policy_id, asset_name
|
|
317
|
+
|
|
318
|
+
@staticmethod
|
|
319
|
+
def _split_asset(asset: str) -> tuple[str, str]:
|
|
320
|
+
"""Split an asset string into policy ID and asset name."""
|
|
321
|
+
if len(asset) == POLICY_ID_LENGTH: # Only policy ID
|
|
322
|
+
return asset, ""
|
|
323
|
+
return asset[:POLICY_ID_LENGTH], asset[POLICY_ID_LENGTH:]
|
|
324
|
+
|
|
325
|
+
@classmethod
|
|
326
|
+
def _refresh_pools(cls) -> None:
|
|
327
|
+
"""Refresh the pools data from the API."""
|
|
328
|
+
try:
|
|
329
|
+
response = requests.get(
|
|
330
|
+
"https://api.vyfi.io/lp?networkId=1&v2=true",
|
|
331
|
+
timeout=10,
|
|
332
|
+
)
|
|
333
|
+
response.raise_for_status()
|
|
334
|
+
cls._pools = {}
|
|
335
|
+
for p in response.json():
|
|
336
|
+
p["json"] = json.loads(p["json"])
|
|
337
|
+
cls._pools[
|
|
338
|
+
p["json"]["mainNFT"]["currencySymbol"]
|
|
339
|
+
] = VyFiPoolDefinition.model_validate(p)
|
|
340
|
+
cls._pools_refresh = time.time()
|
|
341
|
+
except requests.RequestException as e:
|
|
342
|
+
# Log the error or handle it as appropriate for your application
|
|
343
|
+
print(f"Error refreshing pools: {e}") # noqa: T201
|
|
344
|
+
|
|
345
|
+
@property
|
|
346
|
+
def swap_forward(self) -> bool:
|
|
347
|
+
"""Check if swap is forward."""
|
|
348
|
+
return False
|
|
349
|
+
|
|
350
|
+
@property
|
|
351
|
+
def stake_address(self) -> Address:
|
|
352
|
+
"""Get the stake address."""
|
|
353
|
+
return Address.from_primitive(
|
|
354
|
+
VyFiCPPState.pools()[self.pool_id].order_validator_utxo_address,
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
@classmethod
|
|
358
|
+
def order_datum_class(cls) -> type[VyFiOrderDatum]:
|
|
359
|
+
"""Get the order datum class."""
|
|
360
|
+
return VyFiOrderDatum
|
|
361
|
+
|
|
362
|
+
@classmethod
|
|
363
|
+
def pool_datum_class(cls) -> type[VyFiPoolDatum]:
|
|
364
|
+
"""Get the pool datum class."""
|
|
365
|
+
return VyFiPoolDatum
|
|
366
|
+
|
|
367
|
+
@property
|
|
368
|
+
def pool_id(self) -> str:
|
|
369
|
+
"""Get a unique identifier for the pool."""
|
|
370
|
+
return self.pool_nft.unit()
|
|
371
|
+
|
|
372
|
+
@property
|
|
373
|
+
def volume_fee(self) -> int:
|
|
374
|
+
"""Get the volume fee."""
|
|
375
|
+
return self.lp_fee + self.bar_fee
|
|
376
|
+
|
|
377
|
+
@classmethod
|
|
378
|
+
def extract_pool_nft(cls, values: dict[str, Any]) -> Optional[Assets]:
|
|
379
|
+
"""Extract the dex nft from the UTXO."""
|
|
380
|
+
assets = values["assets"]
|
|
381
|
+
|
|
382
|
+
if "pool_nft" in values:
|
|
383
|
+
pool_nft = (
|
|
384
|
+
Assets(root=values["pool_nft"])
|
|
385
|
+
if isinstance(values["pool_nft"], dict)
|
|
386
|
+
else values["pool_nft"]
|
|
387
|
+
)
|
|
388
|
+
if not any(p in cls.pools() for p in values["pool_nft"]):
|
|
389
|
+
raise ValueError("Invalid pool NFT")
|
|
390
|
+
else:
|
|
391
|
+
nfts = [asset for asset, quantity in assets.items() if asset in cls.pools()]
|
|
392
|
+
if len(nfts) < 1:
|
|
393
|
+
if len(assets) == 0:
|
|
394
|
+
raise NoAssetsError(f"{cls.__name__}: No assets supplied.")
|
|
395
|
+
raise NotAPoolError(
|
|
396
|
+
f"{cls.__name__}: Pool must have one DEX NFT token.",
|
|
397
|
+
)
|
|
398
|
+
pool_nft = Assets(**{nfts[0]: assets.root.pop(nfts[0])})
|
|
399
|
+
values["pool_nft"] = pool_nft
|
|
400
|
+
|
|
401
|
+
values["lp_fee"] = cls.pools()[pool_nft.unit()].json_.fees_settings.liq_fee
|
|
402
|
+
values["bar_fee"] = cls.pools()[pool_nft.unit()].json_.fees_settings.bar_fee
|
|
403
|
+
|
|
404
|
+
return pool_nft
|
|
405
|
+
|
|
406
|
+
@classmethod
|
|
407
|
+
def post_init(cls, values: dict[str, Any]) -> None:
|
|
408
|
+
"""Post-initialization processing."""
|
|
409
|
+
super().post_init(values)
|
|
410
|
+
|
|
411
|
+
assets = values["assets"]
|
|
412
|
+
datum = VyFiPoolDatum.from_cbor(values["datum_cbor"])
|
|
413
|
+
|
|
414
|
+
assets.root[assets.unit(0)] -= datum.token_a_fees
|
|
415
|
+
assets.root[assets.unit(1)] -= datum.token_b_fees
|