charli3_dendrite 1.5.8.dev0__tar.gz → 1.5.9.dev0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/PKG-INFO +1 -1
  2. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/pyproject.toml +2 -2
  3. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dexs/amm/dano.py +138 -44
  4. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/loader.py +48 -11
  5. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/oracles/forward.py +0 -4
  6. charli3_dendrite-1.5.9.dev0/src/charli3_dendrite/lending/danogo/oracles/locator.py +300 -0
  7. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/state.py +1 -1
  8. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/state.py +2 -2
  9. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/oracles/models.py +49 -13
  10. charli3_dendrite-1.5.8.dev0/src/charli3_dendrite/lending/danogo/oracles/locator.py +0 -140
  11. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/LICENSE +0 -0
  12. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/README.md +0 -0
  13. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/__init__.py +0 -0
  14. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/backend/__init__.py +0 -0
  15. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/backend/backend_base.py +0 -0
  16. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/backend/blockfrost/__init__.py +0 -0
  17. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/backend/blockfrost/models.py +0 -0
  18. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/backend/dbsync/__init__.py +0 -0
  19. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/backend/dbsync/models.py +0 -0
  20. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/backend/ogmios_kupo/__init__.py +0 -0
  21. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/backend/ogmios_kupo/models.py +0 -0
  22. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/backend/utils.py +0 -0
  23. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dataclasses/__init__.py +0 -0
  24. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dataclasses/datums.py +0 -0
  25. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dataclasses/models.py +0 -0
  26. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dexs/__init__.py +0 -0
  27. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dexs/amm/__init__.py +0 -0
  28. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dexs/amm/amm_base.py +0 -0
  29. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dexs/amm/amm_types.py +0 -0
  30. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dexs/amm/cswap.py +0 -0
  31. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dexs/amm/minswap.py +0 -0
  32. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dexs/amm/muesli.py +0 -0
  33. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dexs/amm/spectrum.py +0 -0
  34. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dexs/amm/splash.py +0 -0
  35. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dexs/amm/sundae.py +0 -0
  36. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dexs/amm/vyfi.py +0 -0
  37. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dexs/amm/wingriders.py +0 -0
  38. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dexs/core/__init__.py +0 -0
  39. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dexs/core/base.py +0 -0
  40. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dexs/core/errors.py +0 -0
  41. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dexs/ob/__init__.py +0 -0
  42. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dexs/ob/axo.py +0 -0
  43. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dexs/ob/cardanoswaps.py +0 -0
  44. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dexs/ob/chadswap.py +0 -0
  45. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dexs/ob/djed.py +0 -0
  46. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dexs/ob/geniusyield.py +0 -0
  47. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dexs/ob/ob_base.py +0 -0
  48. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/dexs/ob/saturnswap.py +0 -0
  49. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/__init__.py +0 -0
  50. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/base.py +0 -0
  51. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/__init__.py +0 -0
  52. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/constants.py +0 -0
  53. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/datums.py +0 -0
  54. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/market.py +0 -0
  55. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/math.py +0 -0
  56. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/oracles/__init__.py +0 -0
  57. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/oracles/aggregator.py +0 -0
  58. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/oracles/aggregator_datums.py +0 -0
  59. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/oracles/deployments.py +0 -0
  60. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/oracles/leaves.py +0 -0
  61. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/oracles/lp_pricing.py +0 -0
  62. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/oracles/recipes.json +0 -0
  63. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/oracles/redeemer.py +0 -0
  64. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/oracles/reproduce.py +0 -0
  65. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/oracles/structured_datums.py +0 -0
  66. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/transactions/__init__.py +0 -0
  67. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/transactions/_common.py +0 -0
  68. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/transactions/build.py +0 -0
  69. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/transactions/builder.py +0 -0
  70. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/transactions/context.py +0 -0
  71. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/transactions/create_loan.py +0 -0
  72. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/transactions/datum_synth.py +0 -0
  73. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/transactions/increase_loan.py +0 -0
  74. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/transactions/loan_ids.py +0 -0
  75. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/transactions/modify_collateral.py +0 -0
  76. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/transactions/oracle_redeemer.py +0 -0
  77. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/transactions/oracle_synth.py +0 -0
  78. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/transactions/redeemers.py +0 -0
  79. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/transactions/repay.py +0 -0
  80. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/transactions/topup_withdraw.py +0 -0
  81. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/danogo/transactions/verify.py +0 -0
  82. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/__init__.py +0 -0
  83. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/constants.py +0 -0
  84. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/datums.py +0 -0
  85. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/loader.py +0 -0
  86. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/market.py +0 -0
  87. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/math.py +0 -0
  88. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/oracles/__init__.py +0 -0
  89. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/oracles/feeds.py +0 -0
  90. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/oracles/fluid_api.py +0 -0
  91. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/oracles/witness.py +0 -0
  92. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/transactions/__init__.py +0 -0
  93. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/transactions/_common.py +0 -0
  94. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/transactions/borrow.py +0 -0
  95. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/transactions/builder.py +0 -0
  96. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/transactions/change_collateral.py +0 -0
  97. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/transactions/context.py +0 -0
  98. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/transactions/datum_synth.py +0 -0
  99. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/transactions/lend.py +0 -0
  100. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/transactions/pool.py +0 -0
  101. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/transactions/recast.py +0 -0
  102. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/transactions/redeemers.py +0 -0
  103. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/transactions/repay.py +0 -0
  104. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/transactions/request.py +0 -0
  105. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/fluidtokens/transactions/resolve.py +0 -0
  106. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/math.py +0 -0
  107. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/oracles/__init__.py +0 -0
  108. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/oracles/charli3.py +0 -0
  109. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/oracles/dex_pooled.py +0 -0
  110. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/oracles/fluid.py +0 -0
  111. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/oracles/orcfax.py +0 -0
  112. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/oracles/qtoken.py +0 -0
  113. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/registry.py +0 -0
  114. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/transactions/__init__.py +0 -0
  115. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/transactions/base.py +0 -0
  116. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/transactions/infra.py +0 -0
  117. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/transactions/snapshot.py +0 -0
  118. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/lending/units.py +0 -0
  119. {charli3_dendrite-1.5.8.dev0 → charli3_dendrite-1.5.9.dev0}/src/charli3_dendrite/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: charli3_dendrite
3
- Version: 1.5.8.dev0
3
+ Version: 1.5.9.dev0
4
4
  Summary:
5
5
  License-File: LICENSE
6
6
  Author: Elder Millenial
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "charli3_dendrite"
3
- version = "1.5.8-dev0"
3
+ version = "1.5.9-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.5.8-dev0"
53
+ current_version = "1.5.9-dev0"
54
54
  parse = """(?x)
55
55
  (?P<major>\\d+)\\.
56
56
  (?P<minor>\\d+)\\.
@@ -35,8 +35,10 @@ from pycardano import TransactionId
35
35
  from pycardano import TransactionInput
36
36
  from pycardano import TransactionOutput
37
37
  from pycardano import UTxO
38
+ from pycardano import Value
38
39
  from pycardano import Withdrawals
39
40
  from pycardano import plutus_script_hash
41
+ from pycardano.serialization import ByteString
40
42
  from pycardano.serialization import CBORSerializable
41
43
 
42
44
  from charli3_dendrite.backend import get_backend
@@ -204,19 +206,44 @@ def parse_swap_redeemer_bytes(
204
206
  return first_byte, action, entries
205
207
 
206
208
 
209
+ def _value_to_assets(value: Value) -> Assets:
210
+ """Convert a pycardano ``Value`` (a raw on-chain UTxO amount) to ``Assets``.
211
+
212
+ Used to seed the new pool output from the spent pool UTxO's exact value bag
213
+ so the swap only mutates the traded reserves + swap-fee lovelace, leaving the
214
+ pool's min-ADA, dex NFT, and any other assets untouched (the withdraw
215
+ validator requires the continuation to preserve them).
216
+ """
217
+ assets = Assets(root={"lovelace": int(value.coin)})
218
+ multi = getattr(value, "multi_asset", None)
219
+ if multi:
220
+ for policy, names in multi.data.items():
221
+ for asset_name, quantity in names.data.items():
222
+ unit = policy.payload.hex() + asset_name.payload.hex()
223
+ assets.root[unit] = int(quantity)
224
+ return assets
225
+
226
+
207
227
  @dataclass
208
228
  class DanoSwapRedeemer(CBORSerializable):
209
- """Single-pool Dano swap redeemer (Spend or Withdraw).
210
-
211
- The on-chain redeemer Plutus Data is the raw swap payload serialized as a
212
- CBOR *byte string* (``0x58<len><payload>``); :meth:`to_primitive` returns
213
- the payload so it serializes that way. The pool input index and the first
214
- byte (``pool_in_idx`` for Spend, the protocol-config reference index for
215
- Withdraw) depend on the FINAL transaction ordering, so they are resolved
216
- post-build by :meth:`set_idx`, which the tx builder calls after sorting and
217
- indexing inputs (mirrors ``SSPoolRedeemer.set_idx`` and the clmm-sdk's
218
- ``RedeemerArg`` callback). ``pool_out_idx`` is the pool's position in the
219
- swap's batch vector — ``0`` for a single-pool swap — NOT a tx output index.
229
+ """Dano swap redeemer (Spend or Withdraw) for a batch of >= 1 pool spends.
230
+
231
+ A Dano swap tx spends N >= 1 pool UTxOs governed by a single withdraw-zero:
232
+ the tx carries N Spend redeemers + one Withdraw redeemer, and EVERY redeemer
233
+ carries the same batch vector of per-pool ``(pool_in_idx, pool_out_idx,
234
+ delta)`` entries. Only the leading ``first_byte`` differs — a Spend's is its
235
+ own pool input index; the Withdraw's is the protocol-config reference index.
236
+ ``pool_in_idx`` is the pool's transaction input index; ``pool_out_idx`` is the
237
+ pool's position in the batch vector (its append index ``0..N-1``), NOT a tx
238
+ output index. The input index depends on the FINAL sorted inputs, so the
239
+ batch is resolved post-build by :meth:`set_idx`, which the tx builder calls
240
+ after sorting and indexing inputs (mirrors ``SSPoolRedeemer.set_idx`` and the
241
+ clmm-sdk's ``RedeemerArg`` callback).
242
+
243
+ ``swap_utxo`` accumulates the batch on ``tx_builder._dano_swap_batch`` as each
244
+ pool is folded in, so a single-pool call and a composed multi-pool tx share
245
+ one code path. The on-chain redeemer Plutus Data is the raw swap payload
246
+ serialized as a CBOR byte string; :meth:`to_primitive` returns the payload.
220
247
  """
221
248
 
222
249
  pool_input: TransactionInput
@@ -240,8 +267,32 @@ class DanoSwapRedeemer(CBORSerializable):
240
267
  return i
241
268
  raise InvalidPoolError("redeemer UTxO not found in transaction during set_idx")
242
269
 
270
+ def _resolve_entries(
271
+ self,
272
+ tx_builder: TransactionBuilder,
273
+ ) -> list[tuple[int, int, int]]:
274
+ """Resolve the shared batch entry vector in pool-batch order.
275
+
276
+ Every Spend and the single Withdraw redeemer of a Dano tx carry the same
277
+ vector of ``(pool_in_idx, pool_out_idx, delta)`` entries — one per pool
278
+ folded into ``tx_builder._dano_swap_batch`` (append order). Mirrors the
279
+ clmm-sdk ``swapTokensRedeemer``: ``pool_in_idx`` is the pool's transaction
280
+ input index; ``pool_out_idx`` is the pool's position in the batch vector
281
+ (its append index ``0..N-1``), NOT a tx output index; ``delta`` is that
282
+ pool's signed swap amount. Order is the batch append order (no sort).
283
+ """
284
+ batch = getattr(tx_builder, "_dano_swap_batch", None)
285
+ if not batch:
286
+ batch = [(self.pool_input, self.delta_amount)]
287
+ inputs = list(tx_builder.inputs)
288
+ return [
289
+ (self._sorted_index(inputs, pool_input), pool_out_idx, delta)
290
+ for pool_out_idx, (pool_input, delta) in enumerate(batch)
291
+ ]
292
+
243
293
  def set_idx(self, tx_builder: TransactionBuilder) -> None:
244
- """Resolve pool/config indices from the final (sorted) transaction."""
294
+ """Resolve the shared batch + this redeemer's first byte post-build."""
295
+ self._entries = self._resolve_entries(tx_builder)
245
296
  self.pool_in_idx = self._sorted_index(list(tx_builder.inputs), self.pool_input)
246
297
  if self.is_withdraw:
247
298
  if self.protocol_config_input is None:
@@ -255,13 +306,22 @@ class DanoSwapRedeemer(CBORSerializable):
255
306
  else:
256
307
  self.first_byte = self.pool_in_idx
257
308
 
258
- def to_primitive(self) -> bytes:
259
- """Serialize the redeemer as the packed swap payload bytestring."""
260
- return build_swap_redeemer_bytes(
261
- delta_amount=self.delta_amount,
262
- pool_in_idx=self.pool_in_idx,
263
- pool_out_idx=self.pool_out_idx,
264
- first_byte=self.first_byte,
309
+ def to_primitive(self) -> ByteString:
310
+ """Serialize the redeemer as the packed batch swap payload bytestring.
311
+
312
+ Wrapped in :class:`ByteString` so a payload longer than 64 bytes (any
313
+ batch of >= 2 pools: 2 + 34*N bytes) serializes as a CBOR
314
+ indefinite-length chunked bytestring, as the on-chain validator requires;
315
+ a single-pool payload (36 bytes) stays a definite bytestring.
316
+ """
317
+ entries = getattr(self, "_entries", None)
318
+ if entries is None:
319
+ entries = [(self.pool_in_idx, self.pool_out_idx, self.delta_amount)]
320
+ return ByteString(
321
+ build_batch_swap_redeemer_bytes(
322
+ first_byte=self.first_byte,
323
+ entries=entries,
324
+ ),
265
325
  )
266
326
 
267
327
  @classmethod
@@ -732,15 +792,20 @@ class DanoCLMMState(AbstractConstantLiquidityPoolState):
732
792
 
733
793
  def _new_pool_assets(
734
794
  self,
795
+ base_assets: Assets,
735
796
  pool_change_x: int,
736
797
  pool_change_y: int,
737
798
  swap_fee: int,
738
799
  staking_reward: int = 0,
739
800
  ) -> Assets:
740
801
  d = self._datum
741
- # Build the new output from the GROSS bag — the on-chain UTxO must keep
742
- # the platform fees + min-ADA that ``assets`` (net) no longer carries.
743
- new = self._gross_assets()
802
+ # Copy the spent pool UTxO's exact value bag and mutate ONLY the traded
803
+ # reserves + swap-fee lovelace. The pool's min-ADA, dex NFT, and any other
804
+ # assets carry over untouched — the withdraw validator requires the
805
+ # continuation to preserve them, and reconstructing from the net reserves
806
+ # (``_gross_assets``) drops a token/token pool's real ADA (its min-ADA is
807
+ # not a reserve, so ``assets`` never carried it).
808
+ new = Assets(root=dict(base_assets.root))
744
809
  if self.dex_nft is not None:
745
810
  new.root[self.dex_nft.unit()] = 1
746
811
  new.root[d.unit_x] = new.root.get(d.unit_x, 0) + pool_change_x
@@ -834,6 +899,22 @@ class DanoCLMMState(AbstractConstantLiquidityPoolState):
834
899
  raise InvalidPoolError("Could not re-fetch pool UTxO address via backend")
835
900
  pool_address = order_info[0].address
836
901
 
902
+ # Seed the input value + new pool output from the pool UTxO's EXACT
903
+ # on-chain value bag (min-ADA, dex NFT, any stray assets included) so the
904
+ # swap mutates only the traded reserves + swap-fee lovelace. Reconstructing
905
+ # from the net reserves (``_gross_assets``) drops a token/token pool's real
906
+ # ADA — its min-ADA is not a reserve, so ``assets`` never carried it — and
907
+ # the withdraw validator then rejects the continuation for not preserving
908
+ # it (mirrors SaturnSwap reading its input's coin). Resolve the raw UTxO by
909
+ # out-ref via the chain context; fall back to the reconstructed gross bag
910
+ # when a context cannot resolve by out-ref.
911
+ pool_bag = pool_in_assets
912
+ resolve_utxo = getattr(tx_builder.context, "utxo_by_tx_id", None)
913
+ if resolve_utxo is not None:
914
+ raw_utxo = resolve_utxo(self.tx_hash, self.tx_index)
915
+ if raw_utxo is not None:
916
+ pool_bag = _value_to_assets(raw_utxo.output.amount)
917
+
837
918
  # Overdue ADA pool: when curEpoch > last_withdraw_epoch the validator
838
919
  # requires the pool's accrued staking rewards to be withdrawn from the
839
920
  # per-pool stake credential in the SAME tx (a second withdrawal) and the
@@ -884,6 +965,7 @@ class DanoCLMMState(AbstractConstantLiquidityPoolState):
884
965
 
885
966
  new_datum = self.compute_new_datum(delta_amount, swap_fee, cur_epoch)
886
967
  new_pool_assets = self._new_pool_assets(
968
+ pool_bag,
887
969
  pool_change_x,
888
970
  pool_change_y,
889
971
  swap_fee,
@@ -897,7 +979,7 @@ class DanoCLMMState(AbstractConstantLiquidityPoolState):
897
979
  ),
898
980
  output=TransactionOutput(
899
981
  address=Address.decode(pool_address),
900
- amount=asset_to_value(pool_in_assets),
982
+ amount=asset_to_value(pool_bag),
901
983
  datum=self._datum,
902
984
  ),
903
985
  )
@@ -914,42 +996,54 @@ class DanoCLMMState(AbstractConstantLiquidityPoolState):
914
996
  "Pool script reference UTxO unavailable from backend",
915
997
  )
916
998
 
917
- # The spend + withdraw redeemers encode the pool input index and the
918
- # first byte (pool input index for Spend, protocol-config reference
919
- # index for Withdraw), which depend on the FINAL sorted inputs /
920
- # reference inputs. The builder may still trim or reorder inputs after
921
- # this call, so the indices are resolved POST-BUILD by
922
- # ``DanoSwapRedeemer.set_idx`` — the tx builder runs it after assigning
923
- # redeemer indices (mirrors Splash's pool redeemer + the clmm-sdk's
924
- # ``RedeemerArg`` callback). ``pool_out_idx`` is the single-pool batch
925
- # position (0), not a tx output index.
999
+ # Fold this pool into the tx's shared Dano swap batch. A Dano tx spends
1000
+ # N >= 1 pool UTxOs under ONE withdraw-zero: every Spend redeemer and the
1001
+ # single Withdraw redeemer carry the same batch vector, resolved
1002
+ # POST-BUILD in ``DanoSwapRedeemer.set_idx`` (the builder runs it after
1003
+ # assigning redeemer indices). Composing another Dano pool into the same
1004
+ # ``tx_builder`` appends here and reuses the existing withdraw-zero — the
1005
+ # first pool folded in owns it, later pools only extend the batch.
1006
+ batch = getattr(tx_builder, "_dano_swap_batch", None)
1007
+ if batch is None:
1008
+ batch = []
1009
+ tx_builder._dano_swap_batch = batch
1010
+ first_dano = len(batch) == 0
1011
+ batch.append((input_utxo.input, delta_amount))
1012
+
926
1013
  spend_redeemer = DanoSwapRedeemer(
927
1014
  pool_input=input_utxo.input,
928
1015
  delta_amount=delta_amount,
929
1016
  is_withdraw=False,
930
1017
  )
931
- withdraw_redeemer = DanoSwapRedeemer(
932
- pool_input=input_utxo.input,
933
- delta_amount=delta_amount,
934
- is_withdraw=True,
935
- protocol_config_input=pc_utxo.input,
936
- )
937
-
938
1018
  tx_builder.add_script_input(
939
1019
  utxo=input_utxo,
940
1020
  script=script_ref,
941
1021
  redeemer=Redeemer(spend_redeemer),
942
1022
  )
943
1023
 
1024
+ # Add the withdraw-zero (and its batch Withdraw redeemer) exactly once per
1025
+ # tx. Merge into any existing withdrawals rather than overwrite, so a
1026
+ # prior Dano pool's withdraw-zero and any overdue per-pool staking
1027
+ # withdrawal survive.
944
1028
  reward_addr = Address.from_primitive(DANO_POOL_REWARD_ADDRESS_MAINNET)
945
- withdrawals: dict[bytes, int] = {bytes(reward_addr): 0}
1029
+ withdrawals: dict[bytes, int] = (
1030
+ dict(tx_builder.withdrawals) if tx_builder.withdrawals else {}
1031
+ )
1032
+ withdrawals[bytes(reward_addr)] = 0
946
1033
  if is_overdue and staking_reward_addr is not None:
947
1034
  withdrawals[bytes(staking_reward_addr)] = reward
948
1035
  tx_builder.withdrawals = Withdrawals(withdrawals)
949
- tx_builder.add_withdrawal_script(
950
- script_ref,
951
- Redeemer(withdraw_redeemer),
952
- )
1036
+ if first_dano:
1037
+ withdraw_redeemer = DanoSwapRedeemer(
1038
+ pool_input=input_utxo.input,
1039
+ delta_amount=delta_amount,
1040
+ is_withdraw=True,
1041
+ protocol_config_input=pc_utxo.input,
1042
+ )
1043
+ tx_builder.add_withdrawal_script(
1044
+ script_ref,
1045
+ Redeemer(withdraw_redeemer),
1046
+ )
953
1047
  if is_overdue and staking_ref_utxo is not None:
954
1048
  # Per-pool staking-reward withdrawal. Its redeemer is pool-input
955
1049
  # indexed (like Spend, NOT the protocol-config-indexed withdraw-zero).
@@ -28,7 +28,10 @@ from charli3_dendrite.lending.danogo.state import DanogoLoanState
28
28
  from charli3_dendrite.lending.danogo.state import DanogoPoolState
29
29
 
30
30
  if TYPE_CHECKING:
31
+ from collections.abc import Collection
32
+
31
33
  from charli3_dendrite.backend.backend_base import AbstractBackend
34
+ from charli3_dendrite.dataclasses.models import Assets
32
35
  from charli3_dendrite.dataclasses.models import PoolStateInfo
33
36
  from charli3_dendrite.lending.oracles.models import PriceMap
34
37
 
@@ -103,6 +106,29 @@ def _pool_nft_name(info: PoolStateInfo, policy: str) -> str | None:
103
106
  return None
104
107
 
105
108
 
109
+ def _loan_market_name(
110
+ assets: Assets,
111
+ loan_policy: str,
112
+ valid_names: Collection[str],
113
+ ) -> str | None:
114
+ """Pool-NFT name (hex) of the market a loan belongs to, from its loan-identity NFT.
115
+
116
+ Each loan UTxO carries a loan token minted under the loan script hash whose asset
117
+ name IS the pool-NFT name of the loan's market (`create_loan` mints it that way).
118
+ Mirroring `_pool_nft_name`, this reads that name directly off the loan rather than
119
+ guessing the market from the borrowed supply token (several markets can share a
120
+ supply token) or the collateral (several markets can accept the same collateral).
121
+ Returns the name only when it is a known market/pool name (in `valid_names`), else
122
+ None (the loan is skipped, matching the existing skip-on-parse-failure degrade).
123
+ """
124
+ for unit, qty in assets.root.items():
125
+ if unit != "lovelace" and unit.startswith(loan_policy) and qty == 1:
126
+ name = unit[len(loan_policy) :]
127
+ if name in valid_names:
128
+ return name
129
+ return None
130
+
131
+
106
132
  def fetch_markets(
107
133
  backend: AbstractBackend,
108
134
  addresses: DanogoScriptAddresses,
@@ -193,9 +219,22 @@ def snapshot(
193
219
  if prices is None:
194
220
  try:
195
221
  from charli3_dendrite.lending.danogo.oracles import forward
222
+ from charli3_dendrite.lending.danogo.oracles.locator import (
223
+ augment_registry_with_bond_dtokens,
224
+ )
196
225
  from charli3_dendrite.lending.danogo.oracles.locator import load_registry
197
226
 
198
227
  registry = load_registry()
228
+ # Bond dTokens (a pool's own redeemable dToken) are accepted as collateral
229
+ # but are not mined for every (dToken, quote); synthesize their pricing
230
+ # recipes from the live pool/market state before resolving, so a dToken
231
+ # collateral prices instead of collapsing the loan's health factor.
232
+ augment_registry_with_bond_dtokens(
233
+ registry,
234
+ pool_address=addresses["pool"],
235
+ config_pool_cred=_payment_cred_hex(addresses["config_pool"]),
236
+ markets=markets,
237
+ )
199
238
  pairs = {
200
239
  (collateral, market.supply_token)
201
240
  for market in markets.values()
@@ -205,28 +244,26 @@ def snapshot(
205
244
  except Exception: # noqa: BLE001 - pricing outage must not crash the snapshot
206
245
  prices = PriceMap()
207
246
 
208
- # Pair pool <-> market (same pool-NFT name) and index pools by supply token so
209
- # loans (which only know their borrowed token) can find their market/pool.
247
+ # Pair pool <-> market (same pool-NFT name) and index BY THAT NAME. Each loan is
248
+ # stitched to the market its own loan-identity NFT names -- not one guessed from its
249
+ # borrowed supply token or collateral, both of which several markets can share.
210
250
  pools: list[DanogoPoolState] = []
211
- by_supply: dict[str, tuple[DanogoPoolState, DanogoMarket]] = {}
251
+ by_name: dict[str, tuple[DanogoPoolState, DanogoMarket]] = {}
212
252
  for name, pool in pools_by_name.items():
213
253
  market = markets.get(name)
214
254
  if market is None:
215
255
  continue
216
256
  pool.attach_market(market)
217
257
  pools.append(pool)
218
- by_supply[market.supply_token] = (pool, market)
258
+ by_name[name] = (pool, market)
219
259
 
260
+ loan_policy = _payment_cred_hex(addresses["loan"])
220
261
  stitched: list[DanogoLoanState] = []
221
262
  for loan in loans:
222
- try:
223
- supply = loan.borrowed_unit
224
- except (ValueError, IndexError, TypeError, AttributeError):
225
- continue
226
- ctx = by_supply.get(supply)
227
- if ctx is None:
263
+ loan_market = _loan_market_name(loan.assets, loan_policy, by_name)
264
+ if loan_market is None:
228
265
  continue
229
- pool, market = ctx
266
+ pool, market = by_name[loan_market]
230
267
  loan.attach_context(pool=pool, market=market, now_ms=now_ms)
231
268
  stitched.append(loan)
232
269
 
@@ -296,10 +296,6 @@ def resolve_prices(
296
296
  Emits ``OraclePrice(token=..., quote=quote, ...)``. A pair with no recipe, a
297
297
  missing leaf, or a non-reproducing walk is simply omitted (collateral stays unpriced
298
298
  -> not-liquidatable). Never raises into the caller.
299
-
300
- NOTE: ``PriceMap`` keys by token only, so a collateral used across markets with
301
- different supply tokens can hold only one quote. That's a pre-existing PriceMap
302
- constraint, not handled here.
303
299
  """
304
300
  pm = PriceMap()
305
301
  for collateral, quote in pairs: