charli3_dendrite 1.4.16.dev0__tar.gz → 1.4.17.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.
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/PKG-INFO +1 -1
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/pyproject.toml +2 -2
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/__init__.py +40 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/base.py +369 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/__init__.py +21 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/constants.py +157 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/datums.py +135 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/loader.py +233 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/market.py +89 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/math.py +255 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/oracles/__init__.py +1 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/oracles/aggregator.py +181 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/oracles/aggregator_datums.py +241 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/oracles/deployments.py +104 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/oracles/forward.py +357 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/oracles/leaves.py +356 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/oracles/locator.py +140 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/oracles/lp_pricing.py +102 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/oracles/recipes.json +837 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/oracles/redeemer.py +298 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/oracles/reproduce.py +213 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/oracles/structured_datums.py +303 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/state.py +253 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/transactions/__init__.py +49 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/transactions/_common.py +1462 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/transactions/build.py +562 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/transactions/builder.py +327 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/transactions/context.py +1146 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/transactions/create_loan.py +77 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/transactions/datum_synth.py +508 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/transactions/increase_loan.py +494 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/transactions/loan_ids.py +37 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/transactions/modify_collateral.py +371 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/transactions/oracle_redeemer.py +53 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/transactions/oracle_synth.py +45 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/transactions/redeemers.py +190 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/transactions/repay.py +665 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/transactions/topup_withdraw.py +369 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/danogo/transactions/verify.py +11 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/math.py +34 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/oracles/__init__.py +1 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/oracles/charli3.py +83 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/oracles/dex_pooled.py +51 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/oracles/fluid.py +136 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/oracles/models.py +168 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/oracles/orcfax.py +153 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/oracles/qtoken.py +50 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/registry.py +68 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/transactions/__init__.py +25 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/transactions/base.py +141 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/transactions/infra.py +241 -0
- charli3_dendrite-1.4.17.dev0/src/charli3_dendrite/lending/transactions/snapshot.py +47 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/utility.py +12 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/LICENSE +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/README.md +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/__init__.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/backend/__init__.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/backend/backend_base.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/backend/blockfrost/__init__.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/backend/blockfrost/models.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/backend/dbsync/__init__.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/backend/dbsync/models.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/backend/ogmios_kupo/__init__.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/backend/ogmios_kupo/models.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/backend/utils.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dataclasses/__init__.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dataclasses/datums.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dataclasses/models.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dexs/__init__.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dexs/amm/__init__.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dexs/amm/amm_base.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dexs/amm/amm_types.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dexs/amm/cswap.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dexs/amm/dano.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dexs/amm/minswap.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dexs/amm/muesli.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dexs/amm/spectrum.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dexs/amm/splash.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dexs/amm/sundae.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dexs/amm/vyfi.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dexs/amm/wingriders.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dexs/core/__init__.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dexs/core/base.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dexs/core/errors.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dexs/ob/__init__.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dexs/ob/axo.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dexs/ob/cardanoswaps.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dexs/ob/chadswap.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dexs/ob/djed.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dexs/ob/geniusyield.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dexs/ob/ob_base.py +0 -0
- {charli3_dendrite-1.4.16.dev0 → charli3_dendrite-1.4.17.dev0}/src/charli3_dendrite/dexs/ob/saturnswap.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "charli3_dendrite"
|
|
3
|
-
version = "1.4.
|
|
3
|
+
version = "1.4.17-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.
|
|
53
|
+
current_version = "1.4.17-dev0"
|
|
54
54
|
parse = """(?x)
|
|
55
55
|
(?P<major>\\d+)\\.
|
|
56
56
|
(?P<minor>\\d+)\\.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Read-only lending-protocol analytics (pools, loans, oracles).
|
|
2
|
+
|
|
3
|
+
Importing this package registers all oracle resolvers. Lending classes are
|
|
4
|
+
intentionally NOT exported from `charli3_dendrite/__init__.py` and do not
|
|
5
|
+
subclass DEX bases, so they never enter DEX discovery.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from charli3_dendrite.lending.base import AbstractLendingPoolState
|
|
9
|
+
from charli3_dendrite.lending.base import AbstractLoanState
|
|
10
|
+
from charli3_dendrite.lending.base import LendingBook
|
|
11
|
+
from charli3_dendrite.lending.base import LendingPriceBook
|
|
12
|
+
from charli3_dendrite.lending.oracles.charli3 import Charli3Resolver
|
|
13
|
+
from charli3_dendrite.lending.oracles.dex_pooled import DexPooledResolver
|
|
14
|
+
from charli3_dendrite.lending.oracles.fluid import FluidResolver
|
|
15
|
+
from charli3_dendrite.lending.oracles.models import OraclePrice
|
|
16
|
+
from charli3_dendrite.lending.oracles.models import OracleRef
|
|
17
|
+
from charli3_dendrite.lending.oracles.models import OracleSource
|
|
18
|
+
from charli3_dendrite.lending.oracles.models import PriceMap
|
|
19
|
+
from charli3_dendrite.lending.oracles.models import register_resolver
|
|
20
|
+
from charli3_dendrite.lending.oracles.orcfax import OrcfaxResolver
|
|
21
|
+
from charli3_dendrite.lending.oracles.qtoken import QTokenResolver
|
|
22
|
+
|
|
23
|
+
register_resolver(Charli3Resolver())
|
|
24
|
+
register_resolver(OrcfaxResolver())
|
|
25
|
+
register_resolver(QTokenResolver())
|
|
26
|
+
register_resolver(DexPooledResolver())
|
|
27
|
+
register_resolver(FluidResolver(OracleSource.FLUID_AGGREGATED))
|
|
28
|
+
register_resolver(FluidResolver(OracleSource.FLUID_POOLED))
|
|
29
|
+
register_resolver(FluidResolver(OracleSource.FLUID_DEDICATED))
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"AbstractLendingPoolState",
|
|
33
|
+
"AbstractLoanState",
|
|
34
|
+
"LendingBook",
|
|
35
|
+
"LendingPriceBook",
|
|
36
|
+
"OraclePrice",
|
|
37
|
+
"OracleRef",
|
|
38
|
+
"OracleSource",
|
|
39
|
+
"PriceMap",
|
|
40
|
+
]
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
"""Read-only lending pool/loan state bases + aggregation views."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC
|
|
6
|
+
from abc import abstractmethod
|
|
7
|
+
from collections.abc import Sequence
|
|
8
|
+
from decimal import Decimal
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
from pycardano import Address
|
|
12
|
+
from pycardano import PlutusData
|
|
13
|
+
from pycardano import PlutusV1Script
|
|
14
|
+
from pycardano import PlutusV2Script
|
|
15
|
+
from pycardano import UTxO
|
|
16
|
+
|
|
17
|
+
from charli3_dendrite.dataclasses.models import Assets
|
|
18
|
+
from charli3_dendrite.dataclasses.models import DendriteBaseModel
|
|
19
|
+
from charli3_dendrite.dataclasses.models import PoolSelector
|
|
20
|
+
from charli3_dendrite.dataclasses.models import PoolStateInfo
|
|
21
|
+
from charli3_dendrite.dataclasses.models import PoolStateList
|
|
22
|
+
from charli3_dendrite.lending.oracles.models import OracleRef
|
|
23
|
+
from charli3_dendrite.lending.oracles.models import OracleSource
|
|
24
|
+
from charli3_dendrite.lending.oracles.models import PriceMap
|
|
25
|
+
from charli3_dendrite.lending.oracles.models import get_resolver
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from charli3_dendrite.backend.backend_base import AbstractBackend
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class AbstractLendingPoolState(DendriteBaseModel, ABC):
|
|
32
|
+
"""Snapshot of a lending pool/market (read-only)."""
|
|
33
|
+
|
|
34
|
+
assets: Assets
|
|
35
|
+
block_time: int
|
|
36
|
+
block_index: int
|
|
37
|
+
plutus_v2: bool
|
|
38
|
+
datum_cbor: str
|
|
39
|
+
datum_hash: str
|
|
40
|
+
tx_index: int
|
|
41
|
+
tx_hash: str
|
|
42
|
+
inactive: bool = False
|
|
43
|
+
|
|
44
|
+
_pool_datum_parsed: PlutusData | None = None
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def protocol(cls) -> str:
|
|
49
|
+
"""Official protocol name (e.g. 'Danogo', 'FluidTokens')."""
|
|
50
|
+
raise NotImplementedError
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def pool_policy(cls) -> list[str] | None:
|
|
54
|
+
"""Pool-NFT policy (or policy+name) used to recognize the pool."""
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
@abstractmethod
|
|
59
|
+
def pool_datum_class(cls) -> type[PlutusData]:
|
|
60
|
+
"""Plutus type for the pool datum."""
|
|
61
|
+
raise NotImplementedError
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def oracle_datum_class(cls) -> type[PlutusData] | None:
|
|
65
|
+
"""Optional protocol-owned oracle datum type."""
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
@abstractmethod
|
|
70
|
+
def pool_selector(cls) -> PoolSelector:
|
|
71
|
+
"""UTxO selector for this pool/market."""
|
|
72
|
+
raise NotImplementedError
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def reference_utxo(cls) -> UTxO | None:
|
|
76
|
+
"""Optional protocol reference-script UTxO."""
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def default_script_class(cls) -> type[PlutusV1Script | PlutusV2Script]:
|
|
81
|
+
"""Default Plutus script version for this protocol."""
|
|
82
|
+
return PlutusV1Script
|
|
83
|
+
|
|
84
|
+
def script_class(self) -> type[PlutusV1Script | PlutusV2Script]:
|
|
85
|
+
"""Plutus script version implied by this UTxO."""
|
|
86
|
+
return PlutusV2Script if self.plutus_v2 else PlutusV1Script
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
@abstractmethod
|
|
90
|
+
def stake_address(self) -> Address:
|
|
91
|
+
"""Stake address controlling this pool."""
|
|
92
|
+
raise NotImplementedError
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
@abstractmethod
|
|
96
|
+
def pool_id(self) -> str:
|
|
97
|
+
"""Stable identifier for this pool/market."""
|
|
98
|
+
raise NotImplementedError
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def pool_datum(self) -> PlutusData:
|
|
102
|
+
"""Parsed pool datum (lazily decoded from CBOR)."""
|
|
103
|
+
if self._pool_datum_parsed is None:
|
|
104
|
+
self._pool_datum_parsed = self.pool_datum_class().from_cbor(self.datum_cbor)
|
|
105
|
+
return self._pool_datum_parsed
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
@abstractmethod
|
|
109
|
+
def borrowable_unit(self) -> str:
|
|
110
|
+
"""Unit that can be borrowed from this pool."""
|
|
111
|
+
raise NotImplementedError
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
@abstractmethod
|
|
115
|
+
def utilization_ratio(self) -> Decimal:
|
|
116
|
+
"""Fraction of supplied liquidity currently borrowed."""
|
|
117
|
+
raise NotImplementedError
|
|
118
|
+
|
|
119
|
+
def oracle_refs(self) -> list[OracleRef]:
|
|
120
|
+
"""Oracle feeds this pool needs (e.g. alt-token rates). Default none."""
|
|
121
|
+
return []
|
|
122
|
+
|
|
123
|
+
@abstractmethod
|
|
124
|
+
def max_borrow_for_collateral_value(self, collateral_value_lovelace: int) -> int:
|
|
125
|
+
"""Best-effort quote: collateral value -> borrowable size."""
|
|
126
|
+
raise NotImplementedError
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class AbstractLoanState(DendriteBaseModel, ABC):
|
|
130
|
+
"""Snapshot of a single borrower position (read-only)."""
|
|
131
|
+
|
|
132
|
+
assets: Assets
|
|
133
|
+
block_time: int
|
|
134
|
+
block_index: int
|
|
135
|
+
plutus_v2: bool
|
|
136
|
+
datum_cbor: str
|
|
137
|
+
datum_hash: str
|
|
138
|
+
tx_index: int
|
|
139
|
+
tx_hash: str
|
|
140
|
+
inactive: bool = False
|
|
141
|
+
|
|
142
|
+
_loan_datum_parsed: PlutusData | None = None
|
|
143
|
+
|
|
144
|
+
@classmethod
|
|
145
|
+
@abstractmethod
|
|
146
|
+
def protocol(cls) -> str:
|
|
147
|
+
"""Official protocol name (e.g. 'Danogo', 'FluidTokens')."""
|
|
148
|
+
raise NotImplementedError
|
|
149
|
+
|
|
150
|
+
@classmethod
|
|
151
|
+
def protocol_nft_policy(cls) -> list[str] | None:
|
|
152
|
+
"""Policy / policy+name prefixes for the loan-identity NFT."""
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
@classmethod
|
|
156
|
+
@abstractmethod
|
|
157
|
+
def loan_datum_class(cls) -> type[PlutusData]:
|
|
158
|
+
"""Plutus type for the loan datum."""
|
|
159
|
+
raise NotImplementedError
|
|
160
|
+
|
|
161
|
+
@classmethod
|
|
162
|
+
def oracle_datum_class(cls) -> type[PlutusData] | None:
|
|
163
|
+
"""Optional protocol-owned oracle datum type."""
|
|
164
|
+
return None
|
|
165
|
+
|
|
166
|
+
@classmethod
|
|
167
|
+
@abstractmethod
|
|
168
|
+
def loan_selector(cls) -> PoolSelector:
|
|
169
|
+
"""UTxO selector for loans (same shape as pool_selector)."""
|
|
170
|
+
raise NotImplementedError
|
|
171
|
+
|
|
172
|
+
@classmethod
|
|
173
|
+
def reference_utxo(cls) -> UTxO | None:
|
|
174
|
+
"""Optional protocol reference-script UTxO."""
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
@classmethod
|
|
178
|
+
def default_script_class(cls) -> type[PlutusV1Script | PlutusV2Script]:
|
|
179
|
+
"""Default Plutus script version for this protocol."""
|
|
180
|
+
return PlutusV1Script
|
|
181
|
+
|
|
182
|
+
def script_class(self) -> type[PlutusV1Script | PlutusV2Script]:
|
|
183
|
+
"""Plutus script version implied by this UTxO."""
|
|
184
|
+
return PlutusV2Script if self.plutus_v2 else PlutusV1Script
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
@abstractmethod
|
|
188
|
+
def stake_address(self) -> Address:
|
|
189
|
+
"""Stake address controlling this loan."""
|
|
190
|
+
raise NotImplementedError
|
|
191
|
+
|
|
192
|
+
@property
|
|
193
|
+
@abstractmethod
|
|
194
|
+
def loan_id(self) -> str:
|
|
195
|
+
"""Stable identifier for this loan."""
|
|
196
|
+
raise NotImplementedError
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
@abstractmethod
|
|
200
|
+
def pool_id(self) -> str:
|
|
201
|
+
"""Identifier of the pool/market this loan belongs to."""
|
|
202
|
+
raise NotImplementedError
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def loan_datum(self) -> PlutusData:
|
|
206
|
+
"""Parsed loan datum (lazily decoded from CBOR)."""
|
|
207
|
+
if self._loan_datum_parsed is None:
|
|
208
|
+
self._loan_datum_parsed = self.loan_datum_class().from_cbor(self.datum_cbor)
|
|
209
|
+
return self._loan_datum_parsed
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
@abstractmethod
|
|
213
|
+
def borrowed_unit(self) -> str:
|
|
214
|
+
"""Unit borrowed by this loan."""
|
|
215
|
+
raise NotImplementedError
|
|
216
|
+
|
|
217
|
+
def oracle_refs(self) -> list[OracleRef]:
|
|
218
|
+
"""Oracle feeds needed to value this loan. Default none (fixed-ratio)."""
|
|
219
|
+
return []
|
|
220
|
+
|
|
221
|
+
@abstractmethod
|
|
222
|
+
def current_debt(self) -> int:
|
|
223
|
+
"""Principal + accrued interest, in smallest units of borrowed asset."""
|
|
224
|
+
raise NotImplementedError
|
|
225
|
+
|
|
226
|
+
@abstractmethod
|
|
227
|
+
def collateral_value_lovelace(self, prices: PriceMap) -> int:
|
|
228
|
+
"""Collateral value in lovelace at the given prices."""
|
|
229
|
+
raise NotImplementedError
|
|
230
|
+
|
|
231
|
+
@abstractmethod
|
|
232
|
+
def health_factor(self, prices: PriceMap) -> Decimal:
|
|
233
|
+
"""Liquidation ratio; default rule liquidatable when <= 1."""
|
|
234
|
+
raise NotImplementedError
|
|
235
|
+
|
|
236
|
+
def is_liquidatable(self, prices: PriceMap) -> bool:
|
|
237
|
+
"""Default: health_factor <= 1. Override if on-chain rule differs."""
|
|
238
|
+
return self.health_factor(prices) <= 1
|
|
239
|
+
|
|
240
|
+
def repay_amount_for_target_hf(self, target_hf: Decimal, prices: PriceMap) -> int:
|
|
241
|
+
"""Repayment to reach `target_hf`.
|
|
242
|
+
|
|
243
|
+
Generic helper assuming the borrowed asset is denominated in lovelace
|
|
244
|
+
(i.e. collateral_value_lovelace and debt share units). Protocols whose
|
|
245
|
+
borrowed asset is not ADA should override.
|
|
246
|
+
"""
|
|
247
|
+
if target_hf <= 0:
|
|
248
|
+
return self.current_debt()
|
|
249
|
+
debt = Decimal(self.current_debt())
|
|
250
|
+
max_safe_debt = Decimal(self.collateral_value_lovelace(prices)) / target_hf
|
|
251
|
+
if debt <= max_safe_debt:
|
|
252
|
+
return 0
|
|
253
|
+
return int((debt - max_safe_debt).to_integral_value(rounding="ROUND_CEILING"))
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class LendingPriceBook:
|
|
257
|
+
"""Batch oracle resolution for a set of loans/pools.
|
|
258
|
+
|
|
259
|
+
Selectors are de-duplicated within each source group, so each unique feed
|
|
260
|
+
is fetched once (one fetch per unique selector per source) no matter how
|
|
261
|
+
many loans reference it.
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
@staticmethod
|
|
265
|
+
def build(
|
|
266
|
+
items: Sequence[AbstractLoanState | AbstractLendingPoolState],
|
|
267
|
+
*,
|
|
268
|
+
backend: AbstractBackend,
|
|
269
|
+
at_ms: int | None = None, # noqa: ARG004
|
|
270
|
+
) -> PriceMap:
|
|
271
|
+
"""Resolve every source referenced by `items` into one `PriceMap`."""
|
|
272
|
+
refs: list[OracleRef] = []
|
|
273
|
+
for item in items:
|
|
274
|
+
refs.extend(item.oracle_refs())
|
|
275
|
+
|
|
276
|
+
by_source: dict[OracleSource, list[OracleRef]] = {}
|
|
277
|
+
for ref in refs:
|
|
278
|
+
by_source.setdefault(ref.source, []).append(ref)
|
|
279
|
+
|
|
280
|
+
price_map = PriceMap()
|
|
281
|
+
for source, group in by_source.items():
|
|
282
|
+
resolver = get_resolver(source)
|
|
283
|
+
seen: set[tuple] = set()
|
|
284
|
+
infos: list[PoolStateInfo] = []
|
|
285
|
+
for selector in resolver.selectors(group):
|
|
286
|
+
key = (
|
|
287
|
+
tuple(sorted(selector.addresses)),
|
|
288
|
+
tuple(sorted(selector.assets or ())),
|
|
289
|
+
)
|
|
290
|
+
if key in seen:
|
|
291
|
+
continue
|
|
292
|
+
seen.add(key)
|
|
293
|
+
infos.extend(list(backend.get_pool_utxos(**selector.model_dump())))
|
|
294
|
+
utxos = PoolStateList(root=infos)
|
|
295
|
+
for ref in group:
|
|
296
|
+
price = resolver.resolve(ref, utxos)
|
|
297
|
+
if price is not None:
|
|
298
|
+
price_map.add(price)
|
|
299
|
+
return price_map
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
class LendingBook:
|
|
303
|
+
"""Aggregated, price-aware view of loans for bots and risk analytics."""
|
|
304
|
+
|
|
305
|
+
def __init__(
|
|
306
|
+
self,
|
|
307
|
+
loans_full: list[AbstractLoanState],
|
|
308
|
+
*,
|
|
309
|
+
block_time: int,
|
|
310
|
+
prices: PriceMap | None = None,
|
|
311
|
+
pool: AbstractLendingPoolState | None = None,
|
|
312
|
+
) -> None:
|
|
313
|
+
"""Store loans plus optional prices/pool context for aggregation."""
|
|
314
|
+
self.loans_full = list(loans_full)
|
|
315
|
+
self.block_time = block_time
|
|
316
|
+
self.prices = prices or PriceMap()
|
|
317
|
+
self.pool = pool
|
|
318
|
+
|
|
319
|
+
@classmethod
|
|
320
|
+
def from_loans(
|
|
321
|
+
cls,
|
|
322
|
+
loans: Sequence[AbstractLoanState],
|
|
323
|
+
*,
|
|
324
|
+
prices: PriceMap | None = None,
|
|
325
|
+
pool: AbstractLendingPoolState | None = None,
|
|
326
|
+
block_time: int | None = None,
|
|
327
|
+
) -> LendingBook:
|
|
328
|
+
"""Construct a book from `loans`, defaulting `block_time` to the latest."""
|
|
329
|
+
bt = (
|
|
330
|
+
block_time
|
|
331
|
+
if block_time is not None
|
|
332
|
+
else max((ln.block_time for ln in loans), default=0)
|
|
333
|
+
)
|
|
334
|
+
return cls(list(loans), block_time=bt, prices=prices, pool=pool)
|
|
335
|
+
|
|
336
|
+
def active_loans(self) -> list[AbstractLoanState]:
|
|
337
|
+
"""Loans that are not marked inactive."""
|
|
338
|
+
return [ln for ln in self.loans_full if not ln.inactive]
|
|
339
|
+
|
|
340
|
+
def liquidatable_loans(self) -> list[AbstractLoanState]:
|
|
341
|
+
"""Active loans whose health factor makes them liquidatable."""
|
|
342
|
+
return [ln for ln in self.active_loans() if ln.is_liquidatable(self.prices)]
|
|
343
|
+
|
|
344
|
+
def liquidation_candidates_by_urgency(self) -> list[AbstractLoanState]:
|
|
345
|
+
"""Liquidatable loans sorted by ascending health factor."""
|
|
346
|
+
scored = [
|
|
347
|
+
(ln, ln.health_factor(self.prices)) for ln in self.liquidatable_loans()
|
|
348
|
+
]
|
|
349
|
+
scored.sort(key=lambda item: (item[1], item[0].loan_id))
|
|
350
|
+
return [ln for ln, _ in scored]
|
|
351
|
+
|
|
352
|
+
def near_liquidation(
|
|
353
|
+
self,
|
|
354
|
+
buffer: Decimal = Decimal("0.05"),
|
|
355
|
+
) -> list[AbstractLoanState]:
|
|
356
|
+
"""Healthy active loans within `buffer` above the liquidation threshold."""
|
|
357
|
+
hi = Decimal(1) + buffer
|
|
358
|
+
scored = [
|
|
359
|
+
(ln, ln.health_factor(self.prices))
|
|
360
|
+
for ln in self.active_loans()
|
|
361
|
+
if not ln.is_liquidatable(self.prices)
|
|
362
|
+
]
|
|
363
|
+
scored = [(ln, hf) for ln, hf in scored if hf <= hi]
|
|
364
|
+
scored.sort(key=lambda item: (item[1], item[0].loan_id))
|
|
365
|
+
return [ln for ln, _ in scored]
|
|
366
|
+
|
|
367
|
+
def by_pool(self, pool_id: str) -> list[AbstractLoanState]:
|
|
368
|
+
"""Active loans belonging to `pool_id`."""
|
|
369
|
+
return [ln for ln in self.active_loans() if ln.pool_id == pool_id]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Read-only Danogo Float-Rate Lending analytics.
|
|
2
|
+
|
|
3
|
+
Importing this module registers the Danogo aggregator resolver. Danogo classes do not
|
|
4
|
+
subclass DEX bases and are not exported from `charli3_dendrite/__init__.py`.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from charli3_dendrite.lending.danogo.loader import build_book
|
|
8
|
+
from charli3_dendrite.lending.danogo.market import DanogoMarket
|
|
9
|
+
from charli3_dendrite.lending.danogo.oracles.aggregator import DanogoAggregatorResolver
|
|
10
|
+
from charli3_dendrite.lending.danogo.state import DanogoLoanState
|
|
11
|
+
from charli3_dendrite.lending.danogo.state import DanogoPoolState
|
|
12
|
+
from charli3_dendrite.lending.oracles.models import register_resolver
|
|
13
|
+
|
|
14
|
+
register_resolver(DanogoAggregatorResolver())
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"DanogoLoanState",
|
|
18
|
+
"DanogoMarket",
|
|
19
|
+
"DanogoPoolState",
|
|
20
|
+
"build_book",
|
|
21
|
+
]
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Danogo deployment anchors + on-chain address resolution.
|
|
2
|
+
|
|
3
|
+
Anchors are None until supplied. With an anchor, `resolve_addresses` reads the Protocol
|
|
4
|
+
Config UTxO's ProtocolDatum and derives the four script addresses.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
from typing import TypedDict
|
|
11
|
+
|
|
12
|
+
from pycardano import Address
|
|
13
|
+
from pycardano import Network
|
|
14
|
+
from pycardano import ScriptHash
|
|
15
|
+
|
|
16
|
+
from charli3_dendrite.lending.danogo.datums import ProtocolDatum
|
|
17
|
+
from charli3_dendrite.lending.danogo.oracles.locator import LeafHandle
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from charli3_dendrite.backend.backend_base import AbstractBackend
|
|
21
|
+
|
|
22
|
+
# Deployment anchors (Cardano mainnet). NFT anchors are policy(56 hex)+asset_name(hex);
|
|
23
|
+
# the path anchor is policy-only. The Oracle Global Config NFT and the Oracle Path FTs
|
|
24
|
+
# share a single minting policy.
|
|
25
|
+
PROTOCOL_CONFIG_NFT: str | None = (
|
|
26
|
+
"bc2d3b7cf1009c788b1daf2208a38026ab20d7f8209d896ad1a09c14"
|
|
27
|
+
"e567b64450c406a17f869eb4e430f2e2a6c74d103edb988cb1bc1347de285c69"
|
|
28
|
+
)
|
|
29
|
+
ORACLE_GLOBAL_CONFIG_NFT: str | None = (
|
|
30
|
+
"bd23e2977937bb6054f625e5b566e28e294dcd8f7e8d0d33979ed90c"
|
|
31
|
+
"1e5e4d7ac0ba99f5f4b3d3c7764054ccce87ef497407785a4ee04d2beadb0b78"
|
|
32
|
+
)
|
|
33
|
+
ORACLE_PATH_FT_POLICY: str | None = (
|
|
34
|
+
"bd23e2977937bb6054f625e5b566e28e294dcd8f7e8d0d33979ed90c"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# Per-supply-token oracle routing anchor. Each supply token's price config is anchored
|
|
38
|
+
# to one Danogo reference pool: the `OraclePathDatum.anchor` (28 bytes) is the terminal
|
|
39
|
+
# bridge that converts every collateral price into the supply token. Many path-config
|
|
40
|
+
# UTxOs (across older config versions and other supply tokens) share the path FT policy;
|
|
41
|
+
# the create-loan oracle Withdraw validator only accepts the path datum anchored to the
|
|
42
|
+
# borrowed market's supply token. Keyed by supply-token unit ("lovelace" for ADA).
|
|
43
|
+
ORACLE_QUOTE_ANCHOR: dict[str, str] = {
|
|
44
|
+
"lovelace": "836cc68931c2e4e3e838602eca1902591d216837bafddfe6f0c8cb07",
|
|
45
|
+
# USDM-supply market routing anchor.
|
|
46
|
+
"c48cbb3d5e57ed56e276bc45f99ab39abe94e6cd7ac39fb402da47ad0014df105553444d": (
|
|
47
|
+
"64e648e02d76567652d55e33106123f72ceac04bf3dcaf4f70737d58"
|
|
48
|
+
),
|
|
49
|
+
# DjedMicroUSD-supply market routing anchor.
|
|
50
|
+
(
|
|
51
|
+
"8db269c3ec630e06ae29f74bc39edd1f87c819f1056206e879a1cd61"
|
|
52
|
+
"446a65644d6963726f555344"
|
|
53
|
+
): "504791c3a0348e8ebadac99d5df8753b1b12e3849b8bd69653e5db92",
|
|
54
|
+
# USDC-supply market routing anchor.
|
|
55
|
+
(
|
|
56
|
+
"1f3aec8bfe7ea4fe14c5f121e2a92e301afe414147860d557cac7e34" "5553444378"
|
|
57
|
+
): "853fca625371e99f0e0b94663f3513dd99ec141bac5dad8128b41db6",
|
|
58
|
+
# USDA-supply market routing anchor.
|
|
59
|
+
(
|
|
60
|
+
"fe7c786ab321f41c654ef6c1af7b3250a613c24e4213e0425a7ae456" "55534441"
|
|
61
|
+
): "a8fe3b72f0195c5af7a9df18402ba5ec77938f5795da7bb738653c9d",
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# Minswap V2 LP minting policy. The oracle path config encodes a Minswap LP source as
|
|
65
|
+
# the LP token *name* (32 bytes) under this policy; matching a borrowed collateral's
|
|
66
|
+
# resolved Minswap LP leaf to a path config's sources confirms the config prices it.
|
|
67
|
+
MINSWAP_LP_POLICY: str = "f5808c2c990d86da54bfc97d89cee6efa20cd8461616359478d96b4c"
|
|
68
|
+
|
|
69
|
+
# Curated cross-check oracle feeds per cross-quote supply token (keyed by supply-token
|
|
70
|
+
# unit). A cross-quote market's ``ada -> quote`` conversion is deviation-checked by the
|
|
71
|
+
# routing path config against independent external price feeds that the mined pricing
|
|
72
|
+
# recipes never reference (no recipe step names them), so the recipe-driven leaf
|
|
73
|
+
# resolution does not fetch them. The oracle ``Withdraw`` still walks them, so a
|
|
74
|
+
# fully-live cross-quote build must additionally fetch these feed UTxOs; the compose
|
|
75
|
+
# filter then references only the candidate whose rate is within the path config's
|
|
76
|
+
# deviation tolerance. Each handle is resolved through the same single-UTxO
|
|
77
|
+
# discriminator the recipe leaves use. Only cross-quote supply tokens (those with a
|
|
78
|
+
# canonical ``ada -> quote`` recipe) appear here; ADA-quote / single-hop markets need
|
|
79
|
+
# none.
|
|
80
|
+
CROSS_CHECK_SUPPLEMENTAL_HANDLES: dict[str, tuple[LeafHandle, ...]] = {
|
|
81
|
+
# USDCx-supply market: Indigo iUSD ``ada -> quote`` feed (a singleton UTxO pinned
|
|
82
|
+
# by its feed NFT at a stable script address), parsed as ``OracleUtxoType.TINDIGO``.
|
|
83
|
+
"1f3aec8bfe7ea4fe14c5f121e2a92e301afe414147860d557cac7e345553444378": (
|
|
84
|
+
LeafHandle(
|
|
85
|
+
otype="TINDIGO",
|
|
86
|
+
address="addr1wygyy4mdrh5kxsmm6ja4phxez3vh2mngz2muqmw4gw3n9jqdu67a0",
|
|
87
|
+
nft_policy="e3455f2715338b454fb853442f72dc03b98396854f97510027fe22ff",
|
|
88
|
+
nft_name="695553443230323231313138313935393036",
|
|
89
|
+
),
|
|
90
|
+
),
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# Script credentials that hold the config/oracle reference UTxOs. Backends match
|
|
94
|
+
# UTxOs by payment credential and don't support NFT-only lookups, so resolution
|
|
95
|
+
# starts from these addresses: PROTOCOL_CONFIG_SKH holds the Protocol Config UTxO,
|
|
96
|
+
# and ORACLE_DATA_SKH holds the Oracle Global Config UTxO and the Oracle Path FTs.
|
|
97
|
+
PROTOCOL_CONFIG_SKH: str | None = (
|
|
98
|
+
"9a0054cf2ab678edcd7528d16a388e5b1cefa3af40d5b9634ab96de6"
|
|
99
|
+
)
|
|
100
|
+
ORACLE_DATA_SKH: str | None = "79a5116ee2ff13ffd53b2615568574fc804740a07bc2ff8930d2d6fb"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class DanogoScriptAddresses(TypedDict):
|
|
104
|
+
"""The four Danogo script addresses derived from the ProtocolDatum."""
|
|
105
|
+
|
|
106
|
+
pool: str
|
|
107
|
+
loan: str
|
|
108
|
+
config_pool: str
|
|
109
|
+
oracle: str
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _addr(skh_hex: str) -> str:
|
|
113
|
+
# Returns a payment-credential (enterprise, no-stake) script *identity*. On-chain
|
|
114
|
+
# pool/loan UTxOs may sit at base addresses with a stake part, so callers must match
|
|
115
|
+
# by payment credential, not raw address-string equality (some backends match raw
|
|
116
|
+
# address strings).
|
|
117
|
+
return Address(
|
|
118
|
+
payment_part=ScriptHash(bytes.fromhex(skh_hex)),
|
|
119
|
+
network=Network.MAINNET,
|
|
120
|
+
).encode()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def resolve_addresses(
|
|
124
|
+
backend: AbstractBackend,
|
|
125
|
+
*,
|
|
126
|
+
config_nft: str | None = None,
|
|
127
|
+
config_address: str | None = None,
|
|
128
|
+
) -> DanogoScriptAddresses:
|
|
129
|
+
"""Read ProtocolDatum at the Protocol Config UTxO; return the 4 script addresses.
|
|
130
|
+
|
|
131
|
+
Backends match UTxOs by payment credential and don't support NFT-only lookups, so
|
|
132
|
+
resolution needs the address that holds the Protocol Config NFT. It defaults to
|
|
133
|
+
`PROTOCOL_CONFIG_SKH`; pass `config_address` to point at another deployment.
|
|
134
|
+
"""
|
|
135
|
+
nft = config_nft or PROTOCOL_CONFIG_NFT
|
|
136
|
+
if nft is None:
|
|
137
|
+
raise ValueError("PROTOCOL_CONFIG_NFT anchor not set")
|
|
138
|
+
if config_address is None and PROTOCOL_CONFIG_SKH is not None:
|
|
139
|
+
config_address = _addr(PROTOCOL_CONFIG_SKH)
|
|
140
|
+
addresses = [config_address] if config_address else []
|
|
141
|
+
rows = list(
|
|
142
|
+
backend.get_pool_utxos(
|
|
143
|
+
addresses=addresses,
|
|
144
|
+
assets=[nft],
|
|
145
|
+
limit=1,
|
|
146
|
+
historical=False,
|
|
147
|
+
),
|
|
148
|
+
)
|
|
149
|
+
if not rows:
|
|
150
|
+
raise ValueError("Protocol Config UTxO not found for anchor")
|
|
151
|
+
d = ProtocolDatum.from_cbor(rows[0].datum_cbor)
|
|
152
|
+
return DanogoScriptAddresses(
|
|
153
|
+
pool=_addr(d.pool_skh.hex()),
|
|
154
|
+
loan=_addr(d.loan_skh.hex()),
|
|
155
|
+
config_pool=_addr(d.config_pool_skh.hex()),
|
|
156
|
+
oracle=_addr(d.oracle_skh.hex()),
|
|
157
|
+
)
|