charli3_dendrite 1.4.10.dev1__tar.gz → 1.4.11.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 (42) hide show
  1. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/PKG-INFO +1 -1
  2. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/pyproject.toml +4 -3
  3. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/__init__.py +1 -0
  4. charli3_dendrite-1.4.11.dev0/src/charli3_dendrite/dexs/ob/cardanoswaps.py +711 -0
  5. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/utility.py +35 -1
  6. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/LICENSE +0 -0
  7. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/README.md +0 -0
  8. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/backend/__init__.py +0 -0
  9. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/backend/backend_base.py +0 -0
  10. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/backend/blockfrost/__init__.py +0 -0
  11. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/backend/blockfrost/models.py +0 -0
  12. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/backend/dbsync/__init__.py +0 -0
  13. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/backend/dbsync/models.py +0 -0
  14. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/backend/ogmios_kupo/__init__.py +0 -0
  15. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/backend/ogmios_kupo/models.py +0 -0
  16. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/backend/utils.py +0 -0
  17. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dataclasses/__init__.py +0 -0
  18. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dataclasses/datums.py +0 -0
  19. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dataclasses/models.py +0 -0
  20. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dexs/__init__.py +0 -0
  21. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dexs/amm/__init__.py +0 -0
  22. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dexs/amm/amm_base.py +0 -0
  23. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dexs/amm/amm_types.py +0 -0
  24. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dexs/amm/cswap.py +0 -0
  25. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dexs/amm/dano.py +0 -0
  26. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dexs/amm/minswap.py +0 -0
  27. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dexs/amm/muesli.py +0 -0
  28. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dexs/amm/spectrum.py +0 -0
  29. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dexs/amm/splash.py +0 -0
  30. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dexs/amm/sundae.py +0 -0
  31. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dexs/amm/vyfi.py +0 -0
  32. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dexs/amm/wingriders.py +0 -0
  33. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dexs/core/__init__.py +0 -0
  34. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dexs/core/base.py +0 -0
  35. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dexs/core/errors.py +0 -0
  36. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dexs/ob/__init__.py +0 -0
  37. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dexs/ob/axo.py +0 -0
  38. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dexs/ob/chadswap.py +0 -0
  39. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dexs/ob/djed.py +0 -0
  40. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dexs/ob/geniusyield.py +0 -0
  41. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dexs/ob/ob_base.py +0 -0
  42. {charli3_dendrite-1.4.10.dev1 → charli3_dendrite-1.4.11.dev0}/src/charli3_dendrite/dexs/ob/saturnswap.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: charli3_dendrite
3
- Version: 1.4.10.dev1
3
+ Version: 1.4.11.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.4.10-dev1"
3
+ version = "1.4.11-dev0"
4
4
  description = ""
5
5
  authors = ["Elder Millenial <eldermillenial@protonmail.com>"]
6
6
  readme = "README.md"
@@ -32,6 +32,7 @@ mkdocs = "^1.6.0"
32
32
  mkdocs-material = "^9.5.32"
33
33
  mkdocstrings = { extras = ["python"], version = "^0.25.2" }
34
34
  pytest-subtests = "^0.14.1"
35
+ uplc = "^1.3.2"
35
36
 
36
37
 
37
38
  [build-system]
@@ -45,11 +46,11 @@ profile = "black"
45
46
  plugins = ["pydantic.mypy"]
46
47
 
47
48
  [[tool.mypy.overrides]]
48
- module = ["pycardano", "pycardano.*", "dotenv", "nox"]
49
+ module = ["pycardano", "pycardano.*", "dotenv", "nox", "uplc", "uplc.*"]
49
50
  ignore_missing_imports = true
50
51
 
51
52
  [tool.bumpversion]
52
- current_version = "1.4.10-dev1"
53
+ current_version = "1.4.11-dev0"
53
54
  parse = """(?x)
54
55
  (?P<major>\\d+)\\.
55
56
  (?P<minor>\\d+)\\.
@@ -24,6 +24,7 @@ from charli3_dendrite.dexs.amm.wingriders import WingRidersCPPState
24
24
  from charli3_dendrite.dexs.amm.wingriders import WingRidersSSPState
25
25
  from charli3_dendrite.dexs.amm.wingriders import WingRidersV2CPPState
26
26
  from charli3_dendrite.dexs.amm.wingriders import WingRidersV2SSPState
27
+ from charli3_dendrite.dexs.ob.cardanoswaps import CardanoSwapsOrderState
27
28
  from charli3_dendrite.dexs.ob.chadswap import ChadSwapOrderBook
28
29
  from charli3_dendrite.dexs.ob.chadswap import ChadSwapOrderState
29
30
  from charli3_dendrite.dexs.ob.djed import DjedOrderBook
@@ -0,0 +1,711 @@
1
+ """Cardano-Swaps v2 one-way order book module.
2
+
3
+ Cardano-Swaps is a fully on-chain, batcher-less peer-to-peer order book. A
4
+ resting *one-way* swap UTxO offers one asset and asks for another at a fixed
5
+ ``Rational`` price (ask-per-offer). Any counterparty may fill it directly by
6
+ spending the UTxO; there is no batcher and no protocol fee.
7
+
8
+ This module provides *parse* support for v2 one-way swaps: it decodes the
9
+ on-chain ``SwapDatum`` and exposes it through the standard order-book interface
10
+ (price/available/get_amount_out). The create/fill/close transaction builders
11
+ are implemented separately.
12
+ """
13
+
14
+ import hashlib
15
+ import time
16
+ from dataclasses import dataclass
17
+ from typing import Union
18
+
19
+ from pycardano import Address
20
+ from pycardano import Network
21
+ from pycardano import PlutusData
22
+ from pycardano import PlutusV2Script
23
+ from pycardano import Redeemer
24
+ from pycardano import ScriptHash
25
+ from pycardano import TransactionBuilder
26
+ from pycardano import TransactionId
27
+ from pycardano import TransactionInput
28
+ from pycardano import TransactionOutput
29
+ from pycardano import UTxO
30
+ from pycardano.utils import min_lovelace
31
+
32
+ from charli3_dendrite.dataclasses.datums import OrderDatum
33
+ from charli3_dendrite.dataclasses.datums import PlutusNone
34
+ from charli3_dendrite.dataclasses.models import Assets
35
+ from charli3_dendrite.dataclasses.models import OrderType
36
+ from charli3_dendrite.dataclasses.models import PoolSelector
37
+ from charli3_dendrite.dexs.ob.ob_base import AbstractOrderState
38
+ from charli3_dendrite.utility import asset_to_value
39
+
40
+ # v2 one-way beacon minting policy id (also stored verbatim in the datum's
41
+ # ``beacon_id`` field).
42
+ BEACON_POLICY_ID = "c4d7d117d9ebcde6db28db40837ff2b1401e9eaaa6eecea9e070e209"
43
+
44
+ # v2 one-way swap spending validator hash.
45
+ SWAP_VALIDATOR_HASH = "1d6cff26bcab91d2061aad0bd259cbb7d76d25ced2eeaed5926a42ad"
46
+
47
+ # Compiled Plutus V2 scripts, inlined verbatim (no on-chain reference script, so
48
+ # they are attached to the witness set directly). The swap validator is
49
+ # un-parameterized; the beacon minting policy is parameterized by ``dapp_hash``
50
+ # (= the swap validator hash), applied here via the aiken blueprint, so
51
+ # ``PlutusV2Script(bytes.fromhex(...))`` hashes to the constants above (swap
52
+ # validator -> SWAP_VALIDATOR_HASH, beacon policy -> BEACON_POLICY_ID).
53
+ SWAP_VALIDATOR_SCRIPT_HEX = "591193010000323232323232323223232322322533300832323232323253233300f300c007132323232323232323253330183016301a3754010264a666032602c60366ea80044c94ccc068cc03d241225374616b696e672063726564656e7469616c20646964206e6f7420617070726f7665003330103020301d375460406042603a6ea8c080c074dd5001002001899807a4812c426561636f6e20736372697074206e6f74206578656375746564206173206d696e74696e6720706f6c6963790033301a3232533302000114a22940c94ccc070c068c078dd50008a5eb7bdb1804dd59811180f9baa001323300100100222533302100114c0103d87a8000132323253330203371e00e6eb8c08800c4c050cc094dd3000a5eb804cc014014008dd598110011812801181180099198008008039129998100008a5eb7bdb1804c8c8c8c94ccc080c048008400c4cc094cdd81ba9002374c0026600c00c0066eacc08800cdd7181000118120011811000a504a22940dd7180f980e1baa018163300b006301e301b37540102c6eb0c074c078c078008dd5980e000980e180e0011bab301a001301a301a301a301a0023758603000260286ea8c05c008c058c05c004c048dd50040a9998079806803899191919191919299980b180a180c1baa0061325333017301430193754002264a6660306601a921225374616b696e672063726564656e7469616c20646964206e6f7420617070726f76650033300e301e301b3754603c603e60366ea8c078c06cdd5001002001899806a4812c426561636f6e20736372697074206e6f74206578656375746564206173207374616b696e6720736372697074003300f004300c3301d3374a90011980e9ba90014bd7025eb805281bae301d301a375402c2c66012008603860326ea801858dd6180d980e180e0011bab301a001301a301a301a301a301a301a0023758603000260286ea8c05c008c058c05c004c048dd5004099191919191919299980b180a180c1baa0061323253330183015301a37540022646464646464646464646666666666666646464444444444444464646464a66606c6464a666070606c60746ea800452889919299981d181b981e1baa003132533303b3038303d3754006266030920127696e76616c69642d686572656166746572206d757374206265203c3d2065787069726174696f6e00337126eb4c104c0f8dd50018008011bad3040303d37540060022a66074920126696e76616c69642d68657265616674657220726571756972656420627574206e6f74207365740016303b00230390013376000a02026464646464646464a66607ca66607ca66607c606002829444c0c004452889980da49194164612063616e206f6e6c79206265206465706f73697465640033712900019b810030061533303e33033491264661696c3a206f666665725f74616b656e202a207072696365203c3d2061736b5f676976656e003371266e08004028cdc1001004899819a4812b5468652061736b2061737365742063616e6e6f742062652074616b656e2066726f6d207468652073776170003371290000010a5016337026eb4c10c01cdd6982180219b81375a60840046eb4c108014dd6982098210009820800991980080081311299981f8008a9981e248123436f72726573706f6e64696e672073776170206f7574707574206e6f7420666f756e6400161323232323253330403375e01c608c608e0042a66608066ebc06400c4ccccccccc07000406005c05805405004c0480444cc01c01c0144cc01c01c014dd5982280098228011821800981f9baa30420023042001375a607c607e002607c00266666666602402201c01a01801601401201000e2c6eb4c0ecc0f0008dd6981d000981b1baa0033374a90021981b98131981b9ba900a3303737520126606e6ea4020cc0dcdd48039981b9ba900633037375200a6606e6ea4010cc0dcdd48019981b8011981b98131981b80e25eb80cc0dc0052f5c097ae0222222222323232323232325333034300730363754607460760042660726ea0014cc0e4dd40019981c9ba80014bd700a9981aa49165554784f206861732077726f6e6720626561636f6e730016375a607200260720046eb4c0dc004c0dc008dd6981a80099980600525eb84101000081010000810100008103d879800011191919191919191929919981c980080a099191919299981e9808181f9baa30430041533303d0021533303d0031330423750016660846ea0024cc108dd40039982119981ea514c103d87a80004c0103d87980004bd7000080080088069807181e9baa30413042002300d303c375460806082608200266602e01097ae10103d87980008103d87980008103d8798000111919191929919199820980100d899823199820800a6103d87a80004c0103d8798000330463330410044c0103d87a80004c0103d8798000330463330410034c0103d87a80004c0103d87980004bd700a999820980100c09982319982080326103d87a80004c0103d8798000330463330410014c0103d87a80004c0103d8798000330463330410034c0103d87a80004c0103d87980004bd700a999820980100a89982319982080326103d87a80004c0103d8798000330463330410044c0103d87a80004c0103d8798000330463330410014c0103d87a80004c0103d87980004bd700a998212481165554784f206861732077726f6e6720626561636f6e730016303e375a608600e6e3cdd71820803180898201baa304430450023010303f375460860026086004601c607a6ea8c10400454c8c8c8c8ccc0f4c0bc0344c94ccc0f8c0ecc100dd50008992991998200028998229ba830070023304537500186608a6ea0004cc114ccc100025300103d87a80004c0103d87980004bd700a9998200020998229ba800e330453750600c0046608a6ea0004cc114ccc10002530103d87a80004c0103d87980004bd700998229ba800e3304537500186608a6ea0004cc114ccc100025300103d87a80004c0103d87980004bd7019b80009001375a608860826ea800458c8cc004004034894ccc10c004530103d87a8000132323253330423034375c60880062606c6608e6ea00052f5c026600a00a0046eb4c110008c11c008c11400454ccc0f54ccc0f400840045280a9998208060a9981f249284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f00161325333042001132323253330413371e00203226608c6ea0c020008cc118dd4006998231ba800b3304633304100a4c0103d87a80004c0103d87980004bd700a99982099b8f001016133046375001e6608c6ea0c01c008cc118dd400599823199820805260103d87a80004c0103d87980004bd700a998212481284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f0016375c60840046eb4c108004c11003454ccc108c1140044c8c8c8c8c8c94ccc110cdc780180e0a99982219b8f00101913304937506016008660926ea0c028008cc124dd400719824999822006a6103d87a80004c0103d87980004bd700a99822a481284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f0016153330443371e0060322a66608866e3c0040704cc124dd41805801198249ba8300a00433049375001c6609266608801a980103d87a80004c0103d87980004bd700a99822a481284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f00161533045491284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f0016375c608a0086eb4c11400cdd718218019bad30430023045002304400d153303f4901284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f0016304400c1533303d0021533304100c153303e4901284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f001615333041304400c13232533303f3371e6eb8c10400805c4cc110dd41803000998221ba800b3304437500126608866607e010980103d87a80004c0103d87980004bd700a998202481284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f0016375a608200260860182a6607c921284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f00161533303d0011533304100c153303e491284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f001615333041304400c13232533303f3371e6eb8c1040080504cc110dd4006998221ba830050013304437500126608866607e010980103d87a80004c0103d87980004bd700a998202481284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f0016375a608200260860182a6607c921284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f0016153303e491284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f001630040123003014370000c6e0001cdc78041805181c9baa303d303e002375a607800260780046eb4c0e8004c0e8010dd6981c0019bab3034003375c60640046e2120002253330253330250014a094454cc098008584004888c8ccc00400401000c8894ccc0b000840044ccc00c00cc0bc008cc010c0b8008004dd59814181480519192999812181118131baa0011301833029302a3027375400297ae014c103d87a800030293026375460526054604c6ea8038c090dd500698140051bae3028009375c60500106eb8c0a001cdd718140031bae3028005375c60500086eb8c0a000cdd71814001181400098141814981480098140009813800981300098128009812000981180098110009810800980e1baa018301b3754603c603e60366ea8c078c06cdd50008b19805002800980e180c9baa00616301b301c301c301c301c301c00237586034002603460340046eb0c060004c050dd5180b801180b180b80098091baa008371e9110022323300100100322533301500114c0103d87a80001323253330133375e6032602c6ea80080144c01ccc0600092f5c02660080080026032004602e0026e95200022533300d33300d0014a094454cc03800840044004888c8c94ccc03cc034004528899299980818068010992999808980718099baa0011325333012300f30143754002264646600200201044a66603200229404c94ccc058cdc79bae301c00200414a226600600600260380026eb8c060c054dd5000801980b980a1baa00100230163013375400600226600c008602a60246ea8008c040dd50009809980a18081baa00322323300100100322533301100114a0264a66601c66ebc010c040c050008528899801801800980a00098051baa00314984d958c94ccc01cc01000454ccc02cc028dd50010a4c2c2a66600e600a0022a66601660146ea800852616153330073370e90020008a99980598051baa00214985858c020dd5000a999802180098031baa003132323232323232323232323232323232323232323232533301e30210021323232498c94ccc074c0680044c8c94ccc08cc09800852616375a604800260406ea801054ccc074c06c00454ccc084c080dd50020a4c2c2c603c6ea800cc94ccc070c0640044c8c94ccc088c0940084c926533301e301b30203754002264646464a66604c6052004264932999811180f98121baa003132325333028302b002149858dd7181480098129baa0031616375a604e002604e004604a00260426ea80045858c08c004c07cdd50028a99980e180d0008a999810180f9baa00514985858c074dd5002299980d180b980e1baa00513232323253330223025002149858dd6981180098118011bad3021001301d375400a2c2c603e002603e004603a002603a004603600260360046eb8c064004c064008dd7180b800980b8011bae30150013015002375c602600260260046eb8c044004c044008dd7180780098078011bae300d001300d002375c6016002600e6ea800c58dc3a40006e1d20025734ae7155ceaab9e5573eae815d0aba201" # noqa: E501
54
+ BEACON_POLICY_SCRIPT_HEX = "5911ed010000332323232323232322322323232322533300932533300a30060011323232533300d3370e900318079baa0011533300d3009300f3754602660206ea80045288010011809180998079baa004153300c4913c546869732072656465656d65722063616e206f6e6c79206265207573656420746f2072656769737465722074686520626561636f6e207363726970740016300c37540042646464646464646464a666026601c0142646464666600802864a66602e602600226eb8c074c068dd5001899299980c1809001099299980c980a980d9baa001132533301a3015301c375400226eb8c080c074dd5000801980f980e1baa001002301e301b37540080022a6603092012852656465656d6572206e6f7420757365642077697468206d696e74696e6720657865637574696f6e00163018375400460166038603a603a603a603a603a0026eb0c070004c070c070c060dd5180d801180d180d800980b1baa00b13232323333004014325333017301130193754006264a666030602860346ea80044c94ccc064c050c06cdd500089bae301f301c3754002006603c60366ea8004008c074c068dd50018008a9980ba4812852656465656d6572206e6f7420757365642077697468207374616b696e6720657865637574696f6e0016300b301c301d301d301d301d301d001375860380026038603860306ea8c06c008c068c06c004c058dd5005911119199800800801251222533301d00210011333003003302000232323232323232325333025001100913253330260011533023491364f6e652d776179207377617073206d75737420686176652065786163746c79207468726565206b696e6473206f6620626561636f6e730016132533302700115330244901364f6e652d776179207377617073206d75737420686176652065786163746c79207468726565206b696e6473206f6620626561636f6e73001615333027302a0011323232533302600e153330263301632330160014911a204441707020616464726573732077697468207374616b696e67003301549114426561636f6e206d75737420676f20746f2061200049010053330263375e01066e9520023302b375202897ae01533302630223028375400e29445280a50132323232323232323232323232323232323232323232323232533303f32325333041303c3043375400229444c8c94ccc10cc0fcc114dd50018992999822182018231baa00315333044330354912745787069726174696f6e206d757374206265203e3d20696e76616c69642d68657265616674657200337126eb4c128c11cdd500180089981aa481234d7573742075736520312d6d696e2065787069726174696f6e20696e74657276616c730030403370c002906054838a50002375a6092608c6ea800c00454cc10d240126696e76616c69642d68657265616674657220726571756972656420627574206e6f742073657400163044002304200133760608a608c608c0120562a66607e6605e92010f57726f6e6720626561636f6e5f6964003371e0580302a66607e6605e9201274f666665722061737365742063616e6e6f742062652073616d652061732061736b2061737365740033303f3375e01000e941288a99981f99817a491157726f6e6720706169725f626561636f6e003371e02c64646464646460706464660760020086607400200466072a66608a601200a291010100001005375c6096609800ca6660886010004291010100001002375c609260940046eb8c120004c110dd50049bae3046001304237540102a66607e6605e9211257726f6e67206f666665725f626561636f6e003371e020606466066911010100330330140121533303f3302f49011057726f6e672061736b5f626561636f6e003371e0146064660669110102003303300e00c1533303f3302f49011e737761705f70726963652064656e6f6d696e61746f72206e6f74203e20300030020041533303f3302f49011c737761705f7072696365206e756d657261746f72206e6f74203e203000300200513371290000008a5014a029405280a5014a0294058dd698221919191919192999822180398231baa304a304b002133049375000a660926ea000ccc124dd4000a5eb8054cc115241165554784f206861732077726f6e6720626561636f6e730016375a609200260920046eb4c11c004c11c008dd69822800999818011a5eb84101000081010000810100008103d8798000111919191919191919299199824980081b0991919192999826980818279baa30530041533304d0021533304d0031330523750016660a46ea0024cc148dd400399829199826a514c103d87a80004c0103d87980004bd700008008008806980718269baa30513052002300d304c375460a060a260a200266607601097ae10103d87980008103d87980008103d8798000111919191929919199828980101409982b199828800a6103d87a80004c0103d8798000330563330510044c0103d87a80004c0103d8798000330563330510034c0103d87a80004c0103d87980004bd700a999828980101109982b19982880326103d87a80004c0103d8798000330563330510014c0103d87a80004c0103d8798000330563330510034c0103d87a80004c0103d87980004bd700a999828980100e09982b19982880326103d87a80004c0103d8798000330563330510044c0103d87a80004c0103d8798000330563330510014c0103d87a80004c0103d87980004bd700a998292481165554784f206861732077726f6e6720626561636f6e730016304b375a60a600e6e3cdd71828803180898281baa305430550023010304f375460a600260a6004601c609a6ea8c14400454c8c8c8c8ccc134c0440344c94ccc138c128c140dd500089929919982800289982a9ba83007002330553750018660aa6ea0004cc154ccc140025300103d87a80004c0103d87980004bd700a99982800209982a9ba800e330553750600c004660aa6ea0004cc154ccc14002530103d87a80004c0103d87980004bd7009982a9ba800e330553750018660aa6ea0004cc154ccc140025300103d87a80004c0103d87980004bd7019b80009001375a60a860a26ea800458c8cc004004034894ccc14c004530103d87a8000132323253330523016375c60a800626090660ae6ea00052f5c026600a00a0046eb4c150008c15c008c15400454ccc1354ccc13400840045280a9998288060a99827249284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f00161325333052001132323253330513371e0020482660ac6ea0c020008cc158dd40069982b1ba800b3305633305100a4c0103d87a80004c0103d87980004bd700a99982899b8f00101e133056375001e660ac6ea0c01c008cc158dd40059982b199828805260103d87a80004c0103d87980004bd700a998292481284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f0016375c60a40046eb4c148004c15003454ccc148c1540044c8c8c8c8c8c94ccc150cdc78018138a99982a19b8f00102113305937506016008660b26ea0c028008cc164dd40071982c99982a006a6103d87a80004c0103d87980004bd700a9982aa481284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f0016153330543371e0060422a6660a866e3c00409c4cc164dd418058011982c9ba8300a00433059375001c660b26660a801a980103d87a80004c0103d87980004bd700a9982aa481284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f00161533055491284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f0016375c60aa0086eb4c15400cdd718298019bad30530023055002305400d153304f4901284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f0016305400c1533304d0021533305100c153304e4901284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f001615333051305400c13232533304f3371e6eb8c1440080884cc150dd418030009982a1ba800b330543750012660a866609e010980103d87a80004c0103d87980004bd700a998282481284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f0016375a60a200260a60182a6609c921284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f00161533304d0011533305100c153304e491284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f001615333051305400c13232533304f3371e6eb8c1440080704cc150dd40069982a1ba83005001330543750012660a866609e010980103d87a80004c0103d87980004bd700a998282481284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f0016375a60a200260a60182a6609c921284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f0016153304e491284e6f2065787472616e656f75732061737365747320616c6c6f77656420696e20746865205554784f0016300401b3003020370000c6e0001cdc7804180518249baa304d304e002375a609800260980046eb4c128004c128010dd698240019bab3044003375c60840046e212000371e91100375a608260840046eb4c100004c0f0dd5181f80198171981e9ba90073303d375200a97ae0302d3303c3752018660786ea40292f5c0607a0046eb8c0ec004c0ec008dd7181c800981c8011bae30370013037002375c606a002606a0046eb8c0cc004c0cc008dd7181880098188011bae302f001302f002375c605a00260526ea94ccc098c080c0a0dd5005099190012999813981198149baa001132323232323232323232323232323232323232323232533304130440021323232498c94ccc100c0f00044c8c94ccc118c12400852616375a608e00260866ea801054ccc100c0ec00454ccc110c10cdd50020a4c2c2c60826ea800cc94ccc0fcc0ec0044c8c94ccc114c1200084c9265333041303d30433754002264646464a6660926098004264932999822982098239baa00313232533304b304e002149858dd7182600098241baa0031616375a60940026094004609000260886ea80045858c118004c108dd50028a99981f981d0008a99982198211baa00514985858c100dd5002299981e981c981f9baa00513232323253330453048002149858dd6982300098230011bad30440013040375400a2c2c6084002608400460800026080004607c002607c0046eb8c0f0004c0f0008dd7181d000981d0011bae30380013038002375c606c002606c0046eb8c0d0004c0d0008dd7181900098190011bae30300013030002375c605c00260546ea800458c0b0c0a4dd50050a99813a48125416c6c207377617020646174756d73206d75737420626520696e6c696e6520646174756d73001614a02940c0ac00cc0a800cc0a400c54cc0912401364f6e652d776179207377617073206d75737420686176652065786163746c79207468726565206b696e6473206f6620626561636f6e73001630290013028001325333021301c30233754002297adef6c6013756604e60486ea8004c8cc004004018894ccc098004530103d87a8000132323253330253371e0246eb8c09c00c4c06ccc0a8dd3000a5eb804cc014014008dd598138011815001181400098129813001181200098101baa30230043022302300237566042002604200260386ea8c07c00888cdcb001000912999809199809000a504a22a660260042002200244a666022666022002941288a998090010b080091119199800800802001911299980c00108008999801801980d80119802180d00100091b92001223371400400246464a66601c601260206ea80044c010cc04cc050c044dd5000a5eb80530103d87a80003013301037546026602860206ea8008c038dd50009ba548000c02cdd50030a4c26cac64a66601060080022a66601860166ea8014526161533300830030011533300c300b375400a2930b0a99980418010008a99980618059baa00514985858c024dd50021b8748010dc3a40046e1d2000375c002ae695ce2ab9d5573caae7d5d02ba157449811e581c1d6cff26bcab91d2061aad0bd259cbb7d76d25ced2eeaed5926a42ad0001" # noqa: E501
55
+
56
+
57
+ @dataclass
58
+ class SpendWithMint(PlutusData):
59
+ """Spending redeemer: spend a swap UTxO while minting/burning beacons (CLOSE)."""
60
+
61
+ CONSTR_ID = 0
62
+
63
+
64
+ @dataclass
65
+ class SpendWithStake(PlutusData):
66
+ """Spending redeemer: spend a swap UTxO with staking execution (in-place update)."""
67
+
68
+ CONSTR_ID = 1
69
+
70
+
71
+ @dataclass
72
+ class Swap(PlutusData):
73
+ """Spending redeemer: fill a resting swap UTxO (FILL)."""
74
+
75
+ CONSTR_ID = 2
76
+
77
+
78
+ @dataclass
79
+ class RegisterBeaconScript(PlutusData):
80
+ """Beacon redeemer: register the beacon staking script."""
81
+
82
+ CONSTR_ID = 0
83
+
84
+
85
+ @dataclass
86
+ class CreateOrCloseSwaps(PlutusData):
87
+ """Beacon redeemer: mint (CREATE) or burn (CLOSE) the three beacons."""
88
+
89
+ CONSTR_ID = 1
90
+
91
+
92
+ @dataclass
93
+ class UpdateSwaps(PlutusData):
94
+ """Beacon redeemer: staking-execution twin of CreateOrCloseSwaps (no mint)."""
95
+
96
+ CONSTR_ID = 2
97
+
98
+
99
+ def _sha256(data: bytes) -> bytes:
100
+ """sha2_256 digest (32 bytes), used for all three beacon token names."""
101
+ return hashlib.sha256(data).digest()
102
+
103
+
104
+ def pair_beacon_name(
105
+ offer_id: bytes,
106
+ offer_name: bytes,
107
+ ask_id: bytes,
108
+ ask_name: bytes,
109
+ ) -> bytes:
110
+ """The pair-beacon token name.
111
+
112
+ ``sha2_256(a1_id ++ offer_name ++ a2_id ++ ask_name)`` where an empty policy
113
+ id (ADA) is substituted with ``0x00`` for *both* legs. The order is
114
+ offer-then-ask (NOT canonically sorted).
115
+ """
116
+ a1 = offer_id if offer_id != b"" else b"\x00"
117
+ a2 = ask_id if ask_id != b"" else b"\x00"
118
+ return _sha256(a1 + offer_name + a2 + ask_name)
119
+
120
+
121
+ def offer_beacon_name(offer_id: bytes, offer_name: bytes) -> bytes:
122
+ """The offer-beacon token name: ``sha2_256(0x01 ++ offer_id ++ offer_name)``.
123
+
124
+ No ``0x00`` substitution: an ADA offer hashes ``sha2_256(0x01)``.
125
+ """
126
+ return _sha256(b"\x01" + offer_id + offer_name)
127
+
128
+
129
+ def ask_beacon_name(ask_id: bytes, ask_name: bytes) -> bytes:
130
+ """The ask-beacon token name: ``sha2_256(0x02 ++ ask_id ++ ask_name)``.
131
+
132
+ No ``0x00`` substitution: an ADA ask hashes ``sha2_256(0x02)``.
133
+ """
134
+ return _sha256(b"\x02" + ask_id + ask_name)
135
+
136
+
137
+ def _unit(policy: bytes, name: bytes) -> str:
138
+ """Build a dendrite asset unit from on-chain policy + name bytes.
139
+
140
+ An empty policy id is the on-chain encoding of ADA/lovelace.
141
+ """
142
+ if policy == b"":
143
+ return "lovelace"
144
+ return policy.hex() + name.hex()
145
+
146
+
147
+ @dataclass
148
+ class CardanoSwapsRational(PlutusData):
149
+ """A positive rational price (numerator / denominator)."""
150
+
151
+ CONSTR_ID = 0
152
+ numerator: int
153
+ denominator: int
154
+
155
+
156
+ @dataclass
157
+ class CardanoSwapsTxId(PlutusData):
158
+ """A transaction id wrapper."""
159
+
160
+ CONSTR_ID = 0
161
+ tx_hash: bytes
162
+
163
+
164
+ @dataclass
165
+ class CardanoSwapsOutputReference(PlutusData):
166
+ """A reference to a transaction output (tx id + output index)."""
167
+
168
+ CONSTR_ID = 0
169
+ transaction_id: CardanoSwapsTxId
170
+ output_index: int
171
+
172
+
173
+ @dataclass
174
+ class CardanoSwapsSomeOutRef(PlutusData):
175
+ """``Some`` wrapper for an optional output reference."""
176
+
177
+ CONSTR_ID = 0
178
+ value: CardanoSwapsOutputReference
179
+
180
+
181
+ @dataclass
182
+ class CardanoSwapsSomeInt(PlutusData):
183
+ """``Some`` wrapper for an optional integer (POSIX millis expiration)."""
184
+
185
+ CONSTR_ID = 0
186
+ value: int
187
+
188
+
189
+ @dataclass
190
+ class CardanoSwapsSwapDatum(OrderDatum):
191
+ """The v2 one-way swap datum (single constructor, 11 fields).
192
+
193
+ Asset policy/name pairs are kept as separate flat bytestrings (not an
194
+ ``AssetClass``) to match the on-chain 11-field wire format exactly. ADA is
195
+ the empty bytestring in the ``*_id`` / ``*_name`` fields.
196
+ """
197
+
198
+ CONSTR_ID = 0
199
+
200
+ beacon_id: bytes
201
+ pair_beacon: bytes
202
+ offer_id: bytes
203
+ offer_name: bytes
204
+ offer_beacon: bytes
205
+ ask_id: bytes
206
+ ask_name: bytes
207
+ ask_beacon: bytes
208
+ swap_price: CardanoSwapsRational
209
+ prev_input: Union[CardanoSwapsSomeOutRef, PlutusNone]
210
+ expiration: Union[CardanoSwapsSomeInt, PlutusNone]
211
+
212
+ def offer_unit(self) -> str:
213
+ """The dendrite unit of the offered (output) asset."""
214
+ return _unit(self.offer_id, self.offer_name)
215
+
216
+ def ask_unit(self) -> str:
217
+ """The dendrite unit of the asked (input) asset."""
218
+ return _unit(self.ask_id, self.ask_name)
219
+
220
+ def offer_assets(self) -> Assets:
221
+ """The offered asset as a zero-quantity Assets bag."""
222
+ return Assets(root={self.offer_unit(): 0})
223
+
224
+ def ask_assets(self) -> Assets:
225
+ """The asked asset as a zero-quantity Assets bag."""
226
+ return Assets(root={self.ask_unit(): 0})
227
+
228
+ def pool_pair(self) -> Assets | None:
229
+ """The traded pair (offer + ask), both at zero quantity."""
230
+ return self.offer_assets() + self.ask_assets()
231
+
232
+ def address_source(self) -> str | None:
233
+ """The owner is the UTxO address staking credential, not the datum."""
234
+ return None
235
+
236
+ def requested_amount(self) -> Assets:
237
+ """The asset the order is asking for."""
238
+ return self.ask_assets()
239
+
240
+ def order_type(self) -> OrderType:
241
+ """A resting swap order."""
242
+ return OrderType.swap
243
+
244
+
245
+ class CardanoSwapsOrderState(AbstractOrderState):
246
+ """A resting Cardano-Swaps v2 one-way swap UTxO.
247
+
248
+ From the taker's view: ``in`` is the ask asset (taker gives) and ``out`` is
249
+ the offer asset (taker receives). ``available`` is the offer asset held in
250
+ the UTxO. The datum's ``swap_price`` is ask-per-offer, so the price tuple is
251
+ returned as ``[numerator, denominator]`` and assets are oriented so that
252
+ ``unit(0)`` is the ask (in) and ``unit(1)`` is the offer (out); then
253
+ ``out = in * denominator / numerator`` falls out of the base class.
254
+ """
255
+
256
+ tx_hash: str
257
+ tx_index: int
258
+ datum_cbor: str
259
+ datum_hash: str
260
+ inactive: bool = False
261
+ fee: int = 0
262
+
263
+ _batcher: Assets = Assets(lovelace=0)
264
+ _datum_parsed: PlutusData | None = None
265
+ _deposit: Assets = Assets(lovelace=0)
266
+
267
+ @classmethod
268
+ def dex(cls) -> str:
269
+ """Official dex name."""
270
+ return "CardanoSwaps"
271
+
272
+ @classmethod
273
+ def order_datum_class(cls) -> type[PlutusData]:
274
+ """Returns data class used for handling order datums."""
275
+ return CardanoSwapsSwapDatum
276
+
277
+ @classmethod
278
+ def default_script_class(cls) -> type[PlutusV2Script]:
279
+ """The swap spending validator is a Plutus V2 script."""
280
+ return PlutusV2Script
281
+
282
+ @classmethod
283
+ def dex_policy(cls) -> list[str] | None:
284
+ """The beacon minting policy id used to discover swap UTxOs."""
285
+ return [BEACON_POLICY_ID]
286
+
287
+ @classmethod
288
+ def extract_dex_nft(cls, values: dict) -> Assets | None:
289
+ """Strip the beacon tokens from the UTxO assets.
290
+
291
+ A one-way swap UTxO carries up to three beacon tokens under one policy
292
+ (pair, offer, ask beacons). They identify the swap but are not part of
293
+ the tradable balance, so remove every beacon-policy token from the
294
+ assets and record them as the dex nft.
295
+ """
296
+ assets = values["assets"]
297
+
298
+ beacons = [
299
+ asset
300
+ for asset in assets
301
+ if any(asset.startswith(policy) for policy in (cls.dex_policy() or []))
302
+ ]
303
+
304
+ if "dex_nft" in values:
305
+ return values["dex_nft"]
306
+
307
+ if not beacons:
308
+ return None
309
+
310
+ dex_nft = Assets(root={beacon: assets.root.pop(beacon) for beacon in beacons})
311
+ values["dex_nft"] = dex_nft
312
+
313
+ return dex_nft
314
+
315
+ @property
316
+ def volume_fee(self) -> int:
317
+ """Batcher-less and fee-less: no volume fee."""
318
+ return 0
319
+
320
+ @property
321
+ def price(self) -> tuple[int, int]:
322
+ """Ask-per-offer price as ``(numerator, denominator)``."""
323
+ return (
324
+ self.order_datum.swap_price.numerator,
325
+ self.order_datum.swap_price.denominator,
326
+ )
327
+
328
+ @property
329
+ def available(self) -> Assets:
330
+ """Max offer asset that can be taken (the offer balance in the UTxO).
331
+
332
+ When the offer asset is ADA, this includes the UTxO's mandatory min-ADA
333
+ (the continuing/owner output must still carry it), so the figure is an
334
+ upper bound on the genuinely-takeable amount. The contract has no
335
+ separate offered-amount field, so the held balance is the only signal;
336
+ the fill builder nets out the min-UTxO.
337
+ """
338
+ return Assets(root={self.out_unit: self.assets[self.out_unit]})
339
+
340
+ @property
341
+ def tvl(self) -> int:
342
+ """Total value locked in the order (the available offer asset)."""
343
+ return self.available
344
+
345
+ @property
346
+ def pool_id(self) -> str:
347
+ """A stable identifier for the swap (the pair beacon name, hex)."""
348
+ return self.order_datum.pair_beacon.hex()
349
+
350
+ @classmethod
351
+ def order_selector(cls) -> list[str]:
352
+ """Order selection: discovery is by beacon policy, not address."""
353
+ return []
354
+
355
+ @classmethod
356
+ def pool_selector(cls) -> PoolSelector:
357
+ """Pool selection: discover by beacon policy id, not address."""
358
+ return PoolSelector(addresses=[], assets=[BEACON_POLICY_ID])
359
+
360
+ @property
361
+ def swap_forward(self) -> bool:
362
+ """Swap forwarding is supported."""
363
+ return True
364
+
365
+ @property
366
+ def stake_address(self) -> Address | None:
367
+ """No batcher stake address."""
368
+ return None
369
+
370
+ @classmethod
371
+ def post_init(cls, values: dict) -> dict:
372
+ """Enforce taker orientation: unit(0)=ask (in), unit(1)=offer (out).
373
+
374
+ Rebuild the assets dict in a fixed order — ask first, offer second, then
375
+ any residual tokens (e.g. the UTxO's min-ADA when it is neither the offer
376
+ nor the ask). ``Assets`` is insertion-ordered (its sort only floats
377
+ ``lovelace`` to the front on construction), so reassigning ``root`` in
378
+ this order makes ``unit(0)`` the ask and ``unit(1)`` the offer.
379
+ """
380
+ datum = cls.order_datum_class().from_cbor(values["datum_cbor"])
381
+
382
+ ask_unit = datum.ask_unit()
383
+ offer_unit = datum.offer_unit()
384
+
385
+ assets = values["assets"]
386
+ root = assets.root
387
+
388
+ # Both legs must be present so unit(0)/unit(1) are well defined, even if a
389
+ # side is absent from the balance (a normal one-way swap holds only the
390
+ # offer asset; the taker brings the ask asset).
391
+ ordered: dict[str, int] = {
392
+ ask_unit: root.pop(ask_unit, 0),
393
+ offer_unit: root.pop(offer_unit, 0),
394
+ }
395
+ # Preserve any residual tokens (e.g. min-ADA) after the traded pair.
396
+ ordered.update(root)
397
+
398
+ assets.root = ordered
399
+
400
+ # A one-way swap whose expiration is in the past is no longer fillable on
401
+ # chain (the validator requires the tx validity interval to end at or
402
+ # before the expiration), so mark it inactive — mirroring the sibling
403
+ # order-book classes. ``expiration`` is POSIX milliseconds; ``PlutusNone``
404
+ # means the swap never expires.
405
+ expiration = datum.expiration
406
+ values["inactive"] = isinstance(expiration, CardanoSwapsSomeInt) and (
407
+ int(time.time() * 1000) >= expiration.value
408
+ )
409
+
410
+ return values
411
+
412
+ # --- transaction builders --------------------------------------------
413
+
414
+ @staticmethod
415
+ def _swap_script() -> PlutusV2Script:
416
+ """The (inline) one-way swap spending validator."""
417
+ return PlutusV2Script(bytes.fromhex(SWAP_VALIDATOR_SCRIPT_HEX))
418
+
419
+ @staticmethod
420
+ def _beacon_script() -> PlutusV2Script:
421
+ """The (inline) beacon minting/staking policy."""
422
+ return PlutusV2Script(bytes.fromhex(BEACON_POLICY_SCRIPT_HEX))
423
+
424
+ @staticmethod
425
+ def beacon_address(owner_address: Address) -> Address:
426
+ """The beacon-tagged swap address for an owner.
427
+
428
+ The payment credential is the swap spending validator hash; the staking
429
+ credential is the owner's (carried over from ``owner_address``). The
430
+ owner *is* this staking credential — beacons can only be paid to a staked
431
+ DApp address, so a resting swap always carries one.
432
+ """
433
+ return Address(
434
+ payment_part=ScriptHash(bytes.fromhex(SWAP_VALIDATOR_HASH)),
435
+ staking_part=owner_address.staking_part,
436
+ network=owner_address.network or Network.MAINNET,
437
+ )
438
+
439
+ @staticmethod
440
+ def _beacon_mint_assets(datum: CardanoSwapsSwapDatum, quantity: int) -> Assets:
441
+ """The three beacons (pair/offer/ask) at +1 (CREATE) or -1 (CLOSE)."""
442
+ return Assets(
443
+ root={
444
+ BEACON_POLICY_ID + datum.pair_beacon.hex(): quantity,
445
+ BEACON_POLICY_ID + datum.offer_beacon.hex(): quantity,
446
+ BEACON_POLICY_ID + datum.ask_beacon.hex(): quantity,
447
+ },
448
+ )
449
+
450
+ @staticmethod
451
+ def _accumulate_mint(tx_builder: TransactionBuilder, mint_assets: Assets) -> None:
452
+ """Add ``mint_assets`` to the builder's mint field (creating it if unset)."""
453
+ multi_asset = asset_to_value(mint_assets).multi_asset
454
+ if tx_builder.mint is None:
455
+ tx_builder.mint = multi_asset
456
+ else:
457
+ tx_builder.mint += multi_asset
458
+
459
+ @classmethod
460
+ def build_create(
461
+ cls,
462
+ owner_address: Address,
463
+ offer: Assets,
464
+ ask: Assets,
465
+ price: tuple[int, int],
466
+ tx_builder: TransactionBuilder,
467
+ expiration: int | None = None,
468
+ ) -> tuple[TransactionOutput, CardanoSwapsSwapDatum]:
469
+ """Build a CREATE: mint the three beacons and emit the resting swap UTxO.
470
+
471
+ Mints +1 of each beacon (pair/offer/ask) under the beacon policy with
472
+ ``CreateOrCloseSwaps`` and produces one output at the owner's
473
+ beacon-tagged swap address carrying the offer asset, the three beacons
474
+ and an inline ``SwapDatum``. No swap-UTxO is spent.
475
+
476
+ Args:
477
+ owner_address: The owner's address; its staking credential identifies
478
+ the owner and is carried onto the swap address.
479
+ offer: The offered (output) asset and quantity (one token).
480
+ ask: The asked (input) asset, quantity ignored (one token).
481
+ price: ``(numerator, denominator)`` ask-per-offer rational.
482
+ tx_builder: The builder to mutate.
483
+ expiration: Optional POSIX-millis expiration (must be a 60000-multiple).
484
+
485
+ Returns:
486
+ The swap output and its inline ``SwapDatum``.
487
+ """
488
+ offer_unit = offer.unit()
489
+ ask_unit = ask.unit()
490
+ offer_id, offer_name = cls._split_unit(offer_unit)
491
+ ask_id, ask_name = cls._split_unit(ask_unit)
492
+
493
+ num, den = price
494
+ exp = (
495
+ CardanoSwapsSomeInt(value=expiration)
496
+ if expiration is not None
497
+ else (PlutusNone())
498
+ )
499
+ datum = CardanoSwapsSwapDatum(
500
+ beacon_id=bytes.fromhex(BEACON_POLICY_ID),
501
+ pair_beacon=pair_beacon_name(offer_id, offer_name, ask_id, ask_name),
502
+ offer_id=offer_id,
503
+ offer_name=offer_name,
504
+ offer_beacon=offer_beacon_name(offer_id, offer_name),
505
+ ask_id=ask_id,
506
+ ask_name=ask_name,
507
+ ask_beacon=ask_beacon_name(ask_id, ask_name),
508
+ swap_price=CardanoSwapsRational(numerator=num, denominator=den),
509
+ prev_input=PlutusNone(),
510
+ expiration=exp,
511
+ )
512
+
513
+ # Mint the three beacons.
514
+ tx_builder.add_minting_script(
515
+ script=cls._beacon_script(),
516
+ redeemer=Redeemer(CreateOrCloseSwaps()),
517
+ )
518
+ cls._accumulate_mint(tx_builder, cls._beacon_mint_assets(datum, 1))
519
+
520
+ # Emit the resting swap output: offer + 3 beacons + min-ADA, inline datum.
521
+ swap_address = cls.beacon_address(owner_address)
522
+ output_assets = offer + cls._beacon_mint_assets(datum, 1)
523
+ txo = TransactionOutput(
524
+ address=swap_address,
525
+ amount=asset_to_value(output_assets),
526
+ datum=datum,
527
+ )
528
+ txo.amount.coin = max(
529
+ txo.amount.coin,
530
+ min_lovelace(tx_builder.context, output=txo),
531
+ )
532
+ tx_builder.add_output(txo)
533
+
534
+ return txo, datum
535
+
536
+ @staticmethod
537
+ def _split_unit(unit: str) -> tuple[bytes, bytes]:
538
+ """Split a dendrite unit into on-chain (policy, name) bytes (ADA -> empty)."""
539
+ if unit == "lovelace":
540
+ return b"", b""
541
+ return bytes.fromhex(unit[:56]), bytes.fromhex(unit[56:])
542
+
543
+ def _input_utxo(self, owner_address: Address) -> UTxO:
544
+ """The synthetic resting swap UTxO (offer + 3 beacons), inline datum.
545
+
546
+ Its address payment part is the swap validator hash (so the inline script
547
+ resolves) and its staking part is the owner's.
548
+ """
549
+ beacons = self.dex_nft if self.dex_nft is not None else Assets(root={})
550
+ amount = self.assets + Assets(root=dict(beacons.root))
551
+ return UTxO(
552
+ TransactionInput(
553
+ transaction_id=TransactionId(bytes.fromhex(self.tx_hash)),
554
+ index=self.tx_index,
555
+ ),
556
+ output=TransactionOutput(
557
+ address=self.beacon_address(owner_address),
558
+ amount=asset_to_value(amount),
559
+ datum=self.order_datum,
560
+ ),
561
+ )
562
+
563
+ def swap_utxo(
564
+ self,
565
+ address_source: Address,
566
+ in_assets: Assets,
567
+ out_assets: Assets,
568
+ tx_builder: TransactionBuilder,
569
+ extra_assets: Assets | None = None,
570
+ address_target: Address | None = None,
571
+ datum_target: PlutusData | None = None,
572
+ owner_address: Address | None = None,
573
+ ) -> tuple[TransactionOutput | None, PlutusData]:
574
+ """Build a FILL: spend the resting swap UTxO and fill at price or better.
575
+
576
+ Spends the resting UTxO with the ``Swap`` spending redeemer (inline
577
+ script — no on-chain reference) and emits the contract-mandated
578
+ continuing output back to the same swap address: the three beacons are
579
+ preserved, the offer balance is decremented by the amount taken, and the
580
+ ask paid in (at price or better) accumulates into this same output. The
581
+ datum is otherwise identical to the input, with ``prev_input`` pointing
582
+ at the consumed UTxO. No beacons are minted or burned, and there is no
583
+ separate maker payment — the maker collects the accumulated ask on CLOSE.
584
+
585
+ ``out_assets`` is the offer asset the taker receives; ``in_assets`` is the
586
+ ask asset they pay. Purity (only offer leaves, only ask is added) and the
587
+ ``offer_taken * price_num <= ask_given * price_den`` price check are
588
+ enforced by the validator; this builder constructs a balanced fill.
589
+
590
+ Returns:
591
+ ``(continuing_output, continuing_datum)`` — the validator always
592
+ requires a continuing beacon output (a full fill simply leaves the
593
+ offer balance at ~0); only CLOSE removes the beacons.
594
+ """
595
+ owner = owner_address if owner_address is not None else address_source
596
+
597
+ # in/out orientation: out = offer (taker receives), in = ask (taker pays).
598
+ if out_assets.unit() != self.out_unit:
599
+ raise ValueError("out_assets must be the offer asset.")
600
+ if in_assets.unit() != self.in_unit:
601
+ raise ValueError("in_assets must be the ask asset.")
602
+
603
+ offer_unit = self.out_unit
604
+ ask_unit = self.in_unit
605
+
606
+ num, den = self.price
607
+ offer_taken = out_assets.quantity()
608
+ # Minimum ask the maker must receive for this offer at price-or-better:
609
+ # offer_taken*num <= ask_given*den => ask_given >= ceil(offer_taken*num/den).
610
+ ask_given = -(-offer_taken * num // den) # ceil division
611
+
612
+ input_utxo = self._input_utxo(owner)
613
+
614
+ tx_builder.add_script_input(
615
+ utxo=input_utxo,
616
+ script=self._swap_script(),
617
+ redeemer=Redeemer(Swap()),
618
+ )
619
+ tx_builder.datums.update({self.order_datum.hash(): self.order_datum})
620
+
621
+ # The swap is a single accumulating UTxO: the taker removes the offer
622
+ # they take and deposits the ask (at price or better) back into the SAME
623
+ # continuing output. The validator reads ``ask_given = ask_out - ask_in``
624
+ # and ``offer_taken = offer_in - offer_out`` from this output — there is
625
+ # no separate maker payment; the maker collects the accumulated ask on
626
+ # CLOSE. The datum is identical to the input EXCEPT prev_input = consumed
627
+ # TxOutRef.
628
+ cont_datum = CardanoSwapsSwapDatum.from_cbor(self.order_datum.to_cbor())
629
+ cont_datum.prev_input = CardanoSwapsSomeOutRef(
630
+ value=CardanoSwapsOutputReference(
631
+ transaction_id=CardanoSwapsTxId(tx_hash=bytes.fromhex(self.tx_hash)),
632
+ output_index=self.tx_index,
633
+ ),
634
+ )
635
+
636
+ # Continuing value = consumed value, less the offer taken, plus the ask
637
+ # paid in. Beacons and any min-ADA carrier are preserved verbatim.
638
+ cont_balance: dict[str, int] = {"lovelace": input_utxo.output.amount.coin}
639
+ multi_asset = input_utxo.output.amount.multi_asset
640
+ if multi_asset is not None:
641
+ for policy, names in multi_asset.data.items():
642
+ for name, qty in names.items():
643
+ cont_balance[bytes(policy).hex() + bytes(name).hex()] = qty
644
+ cont_balance[offer_unit] = cont_balance.get(offer_unit, 0) - offer_taken
645
+ cont_balance[ask_unit] = cont_balance.get(ask_unit, 0) + ask_given
646
+ cont_assets = Assets(
647
+ root={
648
+ unit: qty
649
+ for unit, qty in cont_balance.items()
650
+ if qty != 0 or unit == "lovelace"
651
+ },
652
+ )
653
+
654
+ cont_txo = TransactionOutput(
655
+ address=input_utxo.output.address,
656
+ amount=asset_to_value(cont_assets),
657
+ datum=cont_datum,
658
+ )
659
+ cont_txo.amount.coin = max(
660
+ cont_txo.amount.coin,
661
+ min_lovelace(tx_builder.context, output=cont_txo),
662
+ )
663
+ tx_builder.add_output(cont_txo)
664
+
665
+ return cont_txo, cont_datum
666
+
667
+ def build_close(
668
+ self,
669
+ tx_builder: TransactionBuilder,
670
+ owner_address: Address | None = None,
671
+ ) -> CardanoSwapsSwapDatum:
672
+ """Build a CLOSE: spend the swap UTxO and burn the three beacons.
673
+
674
+ Spends the resting UTxO with ``SpendWithMint`` and burns -1 of each
675
+ beacon under the beacon policy with ``CreateOrCloseSwaps``. The owner's
676
+ stake credential must authorize the spend (sign with the stake key for a
677
+ vkey stake credential, or run the stake script); reclaim of the offer
678
+ asset to the owner is handled by change.
679
+
680
+ Returns:
681
+ The spent (resting) ``SwapDatum``.
682
+ """
683
+ if owner_address is None:
684
+ raise ValueError("build_close requires the owner address.")
685
+
686
+ input_utxo = self._input_utxo(owner_address)
687
+ tx_builder.add_script_input(
688
+ utxo=input_utxo,
689
+ script=self._swap_script(),
690
+ redeemer=Redeemer(SpendWithMint()),
691
+ )
692
+ tx_builder.datums.update({self.order_datum.hash(): self.order_datum})
693
+
694
+ # Burn the three beacons.
695
+ tx_builder.add_minting_script(
696
+ script=self._beacon_script(),
697
+ redeemer=Redeemer(CreateOrCloseSwaps()),
698
+ )
699
+ self._accumulate_mint(
700
+ tx_builder,
701
+ self._beacon_mint_assets(self.order_datum, -1),
702
+ )
703
+
704
+ # NOTE: the validator gates the close on the swap address' STAKING
705
+ # credential, not its payment credential. For a vkey stake credential the
706
+ # owner's stake key must be among the signatories — the caller supplies it
707
+ # via ``build_and_sign(signing_keys=[...])`` and/or
708
+ # ``tx_builder.required_signers``; for a script stake credential the stake
709
+ # script must be executed (a withdrawal). This builder only assembles the
710
+ # spend + burn; signing/withdrawal is the caller's responsibility.
711
+ return self.order_datum
@@ -1,4 +1,4 @@
1
- """Utility functions for handling asset information."""
1
+ """Utility functions for assets and Plutus scripts."""
2
2
 
3
3
  import json
4
4
  from datetime import datetime
@@ -168,3 +168,37 @@ def naturalize_assets(assets: Assets) -> dict[str, Decimal]:
168
168
  nat_assets[unit] = Decimal(quantity) / Decimal(10 ** asset_decimals(unit))
169
169
 
170
170
  return nat_assets
171
+
172
+
173
+ def apply_params_to_script(script_cbor: bytes, *params: bytes | int) -> bytes:
174
+ """Apply Plutus ``Data`` parameters to a parameterized Plutus script.
175
+
176
+ A pure-Python equivalent of ``aiken blueprint apply`` (via the optional
177
+ ``uplc`` library, imported lazily): the script term is wrapped in an
178
+ ``Apply`` node per parameter and re-serialized, so a parameterized
179
+ validator's deployed script — and hence its on-chain script hash /
180
+ minting-policy id — can be derived without external tooling.
181
+
182
+ Parameters are applied left-to-right as ``Data`` constants (``bytes`` -> a
183
+ Data bytestring, ``int`` -> a Data integer). ``script_cbor`` and the return
184
+ value are the CBOR-wrapped flat encodings used by ``PlutusV2Script`` and
185
+ ``plutus.json`` ``compiledCode``.
186
+
187
+ Args:
188
+ script_cbor: The un-applied compiled script, CBOR-wrapped.
189
+ *params: The Data parameters to apply, in order.
190
+
191
+ Returns:
192
+ The parameter-applied compiled script, CBOR-wrapped.
193
+ """
194
+ from uplc.ast import PlutusByteString # type: ignore[import-untyped]
195
+ from uplc.ast import PlutusInteger # type: ignore[import-untyped]
196
+ from uplc.tools import apply # type: ignore[import-untyped]
197
+ from uplc.tools import flatten # type: ignore[import-untyped]
198
+ from uplc.tools import unflatten # type: ignore[import-untyped]
199
+
200
+ constants = [
201
+ PlutusByteString(p) if isinstance(p, bytes) else PlutusInteger(p)
202
+ for p in params
203
+ ]
204
+ return flatten(apply(unflatten(script_cbor), *constants))