@laceio/sdk 0.1.0 → 0.1.1-canary.0

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 (408) hide show
  1. package/dist/{Ed25519PublicKey-lvu1s54U.cjs → Address-AMKTo-dA.cjs} +17150 -61
  2. package/dist/{Ed25519PublicKey-CjnuAYRR.js → Address-_c_-9hLC.js} +16955 -264
  3. package/dist/{Bip32Account-r3KAhCgU.js → Bip32Account-CaW1zC9Y.js} +4 -5
  4. package/dist/{Bip32Account-Cz3h0ZUa.cjs → Bip32Account-DOaGmKpv.cjs} +4 -5
  5. package/dist/Cardano-B-gyqBBD.js +311 -0
  6. package/dist/Cardano-BQ_af0Y3.cjs +322 -0
  7. package/dist/Ed25519PublicKey-B3QcR0J2.cjs +88 -0
  8. package/dist/Ed25519PublicKey-DIE19xHg.js +83 -0
  9. package/dist/InMemoryKeyAgent-Bc_xZY-v.cjs +652 -0
  10. package/dist/InMemoryKeyAgent-D8T3vtXy.js +631 -0
  11. package/dist/{PaymentAddress-DhbtuYb7.cjs → PaymentAddress-B4n1KZMx.cjs} +1 -2
  12. package/dist/{PaymentAddress-xSmGUQ-8.js → PaymentAddress-Cml3EA1n.js} +1 -2
  13. package/dist/Percent-Cfuw5YyL.cjs +11 -0
  14. package/dist/Percent-DpvyJ_93.js +5 -0
  15. package/dist/SodiumBip32Ed25519-BaD-kjyx.cjs +380 -0
  16. package/dist/SodiumBip32Ed25519-DCikAMsl.js +363 -0
  17. package/dist/{TransactionBuilder-CeJTNzm-.cjs → TransactionBuilder-BakTJvsJ.cjs} +59 -89
  18. package/dist/{TransactionBuilder-DGNDM4kt.js → TransactionBuilder-D2ErwVWv.js} +5 -71
  19. package/dist/_md-6zV2Cme0.cjs +684 -0
  20. package/dist/_md-gbUqlhqd.js +399 -0
  21. package/dist/_web-crypto-random-bytes-BvzwTYPG.js +197 -0
  22. package/dist/acc-module-NL_8mEHH.cjs +27654 -0
  23. package/dist/acc-module-ur8CkBd1.js +27624 -0
  24. package/dist/account-call-B7cF8ly4.cjs +293 -0
  25. package/dist/account-call-BF4rBcNw.js +3 -0
  26. package/dist/account-call-CoCq3V55.js +252 -0
  27. package/dist/account-call-DFcD257E.cjs +4 -0
  28. package/dist/account-guards-BeKbV9D1.cjs +41 -0
  29. package/dist/account-guards-qnFXuXSE.js +36 -0
  30. package/dist/{activity-type.stable-ClO9j_PH.cjs → activity-type-BPXxdHBP.cjs} +2 -1
  31. package/dist/{activity-type.stable-Ml0SOYv9.js → activity-type-C1geyPsH.js} +2 -1
  32. package/dist/address-OxwXIwbF.js +15502 -0
  33. package/dist/address-book-address-validator-BqYM0Uyp.cjs +41 -0
  34. package/dist/{address-book-address-validator-BhqRfmlL.cjs → address-book-address-validator-CGIcqn5W.cjs} +5 -4
  35. package/dist/{address-book-address-validator-VqIOB8PY.js → address-book-address-validator-FkkYim3z.js} +6 -4
  36. package/dist/address-book-address-validator-RSx2Jozy.js +39 -0
  37. package/dist/address-lECYBd9-.cjs +13043 -0
  38. package/dist/{apply-vkey-witnesses-Ctzk7eye.cjs → apply-vkey-witnesses-1jyczTkw.cjs} +1 -1
  39. package/dist/{apply-vkey-witnesses-Dfo7XfhL.js → apply-vkey-witnesses-Byb40-yJ.js} +1 -1
  40. package/dist/artefact-loader-Cqc9eSkL.cjs +182 -0
  41. package/dist/artefact-loader-D0T2f6F7.js +159 -0
  42. package/dist/auto-dismiss-failure-on-success-BWJceNXd.cjs +37 -0
  43. package/dist/auto-dismiss-failure-on-success-Bpod-v4r.js +31 -0
  44. package/dist/bip32-EPpcFrpH.cjs +3851 -0
  45. package/dist/{bip32ed25519-D2In8WzR.cjs → bip32ed25519-CRiG-Kjg.cjs} +5 -4
  46. package/dist/{bip32ed25519-qsLH_eWO.js → bip32ed25519-E-fCUZNv.js} +6 -4
  47. package/dist/bip39-DZomfqeY.js +9 -0
  48. package/dist/bip39-MAck9iod.cjs +26 -0
  49. package/dist/bitcoin-data-provider-D0OT_Uiu.cjs +26 -0
  50. package/dist/bitcoin-data-provider-LN_8oamW.js +21 -0
  51. package/dist/bitcoin.cjs +48 -0
  52. package/dist/bitcoin.d.ts +29805 -1
  53. package/dist/bitcoin.js +22 -0
  54. package/dist/{blake2b-BGHUWMsV.js → blake2b-BL-eR5G2.js} +6 -4
  55. package/dist/{blake2b-DspUqw68.cjs → blake2b-Iy324FwH.cjs} +5 -4
  56. package/dist/{blockfrost-tx-submit-provider-BUEOmdvc.js → blockfrost-tx-submit-provider-BHDLgOIE.js} +9 -162
  57. package/dist/{blockfrost-tx-submit-provider-B5dWbdrZ.cjs → blockfrost-tx-submit-provider-BxC19smv.cjs} +7 -171
  58. package/dist/{blockingWithLatestFrom-BhaE7pnA.js → blockingWithLatestFrom-FUfZFEUG.js} +1 -1
  59. package/dist/{blockingWithLatestFrom-DkuT3G2a.cjs → blockingWithLatestFrom-bmCPgA3c.cjs} +1 -1
  60. package/dist/bn-BFq5a4dk.cjs +2553 -0
  61. package/dist/{SodiumBip32Ed25519-BEfo1Q9m.js → browser-D5jRKv_L.js} +4 -401
  62. package/dist/bytes.vo-CziaOsba.js +24 -0
  63. package/dist/bytes.vo-hecRu3YZ.cjs +35 -0
  64. package/dist/{cardano-network-id-to-network-type-PXvn1ycQ.cjs → cardano-network-id-to-network-type-BwG_d6QP.cjs} +1 -1
  65. package/dist/{cardano-network-id-to-network-type-DHgKhDAK.js → cardano-network-id-to-network-type-CYJheetp.js} +1 -1
  66. package/dist/{cardano-network-id.vo-CbNNKd-k.cjs → cardano-network-id.vo-C5VuBXT3.cjs} +2 -2
  67. package/dist/{cardano-network-id.vo-CTYe-Oix.js → cardano-network-id.vo-CeLJqmb_.js} +2 -2
  68. package/dist/{cardano-tx-id.vo-BMwemq5x.js → cardano-tx-id.vo-DJ70NC0-.js} +1 -1
  69. package/dist/{cardano-tx-id.vo-NY-gCOWJ.cjs → cardano-tx-id.vo-lnyO9XaW.cjs} +1 -1
  70. package/dist/cardano.cjs +31 -24
  71. package/dist/cardano.d.ts +27602 -8121
  72. package/dist/cardano.js +21 -13
  73. package/dist/client-Bw2foG5E.cjs +808 -0
  74. package/dist/const-BB3MccLt.js +153 -0
  75. package/dist/{const-BErUlcZw.cjs → const-BWBAzOmN.cjs} +1 -1
  76. package/dist/const-CSWmOyzn.cjs +42 -0
  77. package/dist/{const-Dy8Ctnpd.cjs → const-D3mCkwrM.cjs} +5 -5
  78. package/dist/const-DW7z3R5k.js +13 -0
  79. package/dist/{const-DeUA7zYl.js → const-DbuUHSzR.js} +5 -5
  80. package/dist/const-DgBzKfz6.cjs +31995 -0
  81. package/dist/{const-DVW6DGkZ.js → const-xGSNCtwQ.js} +1 -1
  82. package/dist/contract-BsIGhsYa.js +171 -0
  83. package/dist/contract-DD6CGnpB.cjs +189 -0
  84. package/dist/create-add-accounts-2mQA0ST2.js +102 -0
  85. package/dist/create-add-accounts-B6mR2NMn.cjs +114 -0
  86. package/dist/{delegation-tx-builder-CF0VunDW.cjs → delegation-tx-builder-Cp30jtj9.cjs} +9 -8
  87. package/dist/{delegation-tx-builder-C8LQxU2R.js → delegation-tx-builder-DUMikSTg.js} +9 -7
  88. package/dist/{deregistration-tx-builder-DcUXA3Ls.cjs → deregistration-tx-builder-B-ZeY-dR.cjs} +10 -9
  89. package/dist/{deregistration-tx-builder-BOBPP8m_.js → deregistration-tx-builder-CRLrK99U.js} +10 -8
  90. package/dist/device-commitment-hex.vo-BIUYUNz1.cjs +31 -0
  91. package/dist/device-commitment-hex.vo-D9FsYLn7.js +26 -0
  92. package/dist/device-key-BToCoS4D.js +2394 -0
  93. package/dist/device-key-D6euvxFi.cjs +2489 -0
  94. package/dist/device-readers-BHJeNR8I.js +3 -0
  95. package/dist/device-readers-Dw7uTNM5.cjs +167 -0
  96. package/dist/device-readers-b4g2j3QL.js +144 -0
  97. package/dist/device-readers-cxcJcWq5.cjs +7 -0
  98. package/dist/dist-B00GN41m.js +141 -0
  99. package/dist/dist-BA1xnF9O.cjs +4413 -0
  100. package/dist/dist-BmHOXLe8.cjs +3814 -0
  101. package/dist/dist-Br0OVXKt.cjs +535 -0
  102. package/dist/dist-C1JK9C1I.cjs +3452 -0
  103. package/dist/dist-DKEViNEI.cjs +1306 -0
  104. package/dist/dist-Dwqx184a.cjs +43175 -0
  105. package/dist/dist-MJof_Ky1.cjs +145 -0
  106. package/dist/dist-p84sVVJ9.cjs +1394 -0
  107. package/dist/{earn-rewards-tx-builder-BRlTueHS.js → earn-rewards-tx-builder-8G_rouIk.js} +9 -7
  108. package/dist/{earn-rewards-tx-builder-RgO0QUY8.cjs → earn-rewards-tx-builder-cOUNV58E.cjs} +9 -8
  109. package/dist/enc-key-circuits-0Dud78IB.js +13 -0
  110. package/dist/enc-key-circuits-yjPHbuo_.cjs +24 -0
  111. package/dist/english-BZ_ebp2-.cjs +2057 -0
  112. package/dist/envelopes-B3QCzo5p.js +2658 -0
  113. package/dist/envelopes-BdE1xlgz.cjs +2777 -0
  114. package/dist/errors-BA3b8aVu.cjs +584 -0
  115. package/dist/errors-pkQdxW-7.js +392 -0
  116. package/dist/experimental.cjs +6820 -0
  117. package/dist/experimental.d.ts +63552 -0
  118. package/dist/experimental.js +6670 -0
  119. package/dist/fees-6C2uEWFK.js +653 -0
  120. package/dist/fees-DtC71fOx.cjs +820 -0
  121. package/dist/gated-call-BrPjKolT.js +3 -0
  122. package/dist/gated-call-CcsuW9L5.js +691 -0
  123. package/dist/gated-call-DdDrxEq5.cjs +732 -0
  124. package/dist/gated-call-Dk7PazhG.cjs +4 -0
  125. package/dist/grant-rules-B7eDuD5G.cjs +5468 -0
  126. package/dist/grant-rules-qLrKUIR3.js +5217 -0
  127. package/dist/grant-status-CDmZpx32.js +296 -0
  128. package/dist/grant-status-CQtwjLsA.cjs +337 -0
  129. package/dist/grants-Bnb-3Hkv.cjs +16 -0
  130. package/dist/grants-CIb6nOV7.cjs +273 -0
  131. package/dist/grants-CYY0AquW.js +202 -0
  132. package/dist/grants-DOsFSotP.js +4 -0
  133. package/dist/{group-cardano-addresses-by-account-C-1GHUu8.cjs → group-cardano-addresses-by-account-BGOtPV-Y.cjs} +4 -40
  134. package/dist/{group-cardano-addresses-by-account-p4dXmCoj.js → group-cardano-addresses-by-account-TqvESg9x.js} +6 -37
  135. package/dist/{guarded-transaction-signer-DZSGHp1K.js → guarded-transaction-signer-BbVDE9Nn.js} +12 -18
  136. package/dist/{guarded-transaction-signer-BLSezbfV.cjs → guarded-transaction-signer-DTLjAkif.cjs} +26 -26
  137. package/dist/hex-BVtOKJki.cjs +263 -0
  138. package/dist/hex-CGYJPK_Z.js +192 -0
  139. package/dist/http-client-B7_kBTYn.js +155 -0
  140. package/dist/http-client-DYQ_7Rdf.cjs +167 -0
  141. package/dist/identity-DxPhxkZs.js +239 -0
  142. package/dist/identity-y41Yle03.cjs +274 -0
  143. package/dist/in-memory-wallet-integration-5BKiAvPT.js +20861 -0
  144. package/dist/{in-memory-wallet-integration-CUTa68ET.js → in-memory-wallet-integration-BCOqDgbt.js} +10 -107
  145. package/dist/in-memory-wallet-integration-Bi8I9Dso.js +128 -0
  146. package/dist/in-memory-wallet-integration-Bsl2ONua.cjs +239 -0
  147. package/dist/in-memory-wallet-integration-DZ0CAyvY.cjs +20865 -0
  148. package/dist/{in-memory-wallet-integration-Dw_S7amX.cjs → in-memory-wallet-integration-DsFm8gLk.cjs} +21 -112
  149. package/dist/{in-memory-wallet-integration-Z4Bp_DTv.cjs → in-memory-wallet-integration-aW_SF-go.cjs} +8 -5
  150. package/dist/{in-memory-wallet-integration-B6cbAlvD.js → in-memory-wallet-integration-mIEc8EfR.js} +9 -5
  151. package/dist/index.cjs +95 -58
  152. package/dist/index.d.ts +45152 -13448
  153. package/dist/index.js +55 -28
  154. package/dist/{init-CVi8J0zk.cjs → init-5SfWgCYD.cjs} +3 -3
  155. package/dist/{init-Ci7aqWCf2.cjs → init-8NVWbo302.cjs} +2 -2
  156. package/dist/init-B-C7n6u6.js +5 -0
  157. package/dist/{init-BFMWRf-L.js → init-B-ppfGO-.js} +1 -1
  158. package/dist/{init-IiR093Jw.cjs → init-B2OUhY5T.cjs} +2 -2
  159. package/dist/{init-DXnus199.cjs → init-B6Eg_o6G.cjs} +5 -5
  160. package/dist/{init-4hDl5bpr.js → init-BAWMHL1k.js} +2 -2
  161. package/dist/init-BC2CZZOu2.cjs +2002 -0
  162. package/dist/{init-a228hXnS.cjs → init-BF-a2p6E.cjs} +65 -73
  163. package/dist/{init-fFirX8OY.cjs → init-BFxlQAD8.cjs} +1 -1
  164. package/dist/init-BJE1ry0O.js +17 -0
  165. package/dist/{init-BVnI4z2J.js → init-BPKBVOHU.js} +2 -2
  166. package/dist/{init-bdqZBd-o.js → init-BSP_xcpB.js} +2 -2
  167. package/dist/{init-CFNOn3lo.js → init-BSd7mznK.js} +3 -3
  168. package/dist/{init-MjR4swi6.js → init-BUJTT0MN.js} +36 -43
  169. package/dist/init-BUpFzYsD.cjs +143 -0
  170. package/dist/{init-BvpYHyLH.cjs → init-BZ-WyAIL.cjs} +3 -3
  171. package/dist/init-BZdfdLX7.js +422 -0
  172. package/dist/{init-CJeOys1w.cjs → init-B_HWNXMD.cjs} +2 -2
  173. package/dist/init-BbIMt6xU.cjs +6 -0
  174. package/dist/{init-BYdN1aqE.cjs → init-BewG6swt.cjs} +2 -2
  175. package/dist/{init-BX0AZAVH2.js → init-Bilqsbng.js} +2 -2
  176. package/dist/{init-Tpn9VkFu.cjs → init-BpwlQ6wF.cjs} +1 -1
  177. package/dist/{init-D5-P0HXK.js → init-BtQOzUjX.js} +1 -1
  178. package/dist/{init-BvGz3qUd2.js → init-CCTscPaY2.js} +2 -2
  179. package/dist/{init-D4hPipCD.cjs → init-CH0yyBof.cjs} +1 -1
  180. package/dist/init-CaIkrXal.cjs +121 -0
  181. package/dist/init-CkZy4IMf.cjs +107 -0
  182. package/dist/{init-Drr2f_Pr.js → init-CmfcY7SV.js} +1 -1
  183. package/dist/{init-Kf3_OLv6.js → init-CnJAlks_.js} +1 -1
  184. package/dist/{init-Cdjt_z-b.cjs → init-CqqmtAIK.cjs} +4 -4
  185. package/dist/{init-Bb0avZeZ.js → init-Cu2znmIc.js} +5 -5
  186. package/dist/init-Cz5LAizN.js +2001 -0
  187. package/dist/init-D4WM3-Mn.cjs +18 -0
  188. package/dist/init-DMi2FGpf.cjs +424 -0
  189. package/dist/{init-lcaUSOOG.js → init-DPTJohPd.js} +1 -1
  190. package/dist/{init-BTYLMF9F2.cjs → init-DexLSprL2.cjs} +258 -61
  191. package/dist/init-DfKPgD21.js +106 -0
  192. package/dist/init-DgzRsI-C.js +141 -0
  193. package/dist/init-Dj4Cn7-P.js +4789 -0
  194. package/dist/init-DktAnYI0.cjs +2354 -0
  195. package/dist/{init-DFwhgert.cjs → init-DmyuM9Lo.cjs} +1 -1
  196. package/dist/{init-CX_zLXc6.js → init-IBnizp1U.js} +5 -5
  197. package/dist/init-MDKB6tGI2.cjs +4790 -0
  198. package/dist/{init-DbZ5byj42.js → init-TbB38KNt2.js} +258 -60
  199. package/dist/init-X6m_cqDy.js +2353 -0
  200. package/dist/{init-DvVgKCaP2.cjs → init-bzvl1JhF.cjs} +2 -2
  201. package/dist/{init-BgrY29gW.js → init-dsYkC_0B.js} +4 -4
  202. package/dist/{init-B5BsI3DR.cjs → init-kvn5FlPS.cjs} +7 -7
  203. package/dist/init-vfy9DW_E.js +119 -0
  204. package/dist/{init-BE_6Jyc4.js → init-w7xl6LsP.js} +3 -3
  205. package/dist/{init-DEsWVyn5.cjs → init-wgiBUmyW.cjs} +1 -1
  206. package/dist/{is-retriable-error-DWv_ob-f.js → is-retriable-error-CmFR20pe.js} +1 -1
  207. package/dist/{is-retriable-error-Dtb4JGjd.cjs → is-retriable-error-D2yo0_lS.cjs} +1 -1
  208. package/dist/jubjub-authoriser-A2c8vmkN.js +84 -0
  209. package/dist/jubjub-authoriser-D3ZDgzcF.cjs +85 -0
  210. package/dist/key-source-CcLTx8HK.js +2761 -0
  211. package/dist/key-source-D2tTAag-.cjs +806 -0
  212. package/dist/{key-value-store-DTX1quRS.js → key-value-store-DZcNDB3n.js} +1 -1
  213. package/dist/{key-value-store-B-XRIwaM.cjs → key-value-store-Htj4RgMe.cjs} +1 -1
  214. package/dist/keyDerivation-DcKRUCVh.cjs +274 -0
  215. package/dist/keyDerivation-QGesIMbS.js +4077 -0
  216. package/dist/{lace-context-FpU3iG3f.cjs → lace-context-BN0b_ury.cjs} +4 -4
  217. package/dist/{lace-context-BQvvxVLm.js → lace-context-DMRJ87Vg.js} +4 -4
  218. package/dist/lib-BC-62Byh.cjs +2359 -0
  219. package/dist/lib-JYqcAX0c.js +2354 -0
  220. package/dist/midnight-account-id.vo--Vcx34Xk.cjs +10 -0
  221. package/dist/midnight-account-id.vo-BuB8fiVO.js +5 -0
  222. package/dist/midnight-engine-B7D2hbHo.js +5 -0
  223. package/dist/midnight-engine-D5ikN_5F.cjs +6 -0
  224. package/dist/midnight-wallet-BOdFD6SX.cjs +24 -0
  225. package/dist/midnight-wallet-CAqZrNVe.js +18 -0
  226. package/dist/midnight.cjs +1412 -0
  227. package/dist/midnight.d.ts +28609 -0
  228. package/dist/midnight.js +1355 -0
  229. package/dist/modules-cardano-BiL9hoM3.js +996 -0
  230. package/dist/modules-cardano-UeXks3sd.cjs +1109 -0
  231. package/dist/node-relay-BOuvdZvC.cjs +49 -0
  232. package/dist/node-relay-CLtc5n1v.js +48 -0
  233. package/dist/outputs-5qdW8s-f.js +196 -0
  234. package/dist/outputs-BEM1GPMf.cjs +339 -0
  235. package/dist/{SodiumBip32Ed25519-BQUcrQWX.cjs → pbkdf2-B1veireP.cjs} +6 -411
  236. package/dist/pending-operation-CfTUNQVD.cjs +26 -0
  237. package/dist/pending-operation-Ljijb1Uh.js +21 -0
  238. package/dist/providers-1on_fwJO.cjs +364 -0
  239. package/dist/providers-C1MsxfMA.js +341 -0
  240. package/dist/redacted-4SJPqt-G.js +81 -0
  241. package/dist/redacted-DwxIDdIF.cjs +86 -0
  242. package/dist/{reselect-DPML1uFN.cjs → reselect-kP4DaAII.cjs} +1 -1
  243. package/dist/{retry-config-oNtfdyrG.cjs → retry-config-BGv5q5QM.cjs} +1 -1
  244. package/dist/{retry-config-VdbuqBsI.js → retry-config-Bu5Yn5ua.js} +1 -1
  245. package/dist/safe-buffer-CqGpSFCQ.js +43 -0
  246. package/dist/safe-buffer-DbXbwhUQ.cjs +48 -0
  247. package/dist/sdk-1SRu8Ype.cjs +1445 -0
  248. package/dist/sdk-BneJm7ey.js +1249 -0
  249. package/dist/sdk-init-9TG1ZvHF.cjs +3795 -0
  250. package/dist/sdk-init-Ce3mD-8F.js +1047 -0
  251. package/dist/secret-box-CSmRp6Tt.js +3777 -0
  252. package/dist/secret-box-ZfbYwBgS.cjs +6970 -0
  253. package/dist/send-proof-progress-DtAcrMP6.cjs +33 -0
  254. package/dist/send-proof-progress-Dx2cUz1B.js +22 -0
  255. package/dist/sha2-DaRJZOjk.cjs +390 -0
  256. package/dist/sha2-c4sB6peD.js +378 -0
  257. package/dist/side-effects-C3BhLrdP.js +768 -0
  258. package/dist/side-effects-DG0R-KFc.cjs +804 -0
  259. package/dist/sign-bitcoin-transaction-with-seed-C7vq9om8.js +147 -0
  260. package/dist/sign-bitcoin-transaction-with-seed-Ds1dwzan.cjs +158 -0
  261. package/dist/signer--2I6poMD.js +2 -0
  262. package/dist/signer-CX21Ups_.cjs +121 -0
  263. package/dist/signer-Cnn9q9oY.js +92 -0
  264. package/dist/signer-DbSxjk0F.cjs +7 -0
  265. package/dist/signer-factory-B5oFuUZ-.cjs +130 -0
  266. package/dist/signer-factory-BAt8FPNc.cjs +281 -0
  267. package/dist/signer-factory-BGE4nL4k.js +129 -0
  268. package/dist/signer-factory-C4pgsiJi.js +281 -0
  269. package/dist/{signer-factory-DySc1wRX.cjs → signer-factory-D95jQIPV.cjs} +9 -6
  270. package/dist/{signer-factory-D4HrwFPl.js → signer-factory-PrpsGX-8.js} +10 -6
  271. package/dist/{slice-BZTQgYSs.js → slice-B-xJcuT1.js} +4 -4
  272. package/dist/slice-B4QOYaOG.cjs +105 -0
  273. package/dist/{slice-CtIMw2YT.cjs → slice-B9QKuhi0.cjs} +3 -3
  274. package/dist/slice-BKnGkAkd.cjs +9031 -0
  275. package/dist/{slice-Bhwmqn1p.cjs → slice-Bf4enJKZ.cjs} +8 -8
  276. package/dist/{slice-DaNVz7_l.js → slice-ByNWnxKS.js} +8 -8
  277. package/dist/{slice-C2UDQrdt.js → slice-C1zOeseG.js} +449 -355
  278. package/dist/{slice-DFLyKRm5.cjs → slice-C4toRM6A.cjs} +2 -2
  279. package/dist/{slice-Cx4dULDA.cjs → slice-C8YVv4Qn.cjs} +1 -1
  280. package/dist/{slice-jnpOgiSZ.cjs → slice-CaMfviYu.cjs} +3 -3
  281. package/dist/{slice-DFvrwUur.js → slice-CgeRsYRg.js} +6 -6
  282. package/dist/slice-Cwt8jShq.js +9013 -0
  283. package/dist/{slice-zNEspZND.js → slice-D0VzpMbY.js} +2 -2
  284. package/dist/slice-D14F78Ca.js +87 -0
  285. package/dist/{slice-F6A1YKOF.cjs → slice-DLflttAu.cjs} +479 -366
  286. package/dist/{slice-C96EdeY1.js → slice-DcO-iNz2.js} +1 -1
  287. package/dist/{slice-C19quNWM.cjs → slice-DimtxfAT.cjs} +2 -2
  288. package/dist/{slice-KiNoGwD9.js → slice-DlTEqIoI.js} +1 -1
  289. package/dist/{slice-ws8PccRX.js → slice-Dn0WojD7.js} +3 -3
  290. package/dist/{slice-BLSxLmie.cjs → slice-Dongunf7.cjs} +4 -4
  291. package/dist/{slice-B6vPum19.js → slice-DpufJAjJ.js} +2 -2
  292. package/dist/{slice-exhhmucR.cjs → slice-DtitVc9L.cjs} +7 -7
  293. package/dist/{slice-Bjj2t2ut.cjs → slice-WMMMc1Xv.cjs} +2 -2
  294. package/dist/slice-a8VuIqzS.cjs +378 -0
  295. package/dist/slice-bRASyM-m.js +354 -0
  296. package/dist/{slice-CBFrgHSH.js → slice-hNyKbDry.js} +3 -3
  297. package/dist/{InMemoryKeyAgent-pyB2gA-N.cjs → src-BayYMIjw.cjs} +20 -8228
  298. package/dist/{InMemoryKeyAgent-D-3HejxN.js → src-CgINPlSx.js} +29 -4811
  299. package/dist/store-AUlTfcI_.js +37 -0
  300. package/dist/store-C7AneHEY.cjs +66 -0
  301. package/dist/submit-BuBCVi-l.cjs +87 -0
  302. package/dist/submit-D37lxFrK.js +70 -0
  303. package/dist/submit-bitcoin-tx-DFLnhidO.js +983 -0
  304. package/dist/submit-bitcoin-tx-sih2hSfb.cjs +1049 -0
  305. package/dist/{subtractValueQuantities-DfZg6oiG.js → subtractValueQuantities-BuDJVxq6.js} +3 -22
  306. package/dist/{subtractValueQuantities-C8qDTtgH.cjs → subtractValueQuantities-CqR606HV.cjs} +2 -32
  307. package/dist/{sync-operation-utils-BrQyWFS_.cjs → sync-operation-utils--Z_IQfyW.cjs} +0 -66
  308. package/dist/{sync-operation-utils-CAaCGvEN.js → sync-operation-utils-BimBpvI3.js} +1 -37
  309. package/dist/token-color.vo-CItgoOjw.js +26 -0
  310. package/dist/token-color.vo-Ca4hUXfh.cjs +37 -0
  311. package/dist/token-id-mapper-BVau2RXa.js +16 -0
  312. package/dist/token-id-mapper-CSeLzM4o.cjs +18 -0
  313. package/dist/{token-id-mapper-pDaTxpw2.js → token-id-mapper-ClBeIdJR.js} +2 -2
  314. package/dist/{token-id-mapper-DYcd8hhQ.cjs → token-id-mapper-D0wFdbOy.cjs} +2 -2
  315. package/dist/types-BhzypKMS.cjs +15 -0
  316. package/dist/types-DTE1ctDB.js +10 -0
  317. package/dist/types-DXSxXGz0.js +12 -0
  318. package/dist/types-Dls6Z5gV.cjs +23 -0
  319. package/dist/{util-P4PX2muj.cjs → util-D1YlKzrc.cjs} +2 -2
  320. package/dist/{util-CQSJU0wT.js → util-DHxtzUzY.js} +2 -2
  321. package/dist/{utils-Bv4OHT4S.cjs → utils-DYf4iZKX.cjs} +7 -1
  322. package/dist/{utils-DrtzChYN.js → utils-DcnoETEP.js} +2 -2
  323. package/dist/utils-Du0YXRcU.cjs +112 -0
  324. package/dist/utils-fF_rnZe3.js +65 -0
  325. package/dist/v8Signatures-ClGpA8mS.cjs +71 -0
  326. package/dist/{vote-delegation-tx-builder-69gOEkCB.cjs → vote-delegation-tx-builder-BjZxCOd3.cjs} +9 -8
  327. package/dist/{vote-delegation-tx-builder-BRJ_pf-c.js → vote-delegation-tx-builder-DRJwPXkD.js} +9 -7
  328. package/dist/watch-FGE4B_Ge.js +13056 -0
  329. package/dist/watch-imlsRZDp.cjs +52207 -0
  330. package/package.json +95 -3
  331. package/dist/Address-4KjeDt_x.cjs +0 -17269
  332. package/dist/Address-_VPkf7Xh.js +0 -17101
  333. package/dist/account-guards.stable-BKyYPj4z.cjs +0 -10
  334. package/dist/account-guards.stable-BdmQ43vC.js +0 -5
  335. package/dist/init-BR9ZIbED.cjs +0 -378
  336. package/dist/init-B_k1RUN_.cjs +0 -21
  337. package/dist/init-Bs3b5ZQH.js +0 -378
  338. package/dist/init-ChfcrHgG2.cjs +0 -4354
  339. package/dist/init-D1uUMMpG.js +0 -19
  340. package/dist/init-DV1OSZiK.js +0 -4352
  341. package/dist/modules-cardano-BINzpXpk.js +0 -5454
  342. package/dist/modules-cardano-Dt8OwoQb.cjs +0 -5640
  343. package/dist/slice-Caf98m0d2.js +0 -107
  344. package/dist/slice-PM6llajQ2.cjs +0 -125
  345. /package/dist/{BigIntMath-CoJWXt1h.cjs → BigIntMath-CBciEQn2.cjs} +0 -0
  346. /package/dist/{BigIntMath-BWUJOsOW.js → BigIntMath-X3EniYDo.js} +0 -0
  347. /package/dist/{ChainId-sdLmlIRk.cjs → ChainId-CNPSU9nn.cjs} +0 -0
  348. /package/dist/{ChainId-CKbm87yQ.js → ChainId-CeoG8Sov.js} +0 -0
  349. /package/dist/{_polyfill-node.process-4fnlkbBi.cjs → _polyfill-node.process-5UxyjoHG.cjs} +0 -0
  350. /package/dist/{account-id.vo-Xpgnu-av.cjs → account-id.vo-DYZBZQsz.cjs} +0 -0
  351. /package/dist/{account-id.vo-DP0BtOoF.js → account-id.vo-KDTWJ-W5.js} +0 -0
  352. /package/dist/{actions-BNagEO6m.cjs → actions-BnHFVSr_.cjs} +0 -0
  353. /package/dist/{actions-BWTyAr7B.js → actions-Cwj0ApZf.js} +0 -0
  354. /package/dist/{asyncoption-po7-RiA5.cjs → asyncoption-CYcZNBRM.cjs} +0 -0
  355. /package/dist/{asyncoption-CzIVH5Y1.js → asyncoption-DZQGfznq.js} +0 -0
  356. /package/dist/{bignum.vo-DhhZ9HP1.js → bignum.vo-Co6tMH2q.js} +0 -0
  357. /package/dist/{bignum.vo-CpkrIXjH.cjs → bignum.vo-Du-bpvar.cjs} +0 -0
  358. /package/dist/{blockchain-network-id.vo-CbgrAY_K.js → blockchain-network-id.vo-C6Jowh8t.js} +0 -0
  359. /package/dist/{blockchain-network-id.vo-5iC6Kffo.cjs → blockchain-network-id.vo-DEQ14dYj.cjs} +0 -0
  360. /package/dist/{composite-signer-factory-DABpCe1_.js → composite-signer-factory-D9RWY-1V.js} +0 -0
  361. /package/dist/{composite-signer-factory-BizuNPP7.cjs → composite-signer-factory-v4Pxn8bA.cjs} +0 -0
  362. /package/dist/{error-ggOihIfv.cjs → error-B6wj9nPv.cjs} +0 -0
  363. /package/dist/{error-DHKO2iS4.js → error-CvEYL98P.js} +0 -0
  364. /package/dist/{esm-browser-DnXNvpcn.cjs → esm-browser-CZbMeVod.cjs} +0 -0
  365. /package/dist/{esm-browser-BBVQuyQv.js → esm-browser-DpAD_RKv.js} +0 -0
  366. /package/dist/{feature-flag-key.vo-DoGK5Bfh.js → feature-flag-key.vo-9reURsOQ.js} +0 -0
  367. /package/dist/{feature-flag-key.vo-CP1Z9s8e.cjs → feature-flag-key.vo-KTIdnzYX.cjs} +0 -0
  368. /package/dist/{first-state-of-status-Bx4-QQ5m.cjs → first-state-of-status-9ICeDNSa.cjs} +0 -0
  369. /package/dist/{first-state-of-status-D7XzEuSa.js → first-state-of-status-BscbKj_t.js} +0 -0
  370. /package/dist/{init-4319psUJ.cjs → init-BT67pd9x.cjs} +0 -0
  371. /package/dist/{init-AFUM1TSg.js → init-CHk1fvJ2.js} +0 -0
  372. /package/dist/{init-ZX90nEJe2.js → init-DIyNx6DZ.js} +0 -0
  373. /package/dist/{init-CTQ2g6Xt.js → init-DtxxFaCx.js} +0 -0
  374. /package/dist/{init-Dt1Xifg_2.cjs → init-J8QobR93.cjs} +0 -0
  375. /package/dist/{init-BNewLCqw.cjs → init-xzZPlQwB.cjs} +0 -0
  376. /package/dist/{is-redux-persist-state-DxJSsvxB.cjs → is-redux-persist-state-C5luvPEH.cjs} +0 -0
  377. /package/dist/{is-redux-persist-state-Bl2n4VMr.js → is-redux-persist-state-CjcqyCoh.js} +0 -0
  378. /package/dist/{isNotNil-CZxBMfL5.js → isNotNil-Be7gCY0m.js} +0 -0
  379. /package/dist/{isNotNil-C_SO1tcC.cjs → isNotNil-CrYIUFry.cjs} +0 -0
  380. /package/dist/{logging-BvMDKgl0.js → logging-BAMMfn5j.js} +0 -0
  381. /package/dist/{logging-Bh9ikph3.cjs → logging-CbxgIX9V.cjs} +0 -0
  382. /package/dist/{metadatum-B-sxBnSY.cjs → metadatum-BGvFqGPt.cjs} +0 -0
  383. /package/dist/{metadatum-Ego4jVas.js → metadatum-BVocMYy_.js} +0 -0
  384. /package/dist/{repo-slice-BzZQUTj8.cjs → repo-slice-BB_W-6OL.cjs} +0 -0
  385. /package/dist/{repo-slice-vCcQJotS.js → repo-slice-BQlssw9M.js} +0 -0
  386. /package/dist/{reselect-BPkMzw3k.js → reselect-JIgawvPe.js} +0 -0
  387. /package/dist/{serializable-BxFUShKY.js → serializable-F7OtYl8d.js} +0 -0
  388. /package/dist/{serializable-DWgkHhv_.cjs → serializable-n3UoxbZc.cjs} +0 -0
  389. /package/dist/{slice-DY-DNtSi.js → slice-5dGBAz6w.js} +0 -0
  390. /package/dist/{slice-BEFoeqgr.cjs → slice-BzJgg9FS.cjs} +0 -0
  391. /package/dist/{src-CPmX4KUh.cjs → src-CeBNkonz.cjs} +0 -0
  392. /package/dist/{src-XOZXPKxe.js → src-Cv1ifqbe.js} +0 -0
  393. /package/dist/{time-CQPt4FdU.js → time-ByoKevdZ.js} +0 -0
  394. /package/dist/{time-Blvoyh9G.cjs → time-qD7edGU0.cjs} +0 -0
  395. /package/dist/{time.vo-Xln4yGBU.js → time.vo-C17-tYq3.js} +0 -0
  396. /package/dist/{time.vo-rwWwJzmP.cjs → time.vo-TmYE-_Y2.cjs} +0 -0
  397. /package/dist/{toEmpty-CzmoZVLN.cjs → toEmpty-Db6V94Mf.cjs} +0 -0
  398. /package/dist/{toEmpty-DzLd-ZCi.js → toEmpty-Djj-QbpI.js} +0 -0
  399. /package/dist/{token-id.vo-l8fuSpem.cjs → token-id.vo-CZ622zcZ.cjs} +0 -0
  400. /package/dist/{token-id.vo-DMKSaMlc.js → token-id.vo-IDI9C_Qm.js} +0 -0
  401. /package/dist/{types-B61DQoFW.js → types-By-XeXWg.js} +0 -0
  402. /package/dist/{types-CUC-Kcqh.cjs → types-Cn6WlQV6.cjs} +0 -0
  403. /package/dist/{util-BV3iGOrz.cjs → util-Cp1h58uD.cjs} +0 -0
  404. /package/dist/{util-BsQKLorV.js → util-DHppXj7U.js} +0 -0
  405. /package/dist/{util-selector-CKIyoAOz.js → util-selector-BWmHpo92.js} +0 -0
  406. /package/dist/{util-selector-C9H5RsNw.cjs → util-selector-glXkO-jR.cjs} +0 -0
  407. /package/dist/{while-active-CNXeLxre.js → while-active-CDBnUlQA.js} +0 -0
  408. /package/dist/{while-active-BsDWSNrO.cjs → while-active-I2TlHtm7.cjs} +0 -0
@@ -0,0 +1,4789 @@
1
+ import { r as init__polyfill_buffer_patch } from "./_polyfill-buffer-patch-JcrfT1gU.js";
2
+ import "./dist-B00GN41m.js";
3
+ import { $t as blake2b, At as isPlutusScript, Bt as AssetName, C as Value, Ct as PoolId, Dt as PlutusLanguageVersion, E as CostModel, I as RewardAddress, L as CertificateType, M as isDRepCredential, Nt as CborReader, Ot as ScriptType, P as RewardAccount, Pt as CborReaderState, T as Costmdls, Vt as PolicyId, Zt as Hash28ByteBase16, b as RedeemerPurpose, bt as Slot, c as EnterpriseAddress, dt as AuxiliaryData, et as ConstrPlutusData, ft as Script, it as isConstrPlutusData, kt as isNativeScript, n as AddressType, nt as PlutusData, ot as isPlutusBoundedBytes, p as Transaction, pt as PlutusV3Script, r as CredentialType, st as isPlutusList, t as Address, tn as HexBlob, tt as PlutusList, zt as AssetId } from "./Address-_c_-9hLC.js";
4
+ import { t as Serializable } from "./serializable-F7OtYl8d.js";
5
+ import { _ as encodeCustodyGrant, b as custodyRecipientToAddress, c as isCustodyLovelaceAsset, d as parseCustodyAccountTokenName, g as encodeCustodyAccountState, i as findCustodyGrantViolations, l as CUSTODY_SLOT_LIMIT, o as isCustodyGrantCurrent, s as isCustodyGrantExpired, t as minAdaRequired, u as custodyGrantAssetId, y as custodyRecipientFromAddress } from "./fees-6C2uEWFK.js";
6
+ import "./_web-crypto-random-bytes-BvzwTYPG.js";
7
+ import { t as SodiumBip32Ed25519 } from "./SodiumBip32Ed25519-DCikAMsl.js";
8
+ import "./safe-buffer-CqGpSFCQ.js";
9
+ import "./browser-D5jRKv_L.js";
10
+ import { t as jsonToMetadatum } from "./metadatum-BVocMYy_.js";
11
+ import { r as NetworkMagics, t as ChainIds } from "./ChainId-CeoG8Sov.js";
12
+ import { t as PaymentAddress } from "./PaymentAddress-Cml3EA1n.js";
13
+ import { i as KeyPurpose } from "./types-By-XeXWg.js";
14
+ import { n as createTxInKeyPathMap, t as InMemoryKeyAgent } from "./InMemoryKeyAgent-D8T3vtXy.js";
15
+ import "./src-CgINPlSx.js";
16
+ import { t as SecretBox } from "./secret-box-CSmRp6Tt.js";
17
+ import { _ as CardanoRewardAccount, d as utxoKey, g as CardanoPaymentAddress, l as outpointKey$1, n as filterSpendableUtxos, s as isCardanoAddressOfSupportedNetwork, t as createInputResolver } from "./util-DHxtzUzY.js";
18
+ import { t as ByteArray } from "./bytes.vo-CziaOsba.js";
19
+ import { t as BigNumber } from "./bignum.vo-Co6tMH2q.js";
20
+ import { n as Ok$1, t as Err$1 } from "./asyncoption-DZQGfznq.js";
21
+ import { t as firstStateOfStatus } from "./first-state-of-status-BscbKj_t.js";
22
+ import { n as retryBackoff, t as PROVIDER_REQUEST_RETRY_CONFIG } from "./retry-config-Bu5Yn5ua.js";
23
+ import { t as ActivityType } from "./activity-type-C1geyPsH.js";
24
+ import { t as CardanoNetworkId } from "./cardano-network-id.vo-CeLJqmb_.js";
25
+ import { r as LOVELACE_TOKEN_ID } from "./const-DbuUHSzR.js";
26
+ import { i as identityOfValidatedCreator, n as chainIdFromNetworkMagic, o as isCardanoCustodyAccount, r as deriveCustodyAccountIdentity, t as verifyCardanoCustodyAccountIdentity } from "./identity-DxPhxkZs.js";
27
+ import { a as CURRENT_CUSTODY_CONTRACT_VERSION, c as custodyReferenceScriptUtxos, d as fail$1, f as ok, l as findCustodyContractVersion, r as isHash28Hex, t as bytesToHex, u as findCustodyLogic } from "./hex-CGYJPK_Z.js";
28
+ import { a as serializeCustodyRequestedGrantScope, c as encodeCustodyLogicRedeemer, g as findCustodyScopeViolations, h as custodyGrantAfterSpend, i as parseCustodyRequestedGrantScope, l as encodeCustodyMintRedeemer, n as deriveCustodyDeviceKey, o as inspectCustodyTransaction, p as createCustodyAccountRecord, s as encodeCustodyAccountRedeemer, u as encodeCustodyStakeRedeemer } from "./device-key-BToCoS4D.js";
29
+ import { i as classifyCardanoCustodyAccount, n as isCardanoCustodyGrantExhausted, o as custodyGrantLiveness, r as CARDANO_CUSTODY_DEFAULT_GRANT_FEE_BOUND } from "./grant-status-CDmZpx32.js";
30
+ import { a as referenceScriptSize, c as getUniqueSignerKeyHashes, i as referenceScriptFee, n as TransactionBuilder, o as EvaluatedFeeShortfallError, r as LargeFirstCoinSelector, s as InputSelectionError, t as InsufficientCollateralError } from "./TransactionBuilder-D2ErwVWv.js";
31
+ import { t as applyVkeyWitnesses } from "./apply-vkey-witnesses-Byb40-yJ.js";
32
+ import { t as isRetriableError } from "./is-retriable-error-CmFR20pe.js";
33
+ import { t as refuseExtensionAccount } from "./account-guards-qnFXuXSE.js";
34
+ import { catchError, combineLatest, concat, defer, exhaustMap, filter, first, firstValueFrom, forkJoin, from, groupBy, map, merge, mergeMap, of, switchMap, take, throwError, timeout, timer } from "rxjs";
35
+ //#region ../../packages/contract/cardano-context/src/value-objects/cardano-testnet-id.vo.ts
36
+ const CARDANO_CHAIN_ID_BY_TESTNET = {
37
+ preprod: ChainIds.Preprod,
38
+ preview: ChainIds.Preview,
39
+ sanchonet: ChainIds.Sanchonet
40
+ };
41
+ const PREPROD_MAGIC = Number(ChainIds.Preprod.networkMagic);
42
+ const PREVIEW_MAGIC = Number(ChainIds.Preview.networkMagic);
43
+ const SANCHONET_MAGIC = Number(ChainIds.Sanchonet.networkMagic);
44
+ const getCardanoTestnetIdByNetworkMagic = (networkMagic) => {
45
+ const magic = Number(networkMagic);
46
+ if (magic === PREPROD_MAGIC) return "preprod";
47
+ if (magic === PREVIEW_MAGIC) return "preview";
48
+ if (magic === SANCHONET_MAGIC) return "sanchonet";
49
+ };
50
+ const getCardanoChainIdByTestnetId = (id) => CARDANO_CHAIN_ID_BY_TESTNET[id];
51
+ //#endregion
52
+ //#region ../../packages/lib/cardano-custody/src/analysis/sponsor-rules.ts
53
+ /** The service's limit on the size of a transaction it inspects, in bytes. */
54
+ const MAX_TRANSACTION_BYTES = 16 * 1024;
55
+ /** The service's default limit on the fee a leased fee UTxO pays, in lovelace. */
56
+ const MAX_FEE_LOVELACE = 2000000n;
57
+ /**
58
+ * The most devices a well formed state lists, which bounds the stake scripts
59
+ * a creation has the service derive.
60
+ */
61
+ const MAX_DEVICES = 8;
62
+ const outpointKey = ({ txId, index }) => `${txId.toLowerCase()}#${index}`;
63
+ const toTxIn = ({ txId, index }) => ({
64
+ txId,
65
+ index
66
+ });
67
+ const paymentCredentialOf = (address) => {
68
+ const parsed = Address.fromString(address);
69
+ return (parsed?.asBase() ?? parsed?.asEnterprise() ?? parsed?.asPointer())?.getPaymentCredential();
70
+ };
71
+ const stakeCredentialOf = (address) => {
72
+ const parsed = Address.fromString(address);
73
+ return parsed?.asBase()?.getStakeCredential() ?? parsed?.asReward()?.getPaymentCredential();
74
+ };
75
+ const keyHashOf = (credential) => credential?.type === CredentialType.KeyHash ? credential.hash : void 0;
76
+ const scriptHashOf$1 = (credential) => credential?.type === CredentialType.ScriptHash ? credential.hash : void 0;
77
+ const sponsorKeysOf = (address) => ({
78
+ paymentKeyHash: keyHashOf(paymentCredentialOf(address)),
79
+ stakeKeyHash: keyHashOf(stakeCredentialOf(address))
80
+ });
81
+ const namesSponsorKey = (credential, { paymentKeyHash, stakeKeyHash }) => credential.type === CredentialType.KeyHash && (credential.hash === paymentKeyHash || credential.hash === stakeKeyHash);
82
+ const isAccountOutput = (output, { contractVersion }) => scriptHashOf$1(paymentCredentialOf(output.address)) === contractVersion.accountValidatorHash;
83
+ const policyIdOf = (assetId) => AssetId.getPolicyId(assetId);
84
+ /** The 28-byte hash a datum field holds, or undefined for a field of any other shape. */
85
+ const hashFieldOf = (field) => isPlutusBoundedBytes(field) && field.length === 28 ? Hash28ByteBase16(bytesToHex(field)) : void 0;
86
+ /** The fields of an inline datum that is constructor 0, or undefined for any other datum. */
87
+ const inlineFieldsOf = (output) => isConstrPlutusData(output.datum) && output.datum.constructor === 0n ? output.datum.fields.items : void 0;
88
+ /** The logic a control datum names: a 28-byte hash in its first field. */
89
+ const logicHashOf = (output) => hashFieldOf(inlineFieldsOf(output)?.[0]);
90
+ /** The devices a control datum lists: a list of 28-byte hashes in its second field. */
91
+ const devicesOf = (output) => {
92
+ const list = inlineFieldsOf(output)?.[1];
93
+ if (!isPlutusList(list)) return void 0;
94
+ const devices = list.items.map(hashFieldOf);
95
+ return devices.every((device) => device !== void 0) ? devices : void 0;
96
+ };
97
+ /**
98
+ * The account token an input holds, when it is a control or a grant UTxO:
99
+ * the output sits at an account address staked to a script and holds a
100
+ * token under the account policy named after that very script, alone or
101
+ * followed by a slot. The validator mints a token only into such an
102
+ * output, so an output shaped otherwise is never read as an account's. The
103
+ * control token wins when an output holds both kinds.
104
+ */
105
+ const accountTokenOf = ({ input, output }, context) => {
106
+ if (output === void 0 || !isAccountOutput(output, context)) return;
107
+ const account = scriptHashOf$1(stakeCredentialOf(output.address));
108
+ if (account === void 0) return void 0;
109
+ const kinds = [...output.value.assets ?? /* @__PURE__ */ new Map()].flatMap(([assetId, quantity]) => {
110
+ if (policyIdOf(assetId) !== context.contractVersion.accountValidatorHash || quantity <= 0n) return [];
111
+ const name = parseCustodyAccountTokenName(AssetId.getAssetName(assetId));
112
+ return name?.stakeScriptHash === account ? [name.kind] : [];
113
+ });
114
+ if (kinds.length === 0) return void 0;
115
+ return {
116
+ input,
117
+ output,
118
+ account,
119
+ kind: kinds.includes("state-nft") ? "control" : "grant"
120
+ };
121
+ };
122
+ /**
123
+ * The account a control output belongs to: an output at an account address
124
+ * staked to a script, holding exactly one state NFT named after that script.
125
+ * The validator pins the state NFT to such an output, so its datum names
126
+ * the logic the account runs once the transaction lands.
127
+ */
128
+ const controlOutputAccount = (output, context) => {
129
+ if (!isAccountOutput(output, context)) return void 0;
130
+ const account = scriptHashOf$1(stakeCredentialOf(output.address));
131
+ if (account === void 0) return void 0;
132
+ const stateNft = AssetId.fromParts(PolicyId(context.contractVersion.accountValidatorHash), AssetName(account));
133
+ return output.value.assets?.get(stateNft) === 1n ? account : void 0;
134
+ };
135
+ /**
136
+ * Every logic the transaction names: the one each control UTxO it spends or
137
+ * references names, then the one each control output it writes names. A
138
+ * creation names its initial logic in its control output, and an upgrade
139
+ * names the logic it leaves in the control UTxO and the one it arrives at in
140
+ * the control output.
141
+ */
142
+ const namedLogicsOf = ({ spent, referencedControls }, body, context) => [...[...spent.filter(({ kind }) => kind === "control"), ...referencedControls].map(({ input, output, account }) => ({
143
+ logic: logicHashOf(output),
144
+ control: {
145
+ kind: "control-utxo",
146
+ input
147
+ },
148
+ stakeScriptHash: account
149
+ })), ...body.outputs.flatMap((output, outputIndex) => {
150
+ const account = controlOutputAccount(output, context);
151
+ return account === void 0 ? [] : [{
152
+ logic: logicHashOf(output),
153
+ control: {
154
+ kind: "control-output",
155
+ outputIndex
156
+ },
157
+ stakeScriptHash: account
158
+ }];
159
+ })];
160
+ /**
161
+ * A sponsor UTxO is the leased fee UTxO, the shared collateral, or any
162
+ * output locked by the sponsor payment key: the sponsor's signature
163
+ * authorises every one of them.
164
+ */
165
+ const isSponsorInput = ({ input, output }, context, sponsorKeys) => {
166
+ const key = outpointKey(input);
167
+ return context.mode.kind === "fee" && key === outpointKey(context.mode.feeUtxo[0]) || key === outpointKey(context.collateralUtxo[0]) || output !== void 0 && sponsorKeys.paymentKeyHash !== void 0 && keyHashOf(paymentCredentialOf(output.address)) === sponsorKeys.paymentKeyHash;
168
+ };
169
+ /** Every credential a certificate names, any of which may have to sign. */
170
+ const certificateCredentials = (certificate) => {
171
+ const named = [];
172
+ if ("stakeCredential" in certificate) named.push(certificate.stakeCredential);
173
+ if ("dRep" in certificate && isDRepCredential(certificate.dRep)) named.push(certificate.dRep);
174
+ if ("dRepCredential" in certificate) named.push(certificate.dRepCredential);
175
+ if ("coldCredential" in certificate) named.push(certificate.coldCredential);
176
+ if ("hotCredential" in certificate) named.push(certificate.hotCredential);
177
+ const poolIds = [];
178
+ if ("poolId" in certificate) poolIds.push(certificate.poolId);
179
+ if ("poolParameters" in certificate) {
180
+ const { id, rewardAccount, owners } = certificate.poolParameters;
181
+ poolIds.push(id);
182
+ named.push(stakeCredentialOf(rewardAccount), ...owners.map((owner) => stakeCredentialOf(owner)));
183
+ }
184
+ for (const poolId of poolIds) named.push({
185
+ type: CredentialType.KeyHash,
186
+ hash: PoolId.toKeyHash(poolId)
187
+ });
188
+ return named.filter((credential) => credential !== void 0);
189
+ };
190
+ const LOVELACE_KEY = "";
191
+ /** Lovelace and every asset a list of values holds, keyed by asset id ('' for lovelace). */
192
+ const balanceOf = (values) => {
193
+ const balance = /* @__PURE__ */ new Map();
194
+ const add = (key, quantity) => balance.set(key, (balance.get(key) ?? 0n) + quantity);
195
+ for (const value of values) {
196
+ add(LOVELACE_KEY, value.coins);
197
+ for (const [assetId, quantity] of value.assets ?? []) add(assetId, quantity);
198
+ }
199
+ return balance;
200
+ };
201
+ /** Reads each entry of the CBOR map the reader is at, definite or not. */
202
+ const readMapEntries = (reader, readEntry) => {
203
+ const count = reader.readStartMap();
204
+ for (let read = 0; count === null ? reader.peekState() !== CborReaderState.EndMap : read < count; read++) readEntry();
205
+ };
206
+ /** The fields of an encoded body or witness set by key, each as the bytes it is stated in. */
207
+ const fieldsOf = (encoded) => {
208
+ const reader = new CborReader(bytesToHex(encoded));
209
+ const fields = /* @__PURE__ */ new Map();
210
+ readMapEntries(reader, () => fields.set(reader.readUInt(), bytesToHex(reader.readEncodedValue())));
211
+ return fields;
212
+ };
213
+ const BODY_WITHDRAWALS = 5n;
214
+ const WITNESS_DATUMS = 4n;
215
+ const WITNESS_REDEEMERS = 5n;
216
+ /**
217
+ * The body withdrawals and the witness script data as the transaction's
218
+ * bytes state them, which is what the service reads: the decoded body keeps
219
+ * one withdrawal per reward account, and the script data hash is taken over
220
+ * the bytes the witness set carries.
221
+ */
222
+ const statedOf = (transaction) => {
223
+ const reader = new CborReader(HexBlob(transaction.toCbor()));
224
+ reader.readStartArray();
225
+ const body = fieldsOf(reader.readEncodedValue());
226
+ const witnessSet = fieldsOf(reader.readEncodedValue());
227
+ const withdrawals = [];
228
+ const encodedWithdrawals = body.get(BODY_WITHDRAWALS);
229
+ if (encodedWithdrawals !== void 0) {
230
+ const withdrawalReader = new CborReader(encodedWithdrawals);
231
+ readMapEntries(withdrawalReader, () => withdrawals.push({
232
+ stakeAddress: RewardAccount(Address.fromBytes(bytesToHex(withdrawalReader.readByteString())).toBech32()),
233
+ quantity: withdrawalReader.readUInt()
234
+ }));
235
+ }
236
+ return {
237
+ withdrawals,
238
+ witnessScriptData: {
239
+ redeemers: witnessSet.get(WITNESS_REDEEMERS),
240
+ datums: witnessSet.get(WITNESS_DATUMS)
241
+ }
242
+ };
243
+ };
244
+ /**
245
+ * Reads an account creation, or the reason the transaction is none:
246
+ * exactly one token minted under the account policy, named with the 28
247
+ * bytes of a stake script hash, exactly one registration of a script stake
248
+ * credential with its deposit, the token named after that credential and
249
+ * held by exactly one output at an account address staked to it, and that
250
+ * credential the version's stake script of a device the output's datum
251
+ * lists. A datum listing more devices than a well formed state is refused
252
+ * before any script is derived.
253
+ */
254
+ const readCreation = (body, context) => {
255
+ const refuse = (reason) => ({
256
+ rule: "account_transaction",
257
+ reason
258
+ });
259
+ const { accountValidatorHash } = context.contractVersion;
260
+ const minted = [...body.mint ?? /* @__PURE__ */ new Map()].filter(([assetId]) => policyIdOf(assetId) === accountValidatorHash);
261
+ if (minted.length === 0) return refuse("not-account-transaction");
262
+ const [[assetId, quantity]] = minted;
263
+ if (minted.length !== 1 || quantity !== 1n) return refuse("mint-count");
264
+ if (parseCustodyAccountTokenName(AssetId.getAssetName(assetId))?.kind !== "state-nft") return refuse("state-nft-name-length");
265
+ const registrations = (body.certificates ?? []).filter((certificate) => certificate.__typename === CertificateType.Registration && certificate.stakeCredential.type === CredentialType.ScriptHash);
266
+ if (registrations.length !== 1) return refuse("registration-count");
267
+ const [registration] = registrations;
268
+ const stakeScriptHash = registration.stakeCredential.hash;
269
+ if (AssetId.getAssetName(assetId) !== stakeScriptHash) return refuse("state-nft-name");
270
+ const holders = body.outputs.filter((output) => output.value.assets?.get(assetId) === 1n);
271
+ if (holders.length !== 1 || !isAccountOutput(holders[0], context)) return refuse("control-output-placement");
272
+ const [controlOutput] = holders;
273
+ if (scriptHashOf$1(stakeCredentialOf(controlOutput.address)) !== stakeScriptHash) return refuse("control-output-stake");
274
+ const devices = devicesOf(controlOutput);
275
+ if (devices === void 0) return refuse("control-output-devices");
276
+ if (devices.length > MAX_DEVICES) return {
277
+ rule: "account_transaction",
278
+ reason: "device-count",
279
+ count: devices.length,
280
+ limit: MAX_DEVICES
281
+ };
282
+ const chainId = chainIdFromNetworkMagic(context.networkMagic);
283
+ if (!devices.some((device) => identityOfValidatedCreator({
284
+ contractVersion: context.contractVersion,
285
+ creatorKeyHash: device,
286
+ chainId
287
+ }).stakeScriptHash === stakeScriptHash)) return {
288
+ rule: "account_transaction",
289
+ reason: "underived-stake-credential",
290
+ stakeScriptHash
291
+ };
292
+ return {
293
+ deposit: registration.deposit,
294
+ controlLovelace: controlOutput.value.coins,
295
+ stakeScriptHash
296
+ };
297
+ };
298
+ /** The accounts whose control UTxO the transaction spends or references. */
299
+ const controlledAccountsOf = ({ spent, referencedControls }) => new Set([...spent, ...referencedControls].filter(({ kind }) => kind === "control").map(({ account }) => account));
300
+ /**
301
+ * Reads what the transaction does to the account contract. A referenced
302
+ * control UTxO serves only an account the transaction operates, so one of
303
+ * any other account is refused first, even beside a creation. A
304
+ * transaction spending no account token is a creation, or no account
305
+ * transaction at all. One spending a token is an operation, and every
306
+ * account it operates needs its control UTxO spent or referenced, as the
307
+ * contract needs on the owner, the sweep and the agent path alike.
308
+ */
309
+ const readAccountTransaction = (body, { spent, referencedControls }, context) => {
310
+ const refuse = (reason, { input, account }) => ({
311
+ kind: "refused",
312
+ violation: {
313
+ rule: "account_transaction",
314
+ reason,
315
+ input,
316
+ stakeScriptHash: account
317
+ }
318
+ });
319
+ const operated = new Set(spent.map(({ account }) => account));
320
+ const foreignControl = referencedControls.find(({ account }) => !operated.has(account));
321
+ if (foreignControl) return refuse("foreign-referenced-control", foreignControl);
322
+ if (spent.length === 0) {
323
+ const creation = readCreation(body, context);
324
+ return "rule" in creation ? {
325
+ kind: "refused",
326
+ violation: creation
327
+ } : {
328
+ kind: "creation",
329
+ creation
330
+ };
331
+ }
332
+ const controlled = controlledAccountsOf({
333
+ spent,
334
+ referencedControls
335
+ });
336
+ const orphan = spent.find(({ account }) => !controlled.has(account));
337
+ return orphan ? refuse("orphan-grant", orphan) : { kind: "operation" };
338
+ };
339
+ /**
340
+ * The stake scripts of the accounts the transaction belongs to: those of
341
+ * the control UTxOs it spends or references, which name them, and the one
342
+ * it registers at creation. A grant UTxO alone names none: an agent spend
343
+ * references the control UTxO of its account.
344
+ */
345
+ const stakeScriptHashesOf = (tokens, reading) => {
346
+ const hashes = controlledAccountsOf(tokens);
347
+ if (reading.kind === "creation") hashes.add(reading.creation.stakeScriptHash);
348
+ return hashes;
349
+ };
350
+ const analyse = (transaction, resolvedInputs, context) => {
351
+ const body = transaction.body().toCore();
352
+ const sponsorKeys = sponsorKeysOf(context.sponsorAddress);
353
+ const resolutions = new Map(resolvedInputs.map(([input, output]) => [outpointKey(input), output]));
354
+ const resolve = (input) => ({
355
+ input: toTxIn(input),
356
+ output: resolutions.get(outpointKey(input))
357
+ });
358
+ const inputs = body.inputs.map(resolve);
359
+ const referenceInputs = (body.referenceInputs ?? []).map(resolve);
360
+ const sponsorInputs = inputs.filter((input) => isSponsorInput(input, context, sponsorKeys));
361
+ const tokens = {
362
+ spent: inputs.filter((input) => !sponsorInputs.includes(input)).flatMap((input) => accountTokenOf(input, context) ?? []),
363
+ referencedControls: referenceInputs.flatMap((input) => accountTokenOf(input, context) ?? []).filter(({ kind }) => kind === "control")
364
+ };
365
+ const reading = readAccountTransaction(body, tokens, context);
366
+ const namedLogics = namedLogicsOf(tokens, body, context);
367
+ const sponsorOutputs = body.outputs.flatMap((output, outputIndex) => output.address === context.sponsorAddress ? [{
368
+ output,
369
+ outputIndex
370
+ }] : []);
371
+ return {
372
+ transaction,
373
+ body,
374
+ witness: transaction.witnessSet().toCore(),
375
+ inputs,
376
+ referenceInputs,
377
+ sponsorInputs,
378
+ sponsorOutputs,
379
+ foreignOutputs: body.outputs.filter((output) => output.address !== context.sponsorAddress && !isAccountOutput(output, context)),
380
+ ...statedOf(transaction),
381
+ reading,
382
+ stakeScriptHashes: stakeScriptHashesOf(tokens, reading),
383
+ namedLogics,
384
+ logicHashes: new Set(namedLogics.flatMap(({ logic }) => logic ?? [])),
385
+ sponsorKeys
386
+ };
387
+ };
388
+ const wellFormed = ({ transaction, body }) => {
389
+ const size = transaction.toCbor().length / 2;
390
+ if (size > MAX_TRANSACTION_BYTES) return {
391
+ rule: "well_formed",
392
+ reason: "too-large",
393
+ size,
394
+ limit: MAX_TRANSACTION_BYTES
395
+ };
396
+ if ((body.proposalProcedures ?? []).length > 0) return {
397
+ rule: "well_formed",
398
+ reason: "proposal-procedures"
399
+ };
400
+ return body.donation === void 0 ? void 0 : {
401
+ rule: "well_formed",
402
+ reason: "treasury-donation"
403
+ };
404
+ };
405
+ /**
406
+ * Fee mode spends the leased fee UTxO and no other sponsor UTxO; collateral
407
+ * mode spends none. An input whose payment credential cannot be read
408
+ * cannot be told from the sponsor's.
409
+ */
410
+ const sponsorInputsRule = ({ inputs, sponsorInputs }, { mode }) => {
411
+ const rule = mode.kind === "fee" ? "uses_leased_fee_input" : "no_sponsor_inputs";
412
+ const feeKey = mode.kind === "fee" ? outpointKey(mode.feeUtxo[0]) : void 0;
413
+ if (mode.kind === "fee" && !inputs.some(({ input }) => outpointKey(input) === feeKey)) return {
414
+ rule: "uses_leased_fee_input",
415
+ reason: "fee-input-missing",
416
+ input: toTxIn(mode.feeUtxo[0])
417
+ };
418
+ const unreadable = inputs.find(({ output }) => output !== void 0 && paymentCredentialOf(output.address) === void 0);
419
+ if (unreadable) return {
420
+ rule,
421
+ reason: "unreadable-input",
422
+ input: unreadable.input
423
+ };
424
+ const extra = sponsorInputs.find(({ input }) => outpointKey(input) !== feeKey);
425
+ return extra ? {
426
+ rule,
427
+ reason: "sponsor-input",
428
+ input: extra.input
429
+ } : void 0;
430
+ };
431
+ /**
432
+ * The transaction is not flagged as failing phase two, and its collateral
433
+ * is exactly the shared collateral UTxO, returned plainly to the sponsor,
434
+ * with the total collateral set within what it holds. The ledger also
435
+ * takes collateral of at least the collateral percentage of the fee, so a
436
+ * fee above what the shared collateral covers is refused: the service
437
+ * would refuse the total it needs, and the ledger any less.
438
+ */
439
+ const sharedCollateral = ({ transaction, body }, context) => {
440
+ const refuse = (reason) => ({
441
+ rule: "uses_shared_collateral",
442
+ reason
443
+ });
444
+ if (!transaction.isValid()) return refuse("phase-two-invalid");
445
+ const collaterals = body.collaterals ?? [];
446
+ if (collaterals.length !== 1 || outpointKey(collaterals[0]) !== outpointKey(context.collateralUtxo[0])) return refuse("collateral-inputs");
447
+ const { collateralReturn, totalCollateral } = body;
448
+ if (!collateralReturn) return refuse("no-collateral-return");
449
+ if (collateralReturn.address !== context.sponsorAddress) return refuse("collateral-return-address");
450
+ if (collateralReturn.datum !== void 0 || collateralReturn.datumHash !== void 0) return refuse("collateral-return-datum");
451
+ if (collateralReturn.scriptReference !== void 0) return refuse("collateral-return-reference-script");
452
+ if (totalCollateral === void 0) return refuse("no-total-collateral");
453
+ const available = context.collateralUtxo[1].value.coins;
454
+ if (totalCollateral > available) return {
455
+ rule: "uses_shared_collateral",
456
+ reason: "total-collateral-exceeds",
457
+ totalCollateral,
458
+ available
459
+ };
460
+ const percentage = BigInt(context.collateralPercentage);
461
+ return body.fee * percentage > available * 100n ? {
462
+ rule: "uses_shared_collateral",
463
+ reason: "fee-exceeds-collateral",
464
+ fee: body.fee,
465
+ limit: available * 100n / percentage
466
+ } : void 0;
467
+ };
468
+ const boundedValidity = ({ body }, context) => {
469
+ const upperBound = body.validityInterval?.invalidHereafter;
470
+ if (upperBound === void 0) return {
471
+ rule: "bounded_validity",
472
+ reason: "no-upper-bound"
473
+ };
474
+ if (upperBound > context.latestValiditySlot) return {
475
+ rule: "bounded_validity",
476
+ reason: "upper-bound-too-late",
477
+ upperBound,
478
+ latest: context.latestValiditySlot
479
+ };
480
+ return upperBound <= context.currentSlot ? {
481
+ rule: "bounded_validity",
482
+ reason: "upper-bound-not-future",
483
+ upperBound,
484
+ current: context.currentSlot
485
+ } : void 0;
486
+ };
487
+ const accountTransaction = ({ reading }) => reading.kind === "refused" ? reading.violation : void 0;
488
+ /**
489
+ * Every logic the transaction names is one the service knows. The proxy
490
+ * runs whatever registered script a control datum names, so the service
491
+ * pays for and lends collateral to no account under rules it has not read,
492
+ * and refuses a control datum that names no script hash at all.
493
+ */
494
+ const knownLogic = ({ namedLogics }, { contractVersion }) => {
495
+ const known = new Set(contractVersion.logics.map(({ hash }) => hash));
496
+ for (const { logic, control, stakeScriptHash } of namedLogics) {
497
+ if (logic === void 0) return {
498
+ rule: "known_logic",
499
+ reason: "logic-missing",
500
+ control,
501
+ stakeScriptHash
502
+ };
503
+ if (!known.has(logic)) return {
504
+ rule: "known_logic",
505
+ reason: "unknown-logic",
506
+ control,
507
+ logic,
508
+ stakeScriptHash
509
+ };
510
+ }
511
+ };
512
+ /**
513
+ * The leased fee UTxO pays for an account creation and nothing else: an
514
+ * operation on an existing account pays its own fee and takes the
515
+ * collateral route, whatever it draws. The fee stays within the service's
516
+ * limit, and the fee UTxO is drawn down by exactly the fee, the
517
+ * registration deposit and the control output, within the sponsored limit,
518
+ * and returns as one plain change output the pool can spend.
519
+ */
520
+ const sponsorOutflowBounded = ({ body, sponsorOutputs, reading }, mode) => {
521
+ const rule = "sponsor_outflow_bounded";
522
+ if (reading.kind !== "creation") return {
523
+ rule,
524
+ reason: "not-creation"
525
+ };
526
+ if (body.fee > MAX_FEE_LOVELACE) return {
527
+ rule,
528
+ reason: "fee-exceeds-limit",
529
+ fee: body.fee,
530
+ limit: MAX_FEE_LOVELACE
531
+ };
532
+ for (const { output, outputIndex } of sponsorOutputs) {
533
+ if (output.datum !== void 0 || output.datumHash !== void 0) return {
534
+ rule,
535
+ reason: "sponsor-output-datum",
536
+ outputIndex
537
+ };
538
+ if (output.scriptReference !== void 0) return {
539
+ rule,
540
+ reason: "sponsor-output-reference-script",
541
+ outputIndex
542
+ };
543
+ }
544
+ if (sponsorOutputs.length !== 1) return {
545
+ rule,
546
+ reason: "sponsor-output-count",
547
+ count: sponsorOutputs.length
548
+ };
549
+ const sponsored = mode.feeUtxo[1].value.coins - sponsorOutputs[0].output.value.coins;
550
+ const { deposit, controlLovelace } = reading.creation;
551
+ const expected = body.fee + deposit + controlLovelace;
552
+ if (sponsored !== expected) return {
553
+ rule,
554
+ reason: "draw-mismatch",
555
+ sponsored,
556
+ expected
557
+ };
558
+ return sponsored > mode.maxSponsoredLovelace ? {
559
+ rule,
560
+ reason: "draw-exceeds-limit",
561
+ sponsored,
562
+ limit: mode.maxSponsoredLovelace
563
+ } : void 0;
564
+ };
565
+ /** In collateral mode nothing pays the sponsor key and nothing draws from its reward account. */
566
+ const sponsorOutflowZero = ({ body, withdrawals, sponsorKeys }) => {
567
+ const outputIndex = body.outputs.findIndex((output) => sponsorKeys.paymentKeyHash !== void 0 && keyHashOf(paymentCredentialOf(output.address)) === sponsorKeys.paymentKeyHash);
568
+ if (outputIndex >= 0) return {
569
+ rule: "sponsor_outflow_zero",
570
+ reason: "pays-sponsor",
571
+ outputIndex
572
+ };
573
+ const withdrawal = withdrawals.find(({ stakeAddress }) => sponsorKeys.stakeKeyHash !== void 0 && keyHashOf(stakeCredentialOf(stakeAddress)) === sponsorKeys.stakeKeyHash);
574
+ return withdrawal ? {
575
+ rule: "sponsor_outflow_zero",
576
+ reason: "sponsor-withdrawal",
577
+ rewardAccount: withdrawal.stakeAddress
578
+ } : void 0;
579
+ };
580
+ const sponsorOutflow = (analysis, { mode }) => mode.kind === "fee" ? sponsorOutflowBounded(analysis, mode) : sponsorOutflowZero(analysis);
581
+ /** Outputs away from the sponsor and the account are covered by what non-sponsor inputs and withdrawals bring. */
582
+ const noSponsorValueElsewhere = ({ withdrawals, inputs, sponsorInputs, foreignOutputs, sponsorKeys }) => {
583
+ const supply = balanceOf(inputs.filter((input) => !sponsorInputs.includes(input)).flatMap(({ output }) => output ? [output.value] : []));
584
+ for (const { stakeAddress, quantity } of withdrawals) if (!(sponsorKeys.stakeKeyHash !== void 0 && keyHashOf(stakeCredentialOf(stakeAddress)) === sponsorKeys.stakeKeyHash)) supply.set(LOVELACE_KEY, (supply.get(LOVELACE_KEY) ?? 0n) + quantity);
585
+ const demand = balanceOf(foreignOutputs.map((output) => output.value));
586
+ for (const [key, demanded] of demand) {
587
+ const supplied = supply.get(key) ?? 0n;
588
+ if (demanded > supplied) return {
589
+ rule: "no_sponsor_value_elsewhere",
590
+ reason: "uncovered-outflow",
591
+ demanded,
592
+ supplied,
593
+ ...key === LOVELACE_KEY ? {} : { assetId: key }
594
+ };
595
+ }
596
+ };
597
+ /**
598
+ * Lace's own rule on reference inputs: one carrying a script must be a
599
+ * version's parked script UTxO on the network, carrying exactly the script
600
+ * parked there, and a parked script's UTxO must carry its script.
601
+ */
602
+ const foreignReferenceScriptOf = (referenceInputs, { contractVersion, networkMagic }) => {
603
+ const parked = new Map((contractVersion.referenceScripts[networkMagic] ?? []).map(({ txIn, scriptHash }) => [outpointKey(txIn), scriptHash]));
604
+ for (const { input, output } of referenceInputs) {
605
+ const script = output?.scriptReference;
606
+ const scriptHash = script && Script.fromCore(script).hash();
607
+ const expected = parked.get(outpointKey(input));
608
+ if (expected === void 0 ? scriptHash !== void 0 : output !== void 0 && scriptHash !== expected) return {
609
+ rule: "no_foreign_scripts",
610
+ reason: "foreign-reference-script",
611
+ input,
612
+ ...scriptHash && { scriptHash }
613
+ };
614
+ }
615
+ };
616
+ /**
617
+ * Every script the transaction runs or attaches belongs to the account.
618
+ * Inputs and mints use the account proxy alone. A certificate or a vote may
619
+ * name the proxy or the stake script of an account the transaction belongs
620
+ * to. A logic its control datums name may also run through a withdrawal,
621
+ * of zero only, and be attached. No reward account is drawn from twice.
622
+ */
623
+ const noForeignScripts = ({ body, witness, inputs, referenceInputs, withdrawals, stakeScriptHashes, logicHashes }, context) => {
624
+ const rule = "no_foreign_scripts";
625
+ const proxy = context.contractVersion.accountValidatorHash;
626
+ const accountScripts = new Set([proxy, ...stakeScriptHashes]);
627
+ const runnable = new Set([...accountScripts, ...logicHashes]);
628
+ for (const { input, output } of inputs) {
629
+ const scriptHash = output && scriptHashOf$1(paymentCredentialOf(output.address));
630
+ if (scriptHash !== void 0 && scriptHash !== proxy) return {
631
+ rule,
632
+ reason: "foreign-script-input",
633
+ input,
634
+ scriptHash
635
+ };
636
+ }
637
+ for (const assetId of body.mint?.keys() ?? []) {
638
+ const policyId = policyIdOf(assetId);
639
+ if (policyId !== proxy) return {
640
+ rule,
641
+ reason: "foreign-mint",
642
+ policyId
643
+ };
644
+ }
645
+ for (const [certificateIndex, certificate] of (body.certificates ?? []).entries()) {
646
+ const scriptHash = certificateCredentials(certificate).map(scriptHashOf$1).find((hash) => hash !== void 0 && !accountScripts.has(hash));
647
+ if (scriptHash) return {
648
+ rule,
649
+ reason: "foreign-certificate",
650
+ certificateIndex,
651
+ scriptHash
652
+ };
653
+ }
654
+ const drawn = /* @__PURE__ */ new Set();
655
+ for (const { stakeAddress: rewardAccount, quantity } of withdrawals) {
656
+ if (drawn.has(rewardAccount)) return {
657
+ rule,
658
+ reason: "duplicate-withdrawal",
659
+ rewardAccount
660
+ };
661
+ drawn.add(rewardAccount);
662
+ const scriptHash = scriptHashOf$1(stakeCredentialOf(rewardAccount));
663
+ if (scriptHash !== void 0 && !runnable.has(scriptHash)) return {
664
+ rule,
665
+ reason: "foreign-withdrawal",
666
+ rewardAccount
667
+ };
668
+ if (scriptHash !== void 0 && logicHashes.has(scriptHash) && quantity !== 0n) return {
669
+ rule,
670
+ reason: "logic-draw",
671
+ rewardAccount,
672
+ logic: scriptHash,
673
+ quantity
674
+ };
675
+ }
676
+ for (const { voter } of body.votingProcedures ?? []) {
677
+ const scriptHash = scriptHashOf$1(voter.credential);
678
+ if (scriptHash !== void 0 && !accountScripts.has(scriptHash)) return {
679
+ rule,
680
+ reason: "foreign-voter",
681
+ scriptHash
682
+ };
683
+ }
684
+ const scripts = witness.scripts ?? [];
685
+ if (scripts.some((script) => isNativeScript(script))) return {
686
+ rule,
687
+ reason: "native-script"
688
+ };
689
+ for (const script of scripts) {
690
+ const scriptHash = Script.fromCore(script).hash();
691
+ if (!runnable.has(scriptHash)) return {
692
+ rule,
693
+ reason: "foreign-attached-script",
694
+ scriptHash
695
+ };
696
+ }
697
+ return foreignReferenceScriptOf(referenceInputs, context);
698
+ };
699
+ /**
700
+ * The Plutus languages of the scripts the transaction attaches, then of the
701
+ * reference scripts its inputs and reference inputs carry: the service's
702
+ * language view covers all of them, not only the scripts that run.
703
+ */
704
+ const languagesOf = ({ witness, inputs, referenceInputs }) => [...(witness.scripts ?? []).flatMap((script) => isPlutusScript(script) ? [script.version] : []), ...[...inputs, ...referenceInputs].flatMap(({ output }) => output?.scriptReference && isPlutusScript(output.scriptReference) ? [output.scriptReference.version] : [])];
705
+ /** The redeemers that stand in for a witness set carrying datums and no redeemers. */
706
+ const EMPTY_REDEEMERS = "a0";
707
+ /**
708
+ * The script data hash the body commits to is the one the witness set's
709
+ * redeemers and datums hash to, under the cost models, with the language
710
+ * view of every attached and referenced script. Every script an account
711
+ * runs is Plutus V3, so a transaction carrying any other language is
712
+ * refused before a view is formed.
713
+ */
714
+ const scriptDataHash = (analysis, { costModels }) => {
715
+ const rule = "script_data_hash";
716
+ const declared = analysis.body.scriptIntegrityHash;
717
+ const { redeemers, datums } = analysis.witnessScriptData;
718
+ if (redeemers === void 0 && datums === void 0) return declared === void 0 ? void 0 : {
719
+ rule,
720
+ reason: "unexpected-script-data-hash",
721
+ declared
722
+ };
723
+ if (declared === void 0) return {
724
+ rule,
725
+ reason: "missing-script-data-hash"
726
+ };
727
+ const languages = languagesOf(analysis);
728
+ const foreign = languages.find((language) => language !== PlutusLanguageVersion.V3);
729
+ if (foreign !== void 0) return {
730
+ rule,
731
+ reason: "non-v3-script",
732
+ language: foreign
733
+ };
734
+ const views = new Costmdls();
735
+ for (const language of new Set(languages)) {
736
+ const costs = costModels.get(language);
737
+ if (!costs) return {
738
+ rule,
739
+ reason: "missing-cost-model",
740
+ language
741
+ };
742
+ views.insert(new CostModel(language, costs));
743
+ }
744
+ const computed = blake2b.hash(HexBlob(`${redeemers ?? EMPTY_REDEEMERS}${datums ?? ""}${views.languageViewsEncoding()}`), 32);
745
+ return computed === declared ? void 0 : {
746
+ rule,
747
+ reason: "script-data-hash-mismatch",
748
+ declared,
749
+ computed
750
+ };
751
+ };
752
+ /** Neither sponsor key signs for anything but the sponsor's own inputs. */
753
+ const signers = ({ body, withdrawals, sponsorKeys }) => {
754
+ const keyHash = (body.requiredExtraSignatures ?? []).find((signer) => signer === sponsorKeys.paymentKeyHash || signer === sponsorKeys.stakeKeyHash);
755
+ if (keyHash) return {
756
+ rule: "signers",
757
+ reason: "sponsor-required-signer",
758
+ keyHash
759
+ };
760
+ const withdrawal = withdrawals.find(({ stakeAddress }) => {
761
+ const credential = stakeCredentialOf(stakeAddress);
762
+ return credential !== void 0 && namesSponsorKey(credential, sponsorKeys);
763
+ });
764
+ if (withdrawal) return {
765
+ rule: "signers",
766
+ reason: "sponsor-withdrawal",
767
+ rewardAccount: withdrawal.stakeAddress
768
+ };
769
+ const certificateIndex = (body.certificates ?? []).findIndex((certificate) => certificateCredentials(certificate).some((credential) => namesSponsorKey(credential, sponsorKeys)));
770
+ if (certificateIndex >= 0) return {
771
+ rule: "signers",
772
+ reason: "sponsor-certificate",
773
+ certificateIndex
774
+ };
775
+ return (body.votingProcedures ?? []).some(({ voter }) => namesSponsorKey(voter.credential, sponsorKeys)) ? {
776
+ rule: "signers",
777
+ reason: "sponsor-voter"
778
+ } : void 0;
779
+ };
780
+ /** The service's rules in its check order; it evaluates the scripts between the last two. */
781
+ const RULES = [
782
+ wellFormed,
783
+ sponsorInputsRule,
784
+ sharedCollateral,
785
+ boundedValidity,
786
+ accountTransaction,
787
+ knownLogic,
788
+ sponsorOutflow,
789
+ noSponsorValueElsewhere,
790
+ noForeignScripts,
791
+ scriptDataHash,
792
+ signers
793
+ ];
794
+ /**
795
+ * Every rule of the fee sponsor service's policy that a transaction breaks,
796
+ * at most one per rule and in the service's check order, so the first is
797
+ * the one the service would answer with. Lace checks its custody builds
798
+ * with it before asking the sponsor for a witness, so a refusal from the
799
+ * service points at a real defect rather than a build Lace knew to be wrong.
800
+ *
801
+ * The script evaluation rule is not mirrored: it needs the chain. Of the
802
+ * sponsor's UTxOs, only the leased fee UTxO, the shared collateral and
803
+ * outputs locked by the sponsor payment key are recognised; the service
804
+ * also knows the rest of its pool. The service knows the logics its
805
+ * operator lists, which Lace takes to be the contract version's. Beyond the
806
+ * service's rules, the shared collateral rule refuses a fee above what the
807
+ * shared collateral covers, which the ledger would refuse after the service
808
+ * signed, and the script rule refuses a reference script other than the
809
+ * version's parked scripts on the network.
810
+ *
811
+ * @param resolvedInputs - the outputs the transaction's inputs spend and its
812
+ * reference inputs name; an input or a reference input without one is
813
+ * read as the service reads one the chain does not know, which is never
814
+ * an account's control or grant UTxO and carries no reference script
815
+ */
816
+ const findCustodySponsorRuleViolations = (transaction, resolvedInputs, context) => {
817
+ const analysis = analyse(transaction, resolvedInputs, context);
818
+ return RULES.flatMap((rule) => {
819
+ const violation = rule(analysis, context);
820
+ return violation ? [violation] : [];
821
+ });
822
+ };
823
+ //#endregion
824
+ //#region ../../packages/lib/cardano-custody/src/state/transitions.ts
825
+ /**
826
+ * The state Lace creates an account with: `logic` as its rules, the creator
827
+ * as the only device, generation zero and zero counters. The logic accepts
828
+ * any generation at creation; consent refuses every other initial state.
829
+ *
830
+ * @param logic - the applied hash of the logic the account runs, normally the
831
+ * contract version's `currentLogicHash`
832
+ */
833
+ const createInitialCustodyAccountState = (creatorKeyHash, logic) => {
834
+ if (!isHash28Hex(logic)) return fail$1({
835
+ code: "invalid-logic-hash",
836
+ logic
837
+ });
838
+ return isHash28Hex(creatorKeyHash) ? ok({
839
+ logic,
840
+ devices: [creatorKeyHash],
841
+ grantGeneration: 0n,
842
+ nextSlot: 0n,
843
+ revoked: [],
844
+ outstanding: 0n
845
+ }) : fail$1({
846
+ code: "invalid-device-key-hash",
847
+ device: creatorKeyHash
848
+ });
849
+ };
850
+ /** Checks a grant Lace is about to write: authoring rules, expiry, separation. */
851
+ const checkAuthoredGrant = (state, grant, now) => {
852
+ const violations = findCustodyGrantViolations(grant);
853
+ if (violations.length > 0) return {
854
+ code: "invalid-grant",
855
+ violations
856
+ };
857
+ if (!Number.isFinite(now)) return {
858
+ code: "invalid-now",
859
+ now
860
+ };
861
+ if (isCustodyGrantExpired(grant, now)) return {
862
+ code: "grant-expired",
863
+ slot: grant.slot,
864
+ expiresAt: grant.scope.expiresAt
865
+ };
866
+ if (state.devices.includes(grant.grantee)) return {
867
+ code: "grantee-is-device",
868
+ grantee: grant.grantee
869
+ };
870
+ };
871
+ /**
872
+ * Refuses a grantee that still holds a grant it could spend. One grant per
873
+ * agent key keeps each agent's authority in one place, so a revocation or a
874
+ * replacement always covers all of it. A grant judged against an older
875
+ * state than its own counts too: it is in flight and about to be current.
876
+ */
877
+ const checkGranteeHoldsNoCurrentGrant = (state, grants, { grantee, now }) => {
878
+ const held = grants.find((grant) => {
879
+ if (grant.grantee !== grantee) return false;
880
+ const liveness = custodyGrantLiveness(grant, state, now);
881
+ return liveness === "current" || liveness === "future-generation";
882
+ });
883
+ return held ? {
884
+ code: "grantee-has-grant",
885
+ grantee,
886
+ slot: held.slot
887
+ } : void 0;
888
+ };
889
+ /**
890
+ * Issues one grant in the state's next slot under its current generation:
891
+ * the next slot and the outstanding count move by one, and the result
892
+ * carries the datum of the new grant UTxO.
893
+ *
894
+ * @param grants - every grant of the account, from its grant UTxOs and from
895
+ * issues still in flight, to keep one current grant per agent key
896
+ */
897
+ const issueCustodyGrant = (state, { grantee, scope, now }, grants) => {
898
+ if (state.nextSlot >= CUSTODY_SLOT_LIMIT) return fail$1({
899
+ code: "slot-space-exhausted",
900
+ nextSlot: state.nextSlot
901
+ });
902
+ const grant = {
903
+ slot: state.nextSlot,
904
+ grantee,
905
+ generation: state.grantGeneration,
906
+ scope
907
+ };
908
+ const error = checkAuthoredGrant(state, grant, now) ?? checkGranteeHoldsNoCurrentGrant(state, grants, {
909
+ grantee,
910
+ now
911
+ });
912
+ if (error) return fail$1(error);
913
+ if (state.outstanding >= BigInt(16)) return fail$1({
914
+ code: "grant-limit",
915
+ limit: 16
916
+ });
917
+ return ok({
918
+ state: {
919
+ ...state,
920
+ nextSlot: state.nextSlot + 1n,
921
+ outstanding: state.outstanding + 1n
922
+ },
923
+ grant
924
+ });
925
+ };
926
+ /**
927
+ * Revokes the current grant in `slot` by appending the slot to the revoked
928
+ * list. The grant UTxO stays until a sweep burns its token.
929
+ *
930
+ * @param grants - every grant of the account; the one in `slot` must be current
931
+ */
932
+ const revokeCustodyGrant = (state, slot, grants) => {
933
+ const grant = grants.find((candidate) => candidate.slot === slot);
934
+ if (!grant) return fail$1({
935
+ code: "grant-not-found",
936
+ slot
937
+ });
938
+ if (!isCustodyGrantCurrent(grant, state)) return fail$1({
939
+ code: "grant-not-current",
940
+ slot,
941
+ liveness: custodyGrantLiveness(grant, state, 0)
942
+ });
943
+ if (state.revoked.length >= 32) return fail$1({
944
+ code: "revoked-list-full",
945
+ limit: 32
946
+ });
947
+ return ok({
948
+ ...state,
949
+ revoked: [...state.revoked, slot]
950
+ });
951
+ };
952
+ /**
953
+ * Revokes the current grant in `request.slot` and issues its replacement in
954
+ * the next slot, in one state. Both happen in one transaction; the old
955
+ * grant UTxO stays until a later sweep.
956
+ *
957
+ * @param grants - every grant of the account, as for `issueCustodyGrant`
958
+ */
959
+ const replaceCustodyGrant = (state, { slot, ...request }, grants) => {
960
+ const revoked = revokeCustodyGrant(state, slot, grants);
961
+ return revoked.ok ? issueCustodyGrant(revoked.value, request, grants) : revoked;
962
+ };
963
+ /** Kills every grant: the generation moves on and the revoked list clears. */
964
+ const revokeAllCustodyGrants = (state) => ({
965
+ ...state,
966
+ grantGeneration: state.grantGeneration + 1n,
967
+ revoked: []
968
+ });
969
+ /**
970
+ * Sweeps dead grants: their tokens burn, the outstanding count drops, and
971
+ * their slots leave the revoked list. Pruning is safe because slots are never
972
+ * reissued, and it keeps the list from filling up. Each grant must be dead
973
+ * against `state`, the state before the sweep.
974
+ *
975
+ * @param grants - the grants to sweep, at most `CUSTODY_MAX_GRANT_BATCH`
976
+ * @param now - POSIX milliseconds; a grant dead by expiry alone must have
977
+ * expired strictly before it, so a validity interval can start after the
978
+ * expiry
979
+ */
980
+ const sweepCustodyGrants = (state, grants, now) => {
981
+ if (!Number.isFinite(now)) return fail$1({
982
+ code: "invalid-now",
983
+ now
984
+ });
985
+ if (grants.length === 0) return fail$1({ code: "sweep-empty" });
986
+ if (grants.length > 8) return fail$1({
987
+ code: "sweep-batch-limit",
988
+ count: grants.length,
989
+ limit: 8
990
+ });
991
+ const slots = /* @__PURE__ */ new Set();
992
+ let startsAfter;
993
+ for (const grant of grants) {
994
+ if (slots.has(grant.slot)) return fail$1({
995
+ code: "duplicate-grant-slot",
996
+ slot: grant.slot
997
+ });
998
+ slots.add(grant.slot);
999
+ const liveness = custodyGrantLiveness(grant, state, now);
1000
+ const { expiresAt } = grant.scope;
1001
+ if (!(liveness === "superseded" || liveness === "revoked" || liveness === "expired" && expiresAt < BigInt(Math.floor(now)))) return fail$1({
1002
+ code: "grant-not-dead",
1003
+ slot: grant.slot,
1004
+ liveness
1005
+ });
1006
+ if (liveness === "expired" && (startsAfter === void 0 || expiresAt > startsAfter)) startsAfter = expiresAt;
1007
+ }
1008
+ return ok({
1009
+ state: {
1010
+ ...state,
1011
+ revoked: state.revoked.filter((slot) => !slots.has(slot)),
1012
+ outstanding: state.outstanding - BigInt(grants.length)
1013
+ },
1014
+ ...startsAfter !== void 0 && { startsAfter }
1015
+ });
1016
+ };
1017
+ /**
1018
+ * Lists a new device key. Every device has full owner authority, so a key
1019
+ * that holds a current grant is refused: removing the device later would
1020
+ * leave the grant in force.
1021
+ *
1022
+ * @param grants - every grant of the account
1023
+ */
1024
+ const addCustodyDevice = (state, device, grants) => {
1025
+ if (!isHash28Hex(device)) return fail$1({
1026
+ code: "invalid-device-key-hash",
1027
+ device
1028
+ });
1029
+ if (state.devices.includes(device)) return fail$1({
1030
+ code: "device-already-listed",
1031
+ device
1032
+ });
1033
+ if (state.devices.length >= 8) return fail$1({
1034
+ code: "device-limit",
1035
+ limit: 8
1036
+ });
1037
+ const slots = grants.filter((grant) => grant.grantee === device && isCustodyGrantCurrent(grant, state)).map((grant) => grant.slot);
1038
+ if (slots.length > 0) return fail$1({
1039
+ code: "device-is-grantee",
1040
+ device,
1041
+ slots
1042
+ });
1043
+ return ok({
1044
+ ...state,
1045
+ devices: [...state.devices, device]
1046
+ });
1047
+ };
1048
+ /**
1049
+ * Removes a device key. Refuses when no well-formed device key would remain,
1050
+ * because the account would then accept no owner operation ever again. Grants
1051
+ * held by the same key stay in force.
1052
+ */
1053
+ const removeCustodyDevice = (state, device) => {
1054
+ const listed = state.devices.find((existing) => existing === device);
1055
+ if (listed === void 0) return fail$1({
1056
+ code: "device-not-listed",
1057
+ device
1058
+ });
1059
+ const devices = state.devices.filter((existing) => existing !== device);
1060
+ if (!devices.some((remaining) => isHash28Hex(remaining))) return fail$1({
1061
+ code: "last-device",
1062
+ device: listed
1063
+ });
1064
+ return ok({
1065
+ ...state,
1066
+ devices
1067
+ });
1068
+ };
1069
+ //#endregion
1070
+ //#region ../../node_modules/@cardano-sdk/tx-construction/dist/esm/input-selection/selectionConstraints.js
1071
+ init__polyfill_buffer_patch();
1072
+ const MAX_U64 = 18446744073709551615n;
1073
+ const computeMinimumCoinQuantity = (coinsPerUtxoByte) => (output) => minAdaRequired(output, BigInt(coinsPerUtxoByte));
1074
+ const tokenBundleSizeExceedsLimit = (maxValueSize) => (tokenBundle) => {
1075
+ if (!tokenBundle) return false;
1076
+ const value = new Value(MAX_U64);
1077
+ value.setMultiasset(tokenBundle);
1078
+ return value.toCbor().length / 2 > maxValueSize;
1079
+ };
1080
+ //#endregion
1081
+ //#region ../../packages/contract/cardano-context/src/estimate-signed-tx-size.ts
1082
+ const DUMMY_SIGNATURE = "f".repeat(128);
1083
+ const dummyVkey = (index) => index.toString(16).padStart(64, "0");
1084
+ /**
1085
+ * Byte length of `tx` once signed. Estimated exactly by populating one dummy
1086
+ * vkey witness per unique signer and letting the serializer measure the result
1087
+ * (the cardano-sdk `getTxSize` technique), which counts the real Conway witness
1088
+ * set framing, unlike a hand-summed byte formula.
1089
+ *
1090
+ * `resolvedInputs` must resolve every input so the signer count (input payment
1091
+ * keys plus withdrawal / certificate stake keys) is complete: an incomplete set
1092
+ * would under-count witnesses and under-estimate the size, the dangerous
1093
+ * direction for a size guard.
1094
+ */
1095
+ const estimateSignedTxSize = (tx, resolvedInputs) => {
1096
+ const core = tx.toCore();
1097
+ const signerCount = getUniqueSignerKeyHashes(core, resolvedInputs).size;
1098
+ const signatures = /* @__PURE__ */ new Map();
1099
+ for (let index = 0; index < signerCount; index++) signatures.set(dummyVkey(index), DUMMY_SIGNATURE);
1100
+ return Transaction.fromCore({
1101
+ ...core,
1102
+ witness: {
1103
+ ...core.witness,
1104
+ signatures
1105
+ }
1106
+ }).toCbor().length / 2;
1107
+ };
1108
+ //#endregion
1109
+ //#region ../../packages/contract/cardano-context/src/tx-builder/transaction-limits.ts
1110
+ /**
1111
+ * Conway's cap on the reference-script bytes one transaction reads, summed
1112
+ * over its spent and reference inputs. A ledger constant, not a protocol
1113
+ * parameter.
1114
+ */
1115
+ const MAX_REFERENCE_SCRIPT_SIZE_PER_TX = 204800;
1116
+ /** A built transaction would be refused by the ledger for exceeding a limit. */
1117
+ var TransactionLimitsExceededError = class extends Error {
1118
+ constructor(exceeded, usage, limits) {
1119
+ super(`Transaction exceeds protocol limits (${exceeded.join(", ")}): execution units ${usage.executionUnits.memory}/${limits.maxExecutionUnitsPerTransaction.memory} memory, ${usage.executionUnits.steps}/${limits.maxExecutionUnitsPerTransaction.steps} steps; signed size ${usage.signedTxSize}/${limits.maxTxSize} bytes; reference scripts ${usage.referenceScriptSize}/${MAX_REFERENCE_SCRIPT_SIZE_PER_TX} bytes`);
1120
+ this.exceeded = exceeded;
1121
+ this.usage = usage;
1122
+ this.limits = limits;
1123
+ this.name = "TransactionLimitsExceededError";
1124
+ }
1125
+ };
1126
+ /**
1127
+ * Checks a built transaction against the per-transaction execution-unit,
1128
+ * size and reference-script limits, which `TransactionBuilder` does not
1129
+ * enforce. Run it on the final (evaluated) transaction. The size is measured
1130
+ * with one vkey witness per required signer, so `resolvedInputs` must resolve
1131
+ * every input and collateral input, as for `estimateSignedTxSize`. It must
1132
+ * also resolve every reference input, whose scripts count toward the
1133
+ * reference-script limit.
1134
+ *
1135
+ * @returns The measured usage when every limit holds.
1136
+ * @throws `TransactionLimitsExceededError` When any limit is exceeded.
1137
+ */
1138
+ const assertWithinTransactionLimits = (tx, resolvedInputs, limits) => {
1139
+ const redeemers = tx.witnessSet().redeemers()?.toCore() ?? [];
1140
+ const body = tx.body().toCore();
1141
+ const usage = {
1142
+ executionUnits: redeemers.reduce((total, { executionUnits }) => ({
1143
+ memory: total.memory + executionUnits.memory,
1144
+ steps: total.steps + executionUnits.steps
1145
+ }), {
1146
+ memory: 0,
1147
+ steps: 0
1148
+ }),
1149
+ signedTxSize: estimateSignedTxSize(tx, resolvedInputs),
1150
+ referenceScriptSize: referenceScriptSize([...body.inputs, ...body.referenceInputs ?? []], resolvedInputs)
1151
+ };
1152
+ const exceeded = [];
1153
+ if (usage.executionUnits.memory > limits.maxExecutionUnitsPerTransaction.memory) exceeded.push("executionMemory");
1154
+ if (usage.executionUnits.steps > limits.maxExecutionUnitsPerTransaction.steps) exceeded.push("executionSteps");
1155
+ if (usage.signedTxSize > limits.maxTxSize) exceeded.push("txSize");
1156
+ if (usage.referenceScriptSize > MAX_REFERENCE_SCRIPT_SIZE_PER_TX) exceeded.push("referenceScriptSize");
1157
+ if (exceeded.length > 0) throw new TransactionLimitsExceededError(exceeded, usage, limits);
1158
+ return usage;
1159
+ };
1160
+ //#endregion
1161
+ //#region ../../packages/module/blockchain-cardano/src/store/composer/resolve-validity-interval.ts
1162
+ /** Two hours, matching the send flow's window (`build-tx.ts`). */
1163
+ const DEFAULT_VALIDITY_SECONDS = 7200;
1164
+ /**
1165
+ * The era covering `slot`: the last summary that has already started.
1166
+ * Summaries arrive oldest-first, so this walks forward while the next
1167
+ * era's start is still at or below the slot.
1168
+ */
1169
+ const eraSummaryAtSlot = (eraSummaries, slot) => {
1170
+ let index = 0;
1171
+ while (index + 1 < eraSummaries.length && eraSummaries[index + 1].start.slot <= slot) index++;
1172
+ return eraSummaries[index];
1173
+ };
1174
+ const secondsToSlots = (seconds, slotLengthMs) => Math.ceil(seconds * 1e3 / slotLengthMs);
1175
+ /**
1176
+ * Convert a tip-relative duration into the absolute slots a Cardano
1177
+ * validity interval needs.
1178
+ *
1179
+ * @throws If the era summaries are missing/unusable, or the requested
1180
+ * window is not a positive, coherent interval.
1181
+ */
1182
+ const resolveValidityInterval = ({ tipSlot, eraSummaries, validitySeconds = DEFAULT_VALIDITY_SECONDS, validityStartSeconds }) => {
1183
+ if (eraSummaries.length === 0) throw new Error("Cannot resolve a validity interval: no Cardano era summaries available");
1184
+ if (!Number.isFinite(validitySeconds) || validitySeconds <= 0) throw new Error(`Validity window must be a positive number of seconds, got ${validitySeconds}`);
1185
+ const { slotLength } = eraSummaryAtSlot(eraSummaries, tipSlot).parameters;
1186
+ if (!Number.isFinite(slotLength) || slotLength <= 0) throw new Error(`Era summary reports an unusable slot length: ${slotLength}`);
1187
+ const invalidHereafter = Slot(tipSlot + secondsToSlots(validitySeconds, slotLength));
1188
+ if (validityStartSeconds === void 0) return { invalidHereafter };
1189
+ if (!Number.isFinite(validityStartSeconds) || validityStartSeconds < 0) throw new Error(`Validity start must be a non-negative number of seconds, got ${validityStartSeconds}`);
1190
+ const invalidBefore = Slot(tipSlot + secondsToSlots(validityStartSeconds, slotLength));
1191
+ if (invalidBefore >= invalidHereafter) throw new Error(`Validity window is empty: start slot ${invalidBefore} is not before end slot ${invalidHereafter}`);
1192
+ return {
1193
+ invalidBefore,
1194
+ invalidHereafter
1195
+ };
1196
+ };
1197
+ //#endregion
1198
+ //#region ../../packages/module/blockchain-cardano/src/store/composer/build-composer-tx.ts
1199
+ /** Failure with a code the side-effect maps to actionable copy. */
1200
+ const fail = (message, code) => Object.assign(new Error(message), { code });
1201
+ const utxoRefKey = (txIn) => `${txIn.txId}#${txIn.index}`;
1202
+ /**
1203
+ * An address from the request, as the SDK's payment address.
1204
+ *
1205
+ * The SDK's own constructor throws an UNCODED `InvalidStringError`, which would
1206
+ * reach the side-effect as a build fault rather than as the request mistake it
1207
+ * is — a bech32 the user mistyped would then read as "we couldn't build your
1208
+ * transaction" instead of "check what you entered".
1209
+ *
1210
+ * @param what - names the address in the message, since a request may carry
1211
+ * several and the failure has to say which one is unusable.
1212
+ */
1213
+ const toPaymentAddress = (value, what) => {
1214
+ try {
1215
+ return PaymentAddress(value);
1216
+ } catch {
1217
+ throw fail(`${what} is not a Cardano address: "${value}"`, "invalid-address");
1218
+ }
1219
+ };
1220
+ /**
1221
+ * Quantities cross the slice boundary as decimal strings, so a malformed
1222
+ * one is a request error rather than a programming error — parse it into
1223
+ * a coded failure instead of letting `BigInt` throw an uncoded SyntaxError.
1224
+ */
1225
+ const toQuantity = (value, what) => {
1226
+ if (!/^-?\d+$/.test(value)) throw fail(`${what} is not a whole number: "${value}"`, "invalid-output");
1227
+ return BigInt(value);
1228
+ };
1229
+ const toValue = (output) => {
1230
+ const coins = toQuantity(output.lovelace, `Lovelace amount for the output to ${output.address}`);
1231
+ if (coins < 0n) throw fail(`Output to ${output.address} requests a negative amount of lovelace`, "invalid-output");
1232
+ if (!output.assets || output.assets.length === 0) return { coins };
1233
+ const assets = /* @__PURE__ */ new Map();
1234
+ for (const asset of output.assets) {
1235
+ const quantity = toQuantity(asset.quantity, `Quantity for asset ${asset.assetId}`);
1236
+ if (quantity <= 0n) throw fail(`Asset ${asset.assetId} requests a non-positive quantity`, "invalid-output");
1237
+ const assetId = asset.assetId;
1238
+ assets.set(assetId, (assets.get(assetId) ?? 0n) + quantity);
1239
+ }
1240
+ return {
1241
+ coins,
1242
+ assets
1243
+ };
1244
+ };
1245
+ /**
1246
+ * Raise the output's lovelace to the protocol minimum for its size when
1247
+ * the request is below it. An assets-only output can therefore ask for
1248
+ * `'0'` and still be spendable.
1249
+ */
1250
+ const withMinimumCoin = (address, value, params) => {
1251
+ if (tokenBundleSizeExceedsLimit(params.maxValueSize)(value.assets)) throw fail(`Output to ${address} exceeds the maximum token bundle size`, "invalid-output");
1252
+ const minimumCoin = BigInt(computeMinimumCoinQuantity(params.coinsPerUtxoByte)({
1253
+ address,
1254
+ value
1255
+ }));
1256
+ return value.coins >= minimumCoin ? value : {
1257
+ ...value,
1258
+ coins: minimumCoin
1259
+ };
1260
+ };
1261
+ const parseMetadata = (request) => {
1262
+ if (!request.metadata || request.metadata.length === 0) return void 0;
1263
+ const blob = /* @__PURE__ */ new Map();
1264
+ for (const entry of request.metadata) {
1265
+ if (!/^\d+$/.test(entry.label)) throw fail(`Metadata label "${entry.label}" is not a non-negative integer`, "invalid-metadata");
1266
+ let parsed;
1267
+ try {
1268
+ parsed = JSON.parse(entry.json);
1269
+ } catch (error) {
1270
+ throw fail(`Metadata for label ${entry.label} is not valid JSON: ${error.message}`, "invalid-metadata");
1271
+ }
1272
+ blob.set(BigInt(entry.label), jsonToMetadatum(parsed));
1273
+ }
1274
+ return blob;
1275
+ };
1276
+ const resolveSelectedInputs = (request, spendableUtxos) => {
1277
+ if (!request.selectedInputs || request.selectedInputs.length === 0) return [];
1278
+ const byRef = new Map(spendableUtxos.map((utxo) => [utxoRefKey(utxo[0]), utxo]));
1279
+ return request.selectedInputs.map((ref) => {
1280
+ const utxo = byRef.get(`${ref.txId}#${ref.index}`);
1281
+ if (!utxo) throw fail(`Selected input ${ref.txId}#${ref.index} is not among the account's spendable UTxOs`, "input-unavailable");
1282
+ return utxo;
1283
+ });
1284
+ };
1285
+ /**
1286
+ * The balancer's own failure type is the only signal that the account cannot
1287
+ * cover what was asked for — an unaffordable amount is a request mistake, not a
1288
+ * build fault, and only the coded form reaches the copy that says so.
1289
+ *
1290
+ * Tagged rather than replaced: the reported name and message stay the
1291
+ * balancer's, which is what names the shortfall for a diagnostic.
1292
+ */
1293
+ const codeInputSelectionFailure = async (build) => {
1294
+ try {
1295
+ return await build;
1296
+ } catch (error) {
1297
+ if (error instanceof InputSelectionError) throw Object.assign(error, { code: "insufficient-funds" });
1298
+ throw error;
1299
+ }
1300
+ };
1301
+ const buildComposerTx = async ({ request, changeAddress, networkMagic, protocolParameters, spendableUtxos, tipSlot, eraSummaries }) => {
1302
+ if (request.outputs.length === 0) throw fail("A composed transaction needs at least one output", "no-outputs");
1303
+ if (spendableUtxos.length === 0) throw fail("The account has no spendable UTxOs to fund this transaction", "no-utxos");
1304
+ const validityInterval = resolveValidityInterval({
1305
+ tipSlot,
1306
+ eraSummaries,
1307
+ validitySeconds: request.validitySeconds,
1308
+ validityStartSeconds: request.validityStartSeconds
1309
+ });
1310
+ const selectedInputs = resolveSelectedInputs(request, spendableUtxos);
1311
+ const selectedReferences = new Set(selectedInputs.map((utxo) => utxoRefKey(utxo[0])));
1312
+ const coverUtxos = spendableUtxos.filter((utxo) => !selectedReferences.has(utxoRefKey(utxo[0])));
1313
+ const builder = new TransactionBuilder(networkMagic, protocolParameters).setChangeAddress(changeAddress).setUnspentOutputs(coverUtxos).setValidityInterval(validityInterval);
1314
+ for (const utxo of selectedInputs) builder.addInput(utxo);
1315
+ const metadataBlob = parseMetadata(request);
1316
+ if (metadataBlob) for (const [label, value] of metadataBlob) builder.setMetadata(label, value);
1317
+ for (const output of request.outputs) {
1318
+ const address = toPaymentAddress(output.address, "The address of an output");
1319
+ builder.addOutput({
1320
+ address,
1321
+ value: withMinimumCoin(address, toValue(output), protocolParameters)
1322
+ });
1323
+ }
1324
+ const transaction = await codeInputSelectionFailure(builder.build());
1325
+ return {
1326
+ cbor: transaction.toCbor(),
1327
+ txId: transaction.getId(),
1328
+ fee: transaction.toCore().body.fee
1329
+ };
1330
+ };
1331
+ //#endregion
1332
+ //#region ../../packages/module/blockchain-cardano/src/store/composer/build-side-effect.ts
1333
+ const GENERIC_BUILD_ERROR_KEYS$1 = {
1334
+ title: "v2.composer.build.error.title",
1335
+ subtitle: "v2.composer.build.error.subtitle"
1336
+ };
1337
+ const BUILD_ERROR_KEYS_BY_CODE$1 = {
1338
+ "no-utxos": {
1339
+ title: "v2.composer.build.error.no-utxos.title",
1340
+ subtitle: "v2.composer.build.error.no-utxos.subtitle"
1341
+ },
1342
+ "no-outputs": {
1343
+ title: "v2.composer.build.error.invalid-request.title",
1344
+ subtitle: "v2.composer.build.error.invalid-request.subtitle"
1345
+ },
1346
+ "invalid-output": {
1347
+ title: "v2.composer.build.error.invalid-request.title",
1348
+ subtitle: "v2.composer.build.error.invalid-request.subtitle"
1349
+ },
1350
+ "invalid-metadata": {
1351
+ title: "v2.composer.build.error.invalid-request.title",
1352
+ subtitle: "v2.composer.build.error.invalid-request.subtitle"
1353
+ },
1354
+ "input-unavailable": {
1355
+ title: "v2.composer.build.error.invalid-request.title",
1356
+ subtitle: "v2.composer.build.error.invalid-request.subtitle"
1357
+ },
1358
+ "invalid-address": {
1359
+ title: "v2.composer.build.error.invalid-request.title",
1360
+ subtitle: "v2.composer.build.error.invalid-request.subtitle"
1361
+ },
1362
+ "insufficient-funds": {
1363
+ title: "v2.composer.build.error.invalid-request.title",
1364
+ subtitle: "v2.composer.build.error.invalid-request.subtitle"
1365
+ }
1366
+ };
1367
+ const failure$1 = (error, code) => ({
1368
+ success: false,
1369
+ error: {
1370
+ name: error.name,
1371
+ message: error.message
1372
+ },
1373
+ errorTranslationKeys: (code ? BUILD_ERROR_KEYS_BY_CODE$1[code] : void 0) ?? GENERIC_BUILD_ERROR_KEYS$1
1374
+ });
1375
+ const buildComposition = async (state, stateObservables, dependencies) => {
1376
+ try {
1377
+ const { accountId, request } = state;
1378
+ const cardano = dependencies.txExecutorCardano;
1379
+ const cardanoContext = stateObservables.cardanoContext;
1380
+ const [networkMagic, protocolParameters, tip, eraSummaries, accountUtxos, unspendableUtxos, addresses] = await Promise.all([
1381
+ firstValueFrom(cardano.cardanoNetworkMagic$),
1382
+ firstValueFrom(cardanoContext.selectProtocolParameters$),
1383
+ firstValueFrom(cardanoContext.selectTip$),
1384
+ firstValueFrom(cardanoContext.selectEraSummaries$),
1385
+ firstValueFrom(cardano.cardanoAccountUtxos$),
1386
+ firstValueFrom(cardano.cardanoAccountUnspendableUtxos$),
1387
+ firstValueFrom(cardano.cardanoAddresses$)
1388
+ ]);
1389
+ if (networkMagic === void 0) throw new Error("Cardano network magic not available");
1390
+ if (!protocolParameters) throw new Error("Cardano protocol parameters not available");
1391
+ if (!tip) throw new Error("Cardano chain tip not available");
1392
+ if (!eraSummaries) throw new Error("Cardano era summaries not available");
1393
+ refuseExtensionAccount(accountId, addresses);
1394
+ const primary = addresses.filter((address) => address.accountId === accountId && address.blockchainName === "Cardano")[0];
1395
+ if (!primary) throw new Error("No Cardano addresses found for account");
1396
+ const result = await buildComposerTx({
1397
+ request,
1398
+ changeAddress: toPaymentAddress(request.changeAddress ?? primary.address, "The change address"),
1399
+ networkMagic,
1400
+ protocolParameters,
1401
+ spendableUtxos: filterSpendableUtxos(accountUtxos[accountId] ?? [], unspendableUtxos[accountId] ?? []),
1402
+ tipSlot: Number(tip.slot),
1403
+ eraSummaries
1404
+ });
1405
+ return {
1406
+ success: true,
1407
+ serializedTx: result.cbor,
1408
+ txId: result.txId,
1409
+ fees: [{
1410
+ tokenId: LOVELACE_TOKEN_ID,
1411
+ amount: BigNumber(result.fee)
1412
+ }]
1413
+ };
1414
+ } catch (error) {
1415
+ return failure$1(error instanceof Error ? error : new Error(String(error)), error.code);
1416
+ }
1417
+ };
1418
+ const makeComposerBuilding = () => (_, stateObservables, dependencies) => firstStateOfStatus(stateObservables.composerFlow.selectState$, "Building").pipe(switchMap((state) => from(buildComposition(state, stateObservables, dependencies)).pipe(map((result) => dependencies.actions.composerFlow.buildCompleted({
1419
+ accountId: state.accountId,
1420
+ result
1421
+ })))));
1422
+ //#endregion
1423
+ //#region ../../packages/module/blockchain-cardano/src/store/composer-flow-side-effects.ts
1424
+ const composerFlowSideEffects = [makeComposerBuilding()];
1425
+ //#endregion
1426
+ //#region ../../packages/module/blockchain-cardano/src/store/dependencies.ts
1427
+ const initializeDependencies = async (_, { logger }) => ({ cardanoInMemorySigning: { signTransaction: (props) => forkJoin([from(SodiumBip32Ed25519.create()), from(createTxInKeyPathMap(props.tx.body().toCore(), props.knownAddresses, createInputResolver(props.utxo)))]).pipe(mergeMap(([bip32Ed25519, txInKeyPathMap]) => {
1428
+ const encryptedRootPrivateKeyBytes = ByteArray.fromHex(props.encryptedRootPrivateKey);
1429
+ return from(new InMemoryKeyAgent({
1430
+ accountIndex: props.accountIndex,
1431
+ chainId: props.chainId,
1432
+ encryptedRootPrivateKeyBytes: [...encryptedRootPrivateKeyBytes],
1433
+ extendedAccountPublicKey: props.extendedAccountPublicKey,
1434
+ getPassphrase: async () => props.authSecret,
1435
+ purpose: KeyPurpose.STANDARD
1436
+ }, {
1437
+ bip32Ed25519,
1438
+ logger,
1439
+ rootPrivateKeyEncryption: SecretBox.isSealed(encryptedRootPrivateKeyBytes) ? {
1440
+ encrypt: SecretBox.seal,
1441
+ decrypt: SecretBox.open
1442
+ } : void 0
1443
+ }).signTransaction(props.tx.body(), {
1444
+ knownAddresses: props.knownAddresses,
1445
+ txInKeyPathMap,
1446
+ scripts: props.tx.toCore().witness.scripts
1447
+ })).pipe(map((signatures) => {
1448
+ const witnessSet = props.tx.witnessSet();
1449
+ applyVkeyWitnesses(witnessSet, signatures);
1450
+ props.tx.setWitnessSet(witnessSet);
1451
+ return Ok$1(props.tx);
1452
+ }));
1453
+ }), catchError((error) => of(Err$1({
1454
+ code: "SIGNING_FAILED",
1455
+ message: error.message
1456
+ })))) } });
1457
+ //#endregion
1458
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/build-failure.ts
1459
+ /**
1460
+ * Stops a custody build with a typed reason. The build side effect reports
1461
+ * it as the failure; any other error thrown during a build is a defect and
1462
+ * is reported as `build-failed`.
1463
+ */
1464
+ var CustodyBuildFailure = class extends Error {
1465
+ constructor(reason) {
1466
+ super(`Custody transaction build failed: ${reason.code}`);
1467
+ this.reason = reason;
1468
+ this.name = "CustodyBuildFailure";
1469
+ }
1470
+ };
1471
+ const failCustodyBuild = (reason) => {
1472
+ throw new CustodyBuildFailure(reason);
1473
+ };
1474
+ /**
1475
+ * The build failure a state transition's refusal stands for. A full account
1476
+ * the owner can act on has its own code: sweep dead grants to free the
1477
+ * outstanding count or the revoked list, or wait for a grant to die.
1478
+ */
1479
+ const transitionFailureOf = (error) => {
1480
+ if (error.code === "grant-limit" || error.code === "revoked-list-full") return {
1481
+ code: error.code,
1482
+ limit: error.limit
1483
+ };
1484
+ if (error.code === "grant-not-dead") return {
1485
+ code: "sweep-not-dead",
1486
+ slot: error.slot,
1487
+ liveness: error.liveness
1488
+ };
1489
+ return {
1490
+ code: "invalid-operation",
1491
+ reason: "state-transition",
1492
+ error
1493
+ };
1494
+ };
1495
+ /** What a library transition produced, or the build refused with its reason. */
1496
+ const transitioned = (result) => result.ok ? result.value : failCustodyBuild(transitionFailureOf(result.error));
1497
+ const messageOf = (error) => error instanceof Error ? error.message : String(error);
1498
+ //#endregion
1499
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/custody-execution-estimate.ts
1500
+ /**
1501
+ * Net memory units of one run of each handler: the largest figure the
1502
+ * budget table of contract b48545f's security review ("Resource
1503
+ * exhaustion": `aiken check -D` less each fixture's baseline, over the
1504
+ * largest state) gives for a transaction Lace builds, which issues one
1505
+ * grant, sweeps at most eight and spends at most twelve funds.
1506
+ */
1507
+ const HANDLER_NET_MEMORY = {
1508
+ device: 48e4,
1509
+ spendWithGrant: 4e5,
1510
+ sweepGrant: 43e4,
1511
+ fund: 2e5,
1512
+ issueGrants: 15e4,
1513
+ burnGrants: 34e4,
1514
+ createAccount: 15e4,
1515
+ operate: 16e4
1516
+ };
1517
+ /**
1518
+ * Net memory units of the logic run per path, from the same table, and
1519
+ * what each issued grant, swept grant and fund input adds: the owner path
1520
+ * runs 1.11 M for a revoke, 1.99 M issuing one grant, 1.54 M sweeping one
1521
+ * and 1.21 M with a reserve beside the control; the agent path 1.96 M over
1522
+ * one deposit and 5.94 M over forty.
1523
+ *
1524
+ * Summed per transaction they reach at least the review's figures, given
1525
+ * in brackets: a creation 0.75 M (0.73 M), a revoke 1.59 M (1.33 M), an
1526
+ * issue 2.62 M (2.41 M), a sweep of eight 8.81 M (8.08 M) and an agent
1527
+ * spend over eight funds 4.76 M (4.17 M).
1528
+ */
1529
+ const LOGIC_NET_MEMORY = {
1530
+ path: {
1531
+ agent: 196e4,
1532
+ arrival: 44e4,
1533
+ owner: 111e4
1534
+ },
1535
+ issuedGrant: 88e4,
1536
+ sweptGrant: 43e4,
1537
+ fund: 1e5
1538
+ };
1539
+ /**
1540
+ * What one run is expected to cost on chain, as a multiple of its net
1541
+ * figure. The runner leaves out decoding the script context, which every
1542
+ * run pays and which grows with the transaction. On upstream's devnet with
1543
+ * Preprod's parameters (docs/devnet-evidence.md, "Budget comparison") a
1544
+ * revoke over the largest state measured 1.74 M against 1.10 M net, a
1545
+ * sweep of eight 9.20 M against 8.26 M and an agent spend over twelve
1546
+ * deposits 4.28 M. A transaction that measures above the estimate is
1547
+ * reseeded once at the measurement.
1548
+ */
1549
+ const CUSTODY_EXECUTION_ESTIMATE_FACTOR = 2;
1550
+ /**
1551
+ * CPU steps the estimate charges per memory unit. Every redeemer of the
1552
+ * devnet run measured between 290 and 460 steps per memory unit, the
1553
+ * smallest Fund runs at the top of that range; the large logic and proxy
1554
+ * runs, which dominate every total, stay below 370.
1555
+ */
1556
+ const CUSTODY_ESTIMATE_STEPS_PER_MEMORY_UNIT = 400;
1557
+ /**
1558
+ * What a reseed adds to the measured execution units, in percent. The
1559
+ * measurement already holds the evaluation margin; this one covers the
1560
+ * reseeded transaction measuring differently, since a higher fee changes
1561
+ * its change output.
1562
+ */
1563
+ const CUSTODY_RESEED_MARGIN_PERCENT = 10;
1564
+ const withReseedMargin = (units) => Math.ceil(units * (100 + CUSTODY_RESEED_MARGIN_PERCENT) / 100);
1565
+ const logicNetMemory = ({ logic, issueGrants = 0, sweepGrant = 0, fund = 0 }) => logic ? LOGIC_NET_MEMORY.path[logic] + LOGIC_NET_MEMORY.issuedGrant * issueGrants + LOGIC_NET_MEMORY.sweptGrant * sweepGrant + LOGIC_NET_MEMORY.fund * fund : 0;
1566
+ /**
1567
+ * The execution units a transaction that runs `runs` is expected to
1568
+ * measure: the handlers' and the logic's net memory units times the
1569
+ * estimate factor, and steps in proportion. It is not capped at the
1570
+ * per-transaction limit.
1571
+ */
1572
+ const custodyExecutionEstimate = (runs) => {
1573
+ const memory = Object.keys(HANDLER_NET_MEMORY).reduce((total, handler) => total + HANDLER_NET_MEMORY[handler] * (runs[handler] ?? 0), logicNetMemory(runs)) * CUSTODY_EXECUTION_ESTIMATE_FACTOR;
1574
+ return {
1575
+ memory,
1576
+ steps: memory * CUSTODY_ESTIMATE_STEPS_PER_MEMORY_UNIT
1577
+ };
1578
+ };
1579
+ /**
1580
+ * The plan's estimate within the per-transaction limit, which no valid
1581
+ * transaction exceeds: what balancing prices a plan's fee at until a
1582
+ * reseed.
1583
+ */
1584
+ const estimateWithinLimit = (estimate, limit) => ({
1585
+ memory: Math.min(estimate.memory, limit.memory),
1586
+ steps: Math.min(estimate.steps, limit.steps)
1587
+ });
1588
+ /** What a build learned from redeemers that measured above `seeded`. */
1589
+ const custodyReseedOf = (evaluated, seeded) => {
1590
+ const sum = evaluated.reduce((total, units) => ({
1591
+ memory: total.memory + units.memory,
1592
+ steps: total.steps + units.steps
1593
+ }), {
1594
+ memory: 0,
1595
+ steps: 0
1596
+ });
1597
+ return {
1598
+ measured: {
1599
+ memory: Math.max(sum.memory, seeded.memory),
1600
+ steps: Math.max(sum.steps, seeded.steps)
1601
+ },
1602
+ redeemers: evaluated.length,
1603
+ dearest: {
1604
+ memory: Math.max(0, ...evaluated.map(({ memory }) => memory)),
1605
+ steps: Math.max(0, ...evaluated.map(({ steps }) => steps))
1606
+ }
1607
+ };
1608
+ };
1609
+ /**
1610
+ * What a reseed prices a plan of `redeemers` redeemers at: the measurement
1611
+ * plus the reseed margin, with each redeemer the plan has beyond the
1612
+ * measured transaction, such as a fund input the selection round added,
1613
+ * priced as the dearest one measured. It is not capped at the
1614
+ * per-transaction limit, so a transaction over the limit still builds and
1615
+ * its limit check reports the overrun.
1616
+ */
1617
+ const reseededExecutionUnits = ({ measured, redeemers: measuredRedeemers, dearest }, redeemers) => {
1618
+ const added = Math.max(0, redeemers - measuredRedeemers);
1619
+ return {
1620
+ memory: withReseedMargin(measured.memory + added * dearest.memory),
1621
+ steps: withReseedMargin(measured.steps + added * dearest.steps)
1622
+ };
1623
+ };
1624
+ //#endregion
1625
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/custody-assembly.ts
1626
+ /**
1627
+ * The account's control output: lovelace and the state NFT only, with the
1628
+ * state encoded afresh as an inline datum.
1629
+ */
1630
+ const controlOutputOf = (identity, state, lovelace) => ({
1631
+ address: identity.address,
1632
+ value: {
1633
+ coins: lovelace,
1634
+ assets: new Map([[identity.stateNftAssetId, 1n]])
1635
+ },
1636
+ datum: encodeCustodyAccountState(state)
1637
+ });
1638
+ /**
1639
+ * A grant UTxO at the account: lovelace and the grant's token only, with
1640
+ * the grant as an inline datum and no reference script.
1641
+ */
1642
+ const grantOutputOf = (identity, grant, lovelace) => ({
1643
+ address: identity.address,
1644
+ value: {
1645
+ coins: lovelace,
1646
+ assets: new Map([[custodyGrantAssetId({
1647
+ accountValidatorHash: identity.contractVersion.accountValidatorHash,
1648
+ stakeScriptHash: identity.stakeScriptHash
1649
+ }, grant.slot), 1n]])
1650
+ },
1651
+ datum: encodeCustodyGrant(grant)
1652
+ });
1653
+ /**
1654
+ * The least lovelace an output holds at its own size, at least `floor`. The
1655
+ * coin's own width changes the size, so the minimum is taken until it
1656
+ * settles.
1657
+ */
1658
+ const outputLovelaceFor = (outputWith, floor, coinsPerUtxoByte) => {
1659
+ let lovelace = floor;
1660
+ for (;;) {
1661
+ const minimum = minAdaRequired(outputWith(lovelace), BigInt(coinsPerUtxoByte));
1662
+ if (minimum <= lovelace) return lovelace;
1663
+ lovelace = minimum;
1664
+ }
1665
+ };
1666
+ /**
1667
+ * The lovelace a control output carrying `state` holds: at least `floor`
1668
+ * and at least the minimum for its size.
1669
+ */
1670
+ const controlLovelaceFor = ({ identity, state, floor }, coinsPerUtxoByte) => outputLovelaceFor((lovelace) => controlOutputOf(identity, state, lovelace), floor, coinsPerUtxoByte);
1671
+ /** What `units` of execution cost at the protocol's prices. */
1672
+ const executionFeeOf = (units, { prices }) => BigInt(Math.ceil(prices.memory * units.memory + prices.steps * units.steps));
1673
+ /**
1674
+ * The most the builder prices a fee at before evaluation, when it seeds
1675
+ * `seed` execution units: the largest transaction's size fee plus the seed
1676
+ * at the protocol's prices. Balancing must fund that fee from the inputs
1677
+ * before evaluation lowers it.
1678
+ */
1679
+ const preEvaluationFeeBound = (parameters, seed) => BigInt(parameters.minFeeConstant) + BigInt(parameters.minFeeCoefficient) * BigInt(parameters.maxTxSize) + executionFeeOf(seed, parameters);
1680
+ /**
1681
+ * The least fee a plan's transaction pays, whatever its body and its
1682
+ * scripts measure: the size fee of the scripts it attaches and the
1683
+ * reference-script fee of the scripts its reference inputs carry.
1684
+ */
1685
+ const leastFeeOf = (plan, parameters) => {
1686
+ const references = plan.referenceInputs ?? [];
1687
+ return BigInt(parameters.minFeeConstant) + BigInt(parameters.minFeeCoefficient) * BigInt(plan.scripts.reduce((bytes, script) => bytes + script.bytes.length / 2, 0)) + referenceScriptFee(referenceScriptSize(references.map(([txIn]) => txIn), references), parameters);
1688
+ };
1689
+ /**
1690
+ * The fee a plan is expected to balance at before evaluation, less the
1691
+ * bytes of its body beyond its scripts: its least fee and its seed
1692
+ * execution units at the protocol's prices. Fund selection aims at it; a
1693
+ * selection that falls short gains the next fund UTxO when balancing
1694
+ * fails, before any evaluation.
1695
+ */
1696
+ const plannedFeeOf = (plan, parameters) => leastFeeOf(plan, parameters) + executionFeeOf(estimateWithinLimit(plan.executionEstimate, parameters.maxExecutionUnitsPerTransaction), parameters);
1697
+ const quantityOf = (utxo, assetId) => utxo[1].value.assets?.get(assetId) ?? 0n;
1698
+ const byCoinsDescending = (a, b) => a[1].value.coins === b[1].value.coins ? 0 : a[1].value.coins > b[1].value.coins ? -1 : 1;
1699
+ /**
1700
+ * Large-First selection over fund UTxOs: first the UTxOs holding most of
1701
+ * each requested asset, then the largest by lovelace, with `reserves` after
1702
+ * every fund. When the UTxOs cannot cover the target, everything is
1703
+ * selected, `covers` is false and the build decides: the target holds only
1704
+ * an estimate of the fee.
1705
+ */
1706
+ const selectFunds = (funds, target, reserves = []) => {
1707
+ const remaining = [...[...funds].sort(byCoinsDescending), ...[...reserves].sort(byCoinsDescending)];
1708
+ const selected = [];
1709
+ const take = (utxo) => {
1710
+ remaining.splice(remaining.indexOf(utxo), 1);
1711
+ selected.push(utxo);
1712
+ };
1713
+ for (const [assetId, quantity] of target.assets ?? []) {
1714
+ let covered = selected.reduce((sum, utxo) => sum + quantityOf(utxo, assetId), 0n);
1715
+ while (covered < quantity) {
1716
+ const richest = remaining.reduce((best, utxo) => !best || quantityOf(utxo, assetId) > quantityOf(best, assetId) ? utxo : best, void 0);
1717
+ if (!richest || quantityOf(richest, assetId) === 0n) return {
1718
+ selected: [...selected, ...remaining],
1719
+ remaining: [],
1720
+ covers: false
1721
+ };
1722
+ take(richest);
1723
+ covered += quantityOf(richest, assetId);
1724
+ }
1725
+ }
1726
+ let lovelace = selected.reduce((sum, [, output]) => sum + output.value.coins, 0n);
1727
+ while (lovelace < target.coins && remaining.length > 0) {
1728
+ const [next] = remaining;
1729
+ take(next);
1730
+ lovelace += next[1].value.coins;
1731
+ }
1732
+ return {
1733
+ selected,
1734
+ remaining,
1735
+ covers: lovelace >= target.coins
1736
+ };
1737
+ };
1738
+ /** The sum of outputs' values. */
1739
+ const totalValueOf = (outputs) => {
1740
+ const assets = /* @__PURE__ */ new Map();
1741
+ let coins = 0n;
1742
+ for (const { value } of outputs) {
1743
+ coins += value.coins;
1744
+ for (const [assetId, quantity] of value.assets ?? []) assets.set(assetId, (assets.get(assetId) ?? 0n) + quantity);
1745
+ }
1746
+ return {
1747
+ coins,
1748
+ assets
1749
+ };
1750
+ };
1751
+ /**
1752
+ * The funds can no longer pay for the transaction; one more fund UTxO may.
1753
+ * Once none is left, the build fails with `exhausted`, or with
1754
+ * `insufficient-funds` when it is absent.
1755
+ */
1756
+ var FundShortfall = class extends Error {
1757
+ constructor(message, exhausted) {
1758
+ super(message);
1759
+ this.exhausted = exhausted;
1760
+ this.name = "FundShortfall";
1761
+ }
1762
+ };
1763
+ /**
1764
+ * The evaluated transaction needs a larger fee than its plan balanced at:
1765
+ * the scripts measured above the execution estimate. Building again seeded
1766
+ * from `reseed` funds it.
1767
+ */
1768
+ var ExecutionEstimateShortfall = class extends Error {
1769
+ constructor(reseed) {
1770
+ super("The scripts measure above the execution estimate");
1771
+ this.reseed = reseed;
1772
+ this.name = "ExecutionEstimateShortfall";
1773
+ }
1774
+ };
1775
+ /**
1776
+ * The transaction needs a fee above the most it may be balanced at. `fee`
1777
+ * is the fee it needs, or, when even the fee of its body without execution
1778
+ * is above that most, that fee. `evaluatedRedeemers` holds the budgets the
1779
+ * fee was priced at when an evaluation priced it.
1780
+ */
1781
+ var MaxBalancedFeeExceeded = class extends Error {
1782
+ constructor(fee, evaluatedRedeemers = []) {
1783
+ super(`The transaction needs a fee of ${fee} lovelace, above the most it may be balanced at`);
1784
+ this.fee = fee;
1785
+ this.evaluatedRedeemers = evaluatedRedeemers;
1786
+ this.name = "MaxBalancedFeeExceeded";
1787
+ }
1788
+ };
1789
+ /** The builder balanced a draft above the most fee allowed; nothing evaluated it. */
1790
+ var DraftFeeAboveMax = class extends Error {
1791
+ constructor(fee) {
1792
+ super(`The draft is balanced at ${fee} lovelace, above the most allowed`);
1793
+ this.fee = fee;
1794
+ this.name = "DraftFeeAboveMax";
1795
+ }
1796
+ };
1797
+ /** Stops a draft balanced above `maxFee` before the provider evaluates it. */
1798
+ const withMaxDraftFee = (evaluator, maxFee) => ({ evaluate: async (tx, resolvedInputs) => {
1799
+ if (tx.body.fee > maxFee) throw new DraftFeeAboveMax(tx.body.fee);
1800
+ return evaluator.evaluate(tx, resolvedInputs);
1801
+ } });
1802
+ /**
1803
+ * `seed` scaled so that its execution costs at most `fee` at the protocol's
1804
+ * prices, which are linear in the units.
1805
+ */
1806
+ const seedPricedAt = (seed, fee, parameters) => {
1807
+ const seedFee = executionFeeOf(seed, parameters);
1808
+ if (seedFee === 0n) return seed;
1809
+ const scale = Number(fee) / Number(seedFee);
1810
+ return {
1811
+ memory: Math.floor(seed.memory * scale),
1812
+ steps: Math.floor(seed.steps * scale)
1813
+ };
1814
+ };
1815
+ /**
1816
+ * Seed changes that keep a draft within the most fee it may be balanced
1817
+ * at. A draft lands within a few bytes of the fee its seed aims at, so one
1818
+ * change usually suffices.
1819
+ */
1820
+ const MAX_DRAFT_FEE_ROUNDS = 4;
1821
+ /**
1822
+ * The plain change output a collateral-mode build returns to the account:
1823
+ * the largest output at `address` after the planned ones, without a datum.
1824
+ * -1 when there is none.
1825
+ */
1826
+ const changeIndexOf = (outputs, address, plannedCount) => {
1827
+ let changeIndex = -1;
1828
+ for (const [index, output] of outputs.entries()) if (index >= plannedCount && output.address === address && output.datum === void 0 && output.datumHash === void 0 && (changeIndex < 0 || output.value.coins > outputs[changeIndex].value.coins)) changeIndex = index;
1829
+ return changeIndex;
1830
+ };
1831
+ const withBody = (transaction, fields) => {
1832
+ const core = transaction.toCore();
1833
+ return Transaction.fromCore({
1834
+ ...core,
1835
+ body: {
1836
+ ...core.body,
1837
+ ...fields
1838
+ }
1839
+ });
1840
+ };
1841
+ const hasGrown = (rewritten, transaction) => rewritten.body().toCbor().length > transaction.body().toCbor().length;
1842
+ const grewBody = () => failCustodyBuild({
1843
+ code: "build-failed",
1844
+ message: "Moving lovelace between outputs grew the body"
1845
+ });
1846
+ /**
1847
+ * The transaction with `outputs` in place of its own. The fee was priced
1848
+ * for the body's size, so the body must not grow, as it would when a coin
1849
+ * crosses a CBOR width boundary. A smaller body keeps a fee that also pays
1850
+ * for the bytes it dropped.
1851
+ */
1852
+ const withOutputs = (transaction, outputs) => {
1853
+ const rewritten = withBody(transaction, { outputs });
1854
+ return hasGrown(rewritten, transaction) ? grewBody() : rewritten;
1855
+ };
1856
+ /** `output` holding `coins` lovelace. */
1857
+ const withCoins = (output, coins) => ({
1858
+ ...output,
1859
+ value: {
1860
+ ...output.value,
1861
+ coins
1862
+ }
1863
+ });
1864
+ /**
1865
+ * The transaction with `outputs` in place of its own and its fee raised by
1866
+ * `excess`. The declared collateral rises with the fee, taken from the
1867
+ * collateral return, which must keep its minimum ADA.
1868
+ */
1869
+ const withExcessInFee = (transaction, { outputs, excess }, { collateralPercentage, coinsPerUtxoByte }) => {
1870
+ const { fee, totalCollateral, collateralReturn } = transaction.body().toCore();
1871
+ const raised = fee + excess;
1872
+ const required = (raised * BigInt(collateralPercentage) + 99n) / 100n;
1873
+ let collateral = {};
1874
+ if (totalCollateral !== void 0 && totalCollateral < required) {
1875
+ const returned = collateralReturn && withCoins(collateralReturn, collateralReturn.value.coins - (required - totalCollateral));
1876
+ if (!returned || returned.value.coins < minAdaRequired(returned, BigInt(coinsPerUtxoByte))) return failCustodyBuild({
1877
+ code: "collateral-insufficient",
1878
+ message: `The collateral cannot cover a fee of ${raised} lovelace`
1879
+ });
1880
+ collateral = {
1881
+ totalCollateral: required,
1882
+ collateralReturn: returned
1883
+ };
1884
+ }
1885
+ const rewritten = withBody(transaction, {
1886
+ outputs,
1887
+ fee: raised,
1888
+ ...collateral
1889
+ });
1890
+ return hasGrown(rewritten, transaction) ? grewBody() : rewritten;
1891
+ };
1892
+ /**
1893
+ * Moves the lovelace the builder's fee correction credited to a planned
1894
+ * output back to the change. The correction refunds the largest output at
1895
+ * the change address, and in collateral mode the planned account outputs
1896
+ * share that address: a control output would lock the lovelace for the
1897
+ * account's life, and a grant output must hold what was planned.
1898
+ *
1899
+ * The change takes it unless that grows the body the fee was priced for,
1900
+ * which happens only when its lovelace crosses 2^32; the fee then keeps
1901
+ * the saving instead.
1902
+ *
1903
+ * @throws `FundShortfall` when no change output exists to take it
1904
+ */
1905
+ const restorePlannedOutputs = (transaction, plan, parameters) => {
1906
+ const outputs = [...transaction.body().toCore().outputs];
1907
+ let excess = 0n;
1908
+ for (const [index, planned] of plan.outputs.entries()) {
1909
+ const credited = outputs[index].value.coins - planned.value.coins;
1910
+ if (credited > 0n) {
1911
+ excess += credited;
1912
+ outputs[index] = withCoins(outputs[index], planned.value.coins);
1913
+ }
1914
+ }
1915
+ if (excess === 0n) return transaction;
1916
+ const changeIndex = changeIndexOf(outputs, plan.changeAddress, plan.outputs.length);
1917
+ if (changeIndex < 0) throw new FundShortfall("No change output to return the fee saving to");
1918
+ const credited = [...outputs];
1919
+ credited[changeIndex] = withCoins(outputs[changeIndex], outputs[changeIndex].value.coins + excess);
1920
+ const rewritten = withBody(transaction, { outputs: credited });
1921
+ return hasGrown(rewritten, transaction) ? withExcessInFee(transaction, {
1922
+ outputs,
1923
+ excess
1924
+ }, parameters) : rewritten;
1925
+ };
1926
+ /**
1927
+ * The lovelace the first output of a plan whose first output pays the fee
1928
+ * must hold to balance at the fee the builder settled on.
1929
+ */
1930
+ var FirstOutputCoinsMissed = class extends Error {
1931
+ constructor(coins) {
1932
+ super(`The first output must hold ${coins} lovelace to balance`);
1933
+ this.coins = coins;
1934
+ this.name = "FirstOutputCoinsMissed";
1935
+ }
1936
+ };
1937
+ /**
1938
+ * Coin selection for a plan whose first output pays the fee: it spends
1939
+ * exactly the preselected inputs and returns no change. The balancer prices
1940
+ * each target and asks again until the fee settles; when the settled target
1941
+ * still misses the inputs, the build stops with the first output's lovelace
1942
+ * that meets it.
1943
+ */
1944
+ const firstOutputPaysSelector = (firstOutputCoins) => {
1945
+ let previousTarget;
1946
+ return { select: ({ preSelectedUtxo = [], targetValue }) => {
1947
+ const surplus = preSelectedUtxo.reduce((total, [, output]) => total + output.value.coins, 0n) - targetValue.coins;
1948
+ if (surplus !== 0n && previousTarget === targetValue.coins) throw new FirstOutputCoinsMissed(firstOutputCoins + surplus);
1949
+ previousTarget = targetValue.coins;
1950
+ return {
1951
+ selection: [...preSelectedUtxo],
1952
+ remaining: [],
1953
+ changeOutputs: []
1954
+ };
1955
+ } };
1956
+ };
1957
+ /**
1958
+ * Builds and balances one plan with the shared `TransactionBuilder`. Every
1959
+ * input is preselected, so balancing never adds an input without its
1960
+ * redeemer; the collateral is the sponsor's shared UTxO, returned to the
1961
+ * sponsor with total collateral set.
1962
+ *
1963
+ * @throws `FundShortfall` when the inputs cannot pay for the transaction
1964
+ * @throws `EvaluatedFeeShortfallError` when the scripts measure above `seed`
1965
+ * by more than the balanced fee covers
1966
+ */
1967
+ const buildPlan = async (plan, chain, { evaluator, coinSelector, seed }) => {
1968
+ const parameters = chain.protocolParameters;
1969
+ const referenceInputs = plan.referenceInputs ?? [];
1970
+ const builder = new TransactionBuilder(chain.chainId.networkMagic, parameters).setChangeAddress(plan.changeAddress).useCoinSelector(coinSelector).setCollateralUtxos([plan.collateral.utxo]).setCollateralChangeAddress(plan.collateral.returnAddress).setPlutusContext({
1971
+ costModels: parameters.costModels,
1972
+ txEvaluator: evaluator,
1973
+ inputResolver: createInputResolver([...plan.inputs.map(({ utxo }) => utxo), plan.collateral.utxo]),
1974
+ executionUnitsEstimate: seed
1975
+ }).setValidityInterval({
1976
+ ...plan.invalidBefore !== void 0 && { invalidBefore: plan.invalidBefore },
1977
+ invalidHereafter: plan.invalidHereafter
1978
+ }).addRequiredSigner(plan.requiredSigner);
1979
+ for (const { utxo, redeemer } of plan.inputs) builder.addInput(utxo, redeemer ? { redeemer } : void 0);
1980
+ for (const utxo of referenceInputs) builder.addReferenceInput(utxo);
1981
+ for (const output of plan.outputs) builder.addOutput(output);
1982
+ for (const script of plan.scripts) builder.attachScript(script);
1983
+ if (plan.mint) builder.mintAssets(plan.mint.assets, plan.mint.redeemer);
1984
+ if (plan.registration) {
1985
+ const { stakeCredential, deposit, redeemer } = plan.registration;
1986
+ builder.addNewStakeAddressCertificate(stakeCredential, deposit, { redeemer });
1987
+ }
1988
+ if (plan.delegation) {
1989
+ const { poolId, stakeCredential, redeemer } = plan.delegation;
1990
+ builder.addStakeDelegationCertificate(poolId, stakeCredential, { redeemer });
1991
+ }
1992
+ for (const { rewardAccount, quantity, redeemer } of plan.withdrawals ?? []) builder.addScriptWithdrawal(rewardAccount, quantity, redeemer);
1993
+ for (const [label, metadatum] of plan.metadata ?? []) builder.setMetadata(label, metadatum);
1994
+ try {
1995
+ return await builder.build();
1996
+ } catch (error) {
1997
+ if (error instanceof InputSelectionError) throw new FundShortfall(error.message);
1998
+ if (error instanceof InsufficientCollateralError) return failCustodyBuild({
1999
+ code: "collateral-insufficient",
2000
+ message: error.message
2001
+ });
2002
+ throw error;
2003
+ }
2004
+ };
2005
+ /**
2006
+ * Rounds of a plan whose first output pays the fee. The first round finds
2007
+ * the fee the builder settles on, the second lands on it; a third covers a
2008
+ * coin that crossed a CBOR width between them.
2009
+ */
2010
+ const MAX_FIRST_OUTPUT_ROUNDS = 3;
2011
+ /**
2012
+ * Builds a plan whose first output pays the fee. The builder balances at a
2013
+ * fee priced for the seed execution units, so the first output is set to
2014
+ * what the inputs hold beyond that fee and the other outputs; once
2015
+ * evaluation lowers the fee, the builder refunds the saving to it, the
2016
+ * largest output at the change address.
2017
+ *
2018
+ * @throws `FundShortfall` when the inputs cannot pay the fee the builder
2019
+ * balances at
2020
+ */
2021
+ const buildFeePaidByFirstOutput = async (plan, chain, { evaluator, seed }) => {
2022
+ const [first, ...rest] = plan.outputs;
2023
+ let coins = first.value.coins;
2024
+ for (let round = 1;; round++) try {
2025
+ return await buildPlan({
2026
+ ...plan,
2027
+ outputs: [withCoins(first, coins), ...rest]
2028
+ }, chain, {
2029
+ evaluator,
2030
+ coinSelector: firstOutputPaysSelector(coins),
2031
+ seed
2032
+ });
2033
+ } catch (error) {
2034
+ if (!(error instanceof FirstOutputCoinsMissed) || round >= MAX_FIRST_OUTPUT_ROUNDS) throw error;
2035
+ if (error.coins < 0n) throw new FundShortfall("The first output cannot pay the fee the builder balances at");
2036
+ coins = error.coins;
2037
+ }
2038
+ };
2039
+ /** How many redeemers a plan's transaction carries. */
2040
+ const redeemerCountOf = (plan) => plan.inputs.filter(({ redeemer }) => redeemer !== void 0).length + [
2041
+ plan.mint,
2042
+ plan.registration,
2043
+ plan.delegation
2044
+ ].filter((part) => part !== void 0).length + (plan.withdrawals?.length ?? 0);
2045
+ /**
2046
+ * Builds a plan, adding the next fund UTxO each time the selected ones
2047
+ * cannot pay for it. Once none is left, it refuses with the shortfall's
2048
+ * reason, `insufficient-funds` by default. Planned outputs keep their
2049
+ * lovelace unless the first output pays the fee.
2050
+ *
2051
+ * With `maxBalancedFee`, a draft balanced above it gets a seed priced at
2052
+ * that much less, and an evaluated transaction that needs more than its
2053
+ * draft's fee gets a seed priced at what it needs, while that fits.
2054
+ *
2055
+ * @throws `ExecutionEstimateShortfall` when the scripts measure above the
2056
+ * seed by more than the balanced fee covers
2057
+ * @throws `MaxBalancedFeeExceeded` when the transaction needs a fee above
2058
+ * `maxBalancedFee`
2059
+ */
2060
+ const assembleWithFunds = async ({ chain, funds, plan, evaluator, reseed, checkFunds, settle, maxBalancedFee }) => {
2061
+ const parameters = chain.protocolParameters;
2062
+ let { selected, remaining } = funds;
2063
+ let seedFeeLimit;
2064
+ let draftFeeRounds = 0;
2065
+ for (;;) {
2066
+ checkFunds?.(selected);
2067
+ const planned = plan(selected);
2068
+ const fullSeed = reseed ? reseededExecutionUnits(reseed, redeemerCountOf(planned)) : estimateWithinLimit(planned.executionEstimate, parameters.maxExecutionUnitsPerTransaction);
2069
+ const seed = seedFeeLimit === void 0 ? fullSeed : seedPricedAt(fullSeed, seedFeeLimit, parameters);
2070
+ const planEvaluator = maxBalancedFee === void 0 ? evaluator(selected) : withMaxDraftFee(evaluator(selected), maxBalancedFee);
2071
+ try {
2072
+ const built = planned.isFeePaidByFirstOutput ? await buildFeePaidByFirstOutput(planned, chain, {
2073
+ evaluator: planEvaluator,
2074
+ seed
2075
+ }) : restorePlannedOutputs(await buildPlan(planned, chain, {
2076
+ evaluator: planEvaluator,
2077
+ coinSelector: new LargeFirstCoinSelector(),
2078
+ seed
2079
+ }), planned, parameters);
2080
+ return {
2081
+ transaction: settle ? settle(built) : built,
2082
+ funds: {
2083
+ selected,
2084
+ remaining
2085
+ }
2086
+ };
2087
+ } catch (error) {
2088
+ if (error instanceof DraftFeeAboveMax && maxBalancedFee !== void 0) {
2089
+ const seedFee = executionFeeOf(seed, parameters);
2090
+ const limit = seedFee - (error.fee - maxBalancedFee);
2091
+ if (limit < 0n) throw new MaxBalancedFeeExceeded(error.fee - seedFee);
2092
+ if (++draftFeeRounds > MAX_DRAFT_FEE_ROUNDS) throw new MaxBalancedFeeExceeded(error.fee);
2093
+ seedFeeLimit = limit;
2094
+ continue;
2095
+ }
2096
+ if (error instanceof EvaluatedFeeShortfallError) {
2097
+ if (maxBalancedFee !== void 0 && error.requiredFee > maxBalancedFee) throw new MaxBalancedFeeExceeded(error.requiredFee, error.evaluatedRedeemers);
2098
+ if (seedFeeLimit !== void 0 && ++draftFeeRounds <= MAX_DRAFT_FEE_ROUNDS) {
2099
+ seedFeeLimit = executionFeeOf(seed, parameters) + error.requiredFee - error.balancedFee;
2100
+ continue;
2101
+ }
2102
+ throw new ExecutionEstimateShortfall(custodyReseedOf(error.evaluatedRedeemers.map(({ executionUnits }) => executionUnits), seed));
2103
+ }
2104
+ if (!(error instanceof FundShortfall)) throw error;
2105
+ if (remaining.length === 0) return failCustodyBuild(error.exhausted ?? {
2106
+ code: "insufficient-funds",
2107
+ message: error.message
2108
+ });
2109
+ selected = [...selected, remaining[0]];
2110
+ remaining = remaining.slice(1);
2111
+ }
2112
+ }
2113
+ };
2114
+ const EVALUATION_MARGIN_PERCENT = 10n;
2115
+ /**
2116
+ * Pads a measured budget. The fee correction after evaluation changes the
2117
+ * body the scripts see, so the final transaction is not the one measured.
2118
+ */
2119
+ const withEvaluationMargin = ({ memory, steps }) => ({
2120
+ memory: Number((BigInt(memory) * (100n + EVALUATION_MARGIN_PERCENT) + 99n) / 100n),
2121
+ steps: Number((BigInt(steps) * (100n + EVALUATION_MARGIN_PERCENT) + 99n) / 100n)
2122
+ });
2123
+ const atLeast = (budget, floor) => floor ? {
2124
+ memory: Math.max(budget.memory, floor.memory),
2125
+ steps: Math.max(budget.steps, floor.steps)
2126
+ } : budget;
2127
+ const redeemerKey = ({ purpose, index }) => `${purpose}:${index}`;
2128
+ /** A redeemer the provider did not evaluate fails, as it does for the sponsor service. */
2129
+ const unmeasured = ({ purpose, index }) => failCustodyBuild({
2130
+ code: "script-evaluation-failed",
2131
+ message: `The provider did not evaluate redeemer ${purpose}#${index}`
2132
+ });
2133
+ /**
2134
+ * Every redeemer whose declared budget is below what the provider measures,
2135
+ * with the measurement.
2136
+ */
2137
+ const findUnderDeclaredBudgets = (transaction, measured) => {
2138
+ return (transaction.witnessSet().redeemers()?.toCore() ?? []).flatMap(({ purpose, index, executionUnits }) => {
2139
+ const measurement = measured.find((entry) => entry.purpose === purpose && entry.index === index) ?? unmeasured({
2140
+ purpose,
2141
+ index
2142
+ });
2143
+ return measurement.budget.memory > executionUnits.memory || measurement.budget.steps > executionUnits.steps ? [measurement] : [];
2144
+ });
2145
+ };
2146
+ /**
2147
+ * The outputs a transaction's inputs, reference inputs and collateral
2148
+ * resolve to, in that order, from the UTxOs the build chose them from.
2149
+ */
2150
+ const resolvedInputsOf = (transaction, candidates) => {
2151
+ const body = transaction.body().toCore();
2152
+ const byOutpoint = new Map(candidates.map((utxo) => [outpointKey$1(utxo[0]), utxo]));
2153
+ return [
2154
+ ...body.inputs,
2155
+ ...body.referenceInputs ?? [],
2156
+ ...body.collaterals ?? []
2157
+ ].map((input) => byOutpoint.get(outpointKey$1(input))).filter((utxo) => utxo !== void 0);
2158
+ };
2159
+ /** A lovelace amount whose CBOR width every change output reaches. */
2160
+ const CHANGE_COIN_WIDTH_PROBE = 1000000n;
2161
+ /** The minimum ADA of a lovelace-only change output at an address. */
2162
+ const changeReserveAt = (address, coinsPerUtxoByte) => minAdaRequired({
2163
+ address,
2164
+ value: { coins: CHANGE_COIN_WIDTH_PROBE }
2165
+ }, BigInt(coinsPerUtxoByte));
2166
+ const scriptCredentialOf = (hash) => ({
2167
+ type: CredentialType.ScriptHash,
2168
+ hash
2169
+ });
2170
+ //#endregion
2171
+ //#region ../../packages/module/blockchain-cardano/src/store/night-designation/unwrap-provider-result.ts
2172
+ /**
2173
+ * Take the single value a `CardanoProvider` call emits, turning a failed
2174
+ * `Result` into a thrown error so the designation side-effects can gather
2175
+ * their reads and catch every failure in one place. Transient failures are
2176
+ * retried first (ADR 15 tier-1).
2177
+ *
2178
+ * Observable rather than `Promise`: `retryBackoff`'s waits are only virtual
2179
+ * while they stay on a scheduler the `TestScheduler` can drive, and a promise
2180
+ * anywhere in a side-effect's pipeline forfeits marble testing for the whole
2181
+ * of it (docs/rxjs-guidelines.md). A promise-shaped caller wraps this in
2182
+ * `firstValueFrom`; a side-effect composes it.
2183
+ *
2184
+ * Takes a request FACTORY, not an observable: the provider builds its
2185
+ * observable over an already-in-flight promise, which replays its settled
2186
+ * value to every later subscriber — so a retry that re-subscribed would
2187
+ * replay the same failure instead of reaching the network. The factory is a
2188
+ * caller-side stand-in for a cold provider method; drop it, and take the
2189
+ * observable directly, once every method this calls mints its promise on
2190
+ * subscribe.
2191
+ *
2192
+ * A provider that rejects with a non-`Error` is wrapped rather than thrown
2193
+ * as-is, so the catch site can always read `.name` / `.message`.
2194
+ */
2195
+ const unwrapProviderResult = (request) => defer(request).pipe(map((result) => {
2196
+ if (!result.isOk()) throw result.error instanceof Error ? result.error : new Error(String(result.error));
2197
+ return result.value;
2198
+ }), retryBackoff(PROVIDER_REQUEST_RETRY_CONFIG), first());
2199
+ //#endregion
2200
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/custody-chain.ts
2201
+ /**
2202
+ * The slot clock of the era history. A time or slot before the first era
2203
+ * is read in the first era: custody transactions only name slots near the
2204
+ * present.
2205
+ */
2206
+ const createSlotClock = (eraSummaries) => {
2207
+ const eraOf = (isStarted) => eraSummaries.filter(isStarted).at(-1) ?? eraSummaries[0];
2208
+ return {
2209
+ slotAt: (posixMs) => {
2210
+ const era = eraOf(({ start }) => start.time.getTime() <= posixMs);
2211
+ return Slot(era.start.slot + Math.floor((posixMs - era.start.time.getTime()) / era.parameters.slotLength));
2212
+ },
2213
+ slotStart: (slot) => {
2214
+ const era = eraOf(({ start }) => start.slot <= slot);
2215
+ return era.start.time.getTime() + (slot - era.start.slot) * era.parameters.slotLength;
2216
+ }
2217
+ };
2218
+ };
2219
+ /**
2220
+ * One provider read, retried while transient. A failure stops the build as
2221
+ * `unavailable`, since nothing about the custody account is known yet.
2222
+ */
2223
+ const readFromProvider = async (what, request) => {
2224
+ try {
2225
+ return await firstValueFrom(unwrapProviderResult(request));
2226
+ } catch (error) {
2227
+ return failCustodyBuild({
2228
+ code: "unavailable",
2229
+ message: `${what}: ${messageOf(error)}`
2230
+ });
2231
+ }
2232
+ };
2233
+ const loadCustodyChain = async ({ chainId, eraSummaries, provider, now }) => {
2234
+ if (!chainId) return failCustodyBuild({
2235
+ code: "unavailable",
2236
+ message: "Cardano chain id not available"
2237
+ });
2238
+ if (!eraSummaries || eraSummaries.length === 0) return failCustodyBuild({
2239
+ code: "unavailable",
2240
+ message: "Cardano era summaries not available"
2241
+ });
2242
+ return {
2243
+ chainId,
2244
+ protocolParameters: await readFromProvider("Cardano protocol parameters", () => provider.getProtocolParameters({ chainId })),
2245
+ clock: createSlotClock(eraSummaries),
2246
+ now,
2247
+ provider
2248
+ };
2249
+ };
2250
+ /**
2251
+ * What the provider measures every redeemer of a transaction at. There is
2252
+ * no fallback budget: a script that fails is a build that fails, with the
2253
+ * provider's message. A transient failure, such as a rate limit or a
2254
+ * dropped connection, is retried like every other read and then reported
2255
+ * as `unavailable`, since it says nothing about the scripts.
2256
+ */
2257
+ const evaluateCustodyTransaction = async (transaction, { provider, chainId }) => {
2258
+ const tx = transaction.toCbor();
2259
+ try {
2260
+ return await firstValueFrom(unwrapProviderResult(() => provider.evaluateTx({ tx }, { chainId })));
2261
+ } catch (error) {
2262
+ return failCustodyBuild(isRetriableError(error) ? {
2263
+ code: "unavailable",
2264
+ message: `Transaction evaluation: ${messageOf(error)}`
2265
+ } : {
2266
+ code: "script-evaluation-failed",
2267
+ message: messageOf(error)
2268
+ });
2269
+ }
2270
+ };
2271
+ /**
2272
+ * The first slot that starts strictly after `posixMs`. A sweep of a grant
2273
+ * dead by expiry alone sets it as the lower validity bound, because the
2274
+ * validator needs the start of that bound to be after the expiry.
2275
+ */
2276
+ const firstSlotStartingAfter = (posixMs, { clock }) => Slot(clock.slotAt(Number(posixMs)) + 1);
2277
+ /**
2278
+ * The upper validity bound a funding window allows: the slot holding
2279
+ * `validUntil`, which starts no later than it. The window must leave a slot
2280
+ * after the current one.
2281
+ */
2282
+ const validitySlotFor = (validUntil, { clock, now }) => {
2283
+ const slot = Number.isFinite(validUntil) ? clock.slotAt(validUntil) : 0;
2284
+ return slot > clock.slotAt(now) ? Slot(slot) : failCustodyBuild({
2285
+ code: "validity-window",
2286
+ validUntil
2287
+ });
2288
+ };
2289
+ //#endregion
2290
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/custody-creator-index.ts
2291
+ /**
2292
+ * Reads what the chain holds at one creator index of one contract version:
2293
+ * whether the stake credential of the creator key is registered and, when
2294
+ * it is, whether the account address holds the state NFT. A registered
2295
+ * credential without the NFT was registered by someone else first, which
2296
+ * blocks creation at that index forever.
2297
+ */
2298
+ const readCustodyCreatorIndex = async ({ contractVersion, deviceAccountPublicKey, creatorIndex }, chain) => {
2299
+ const key = await deriveCustodyDeviceKey(deviceAccountPublicKey, creatorIndex);
2300
+ if (!key.ok) return failCustodyBuild({
2301
+ code: "invalid-operation",
2302
+ reason: "invalid-device-key",
2303
+ message: `Cannot derive custody device key ${creatorIndex}: ${key.error.code}`
2304
+ });
2305
+ const identity = deriveCustodyAccountIdentity({
2306
+ contractVersion,
2307
+ creatorKeyHash: key.value.keyHash,
2308
+ chainId: chain.chainId
2309
+ });
2310
+ if (!identity.ok) return failCustodyBuild({
2311
+ code: "unavailable",
2312
+ message: `Cannot derive a custody account on this chain: ${identity.error.code}`
2313
+ });
2314
+ const { rewardAccount, address, stateNftAssetId } = identity.value;
2315
+ const { isRegistered } = await readFromProvider("Custody reward account", () => chain.provider.getRewardAccountInfo({ rewardAccount: CardanoRewardAccount(rewardAccount) }, { chainId: chain.chainId }));
2316
+ if (!isRegistered) return {
2317
+ status: "unregistered",
2318
+ key: key.value,
2319
+ identity: identity.value
2320
+ };
2321
+ return (await readFromProvider("Custody account UTxOs", () => chain.provider.getUtxosAtAddress({ address: CardanoPaymentAddress(address) }, { chainId: chain.chainId }))).some(([, output]) => output.value.assets?.has(stateNftAssetId)) ? {
2322
+ status: "account",
2323
+ record: createCustodyAccountRecord(identity.value)
2324
+ } : { status: "pre-registered" };
2325
+ };
2326
+ //#endregion
2327
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/custody-evaluation.ts
2328
+ /** Rebuilds allowed after the final transaction measures above its budgets. */
2329
+ const MAX_EVALUATION_ROUNDS = 3;
2330
+ /**
2331
+ * Prices every redeemer at what the provider measures on the draft, with a
2332
+ * margin, and at least at a floor an earlier round measured on final bytes.
2333
+ */
2334
+ const createProviderEvaluator = (chain, floors) => ({ evaluate: async (tx) => {
2335
+ const measured = await evaluateCustodyTransaction(Transaction.fromCore(tx), chain);
2336
+ return (tx.witness.redeemers ?? []).map(({ purpose, index }) => {
2337
+ const measurement = measured.find((entry) => entry.purpose === purpose && entry.index === index);
2338
+ if (!measurement) return unmeasured({
2339
+ purpose,
2340
+ index
2341
+ });
2342
+ return {
2343
+ purpose,
2344
+ index,
2345
+ budget: atLeast(withEvaluationMargin(measurement.budget), floors.get(redeemerKey({
2346
+ purpose,
2347
+ index
2348
+ })))
2349
+ };
2350
+ });
2351
+ } });
2352
+ /**
2353
+ * Builds a transaction whose budgets come from provider evaluation, then
2354
+ * evaluates the final bytes. Every redeemer must declare at least what the
2355
+ * final transaction measures, which is what the sponsor service checks
2356
+ * before it signs; a shortfall raises the budget and rebuilds.
2357
+ *
2358
+ * Balancing prices the fee at the plan's execution estimate, so the funds,
2359
+ * or the control output that pays, need to cover little more than the real
2360
+ * fee. When the scripts measure above the estimate by more than the
2361
+ * balanced fee covers, the plan is built once more, seeded at the
2362
+ * measurement plus a margin: a second selection round, which may add fund
2363
+ * UTxOs.
2364
+ *
2365
+ * @throws `MaxBalancedFeeExceeded` when `maxBalancedFee` is set and the
2366
+ * transaction needs a fee above it
2367
+ */
2368
+ const buildWithEvaluatedBudgets = async (params) => {
2369
+ let floors = /* @__PURE__ */ new Map();
2370
+ let funds = params.funds;
2371
+ let reseed;
2372
+ for (let round = 1;;) {
2373
+ let built;
2374
+ try {
2375
+ built = await assembleWithFunds({
2376
+ ...params,
2377
+ funds,
2378
+ reseed,
2379
+ evaluator: () => createProviderEvaluator(params.chain, floors)
2380
+ });
2381
+ } catch (error) {
2382
+ if (!(error instanceof ExecutionEstimateShortfall)) throw error;
2383
+ if (reseed) return failCustodyBuild({
2384
+ code: "script-evaluation-failed",
2385
+ message: "The scripts measure above the execution estimate after a reseed"
2386
+ });
2387
+ reseed = error.reseed;
2388
+ continue;
2389
+ }
2390
+ funds = built.funds;
2391
+ const underDeclared = findUnderDeclaredBudgets(built.transaction, await evaluateCustodyTransaction(built.transaction, params.chain));
2392
+ if (underDeclared.length === 0) return built.transaction;
2393
+ if (round >= MAX_EVALUATION_ROUNDS) return failCustodyBuild({
2394
+ code: "script-evaluation-failed",
2395
+ message: `Declared budgets stay below the measured ones after ${round} rounds`
2396
+ });
2397
+ round++;
2398
+ floors = new Map([...floors, ...underDeclared.map((entry) => [redeemerKey(entry), withEvaluationMargin(entry.budget)])]);
2399
+ }
2400
+ };
2401
+ //#endregion
2402
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/custody-scripts.ts
2403
+ const scriptHashOf = (script) => Script.fromCore(script).hash();
2404
+ /**
2405
+ * The catalogue pins each parked UTxO's address and lovelace with its
2406
+ * outref, so the builder prices its script without a chain lookup; the
2407
+ * provider's evaluation of every build resolves the outref on chain, so a
2408
+ * UTxO the chain no longer holds fails the build before anyone signs.
2409
+ */
2410
+ const custodyScriptWitnesses = (version, { chainId }, scripts) => {
2411
+ const parked = new Map(custodyReferenceScriptUtxos(version, chainId.networkMagic).map((utxo) => [scriptHashOf(utxo[1].scriptReference), utxo]));
2412
+ const attached = [];
2413
+ const referenced = [];
2414
+ for (const script of scripts) {
2415
+ const reference = parked.get(scriptHashOf(script));
2416
+ if (reference) referenced.push(reference);
2417
+ else attached.push(script);
2418
+ }
2419
+ return {
2420
+ attached,
2421
+ referenced
2422
+ };
2423
+ };
2424
+ const LOGIC_RUN_REDEEMER = encodeCustodyLogicRedeemer();
2425
+ /**
2426
+ * Reads the logic's reward account once per transaction, so every build
2427
+ * round of it draws the same amount. A credential that is not registered
2428
+ * fails the build as `logic-not-registered`: the ledger would refuse the
2429
+ * withdrawal only at submission, after the sponsor and the device signed.
2430
+ * Registration is read, not delegation: the logic delegates to no pool.
2431
+ */
2432
+ const readCustodyLogicRun = async (logic, { chainId, provider }) => {
2433
+ const rewardAccount = RewardAccount.fromCredential({
2434
+ type: CredentialType.ScriptHash,
2435
+ hash: logic.hash
2436
+ }, chainId.networkId);
2437
+ const { isRegistered, withdrawableAmount } = await readFromProvider("Custody logic reward account", () => provider.getRewardAccountInfo({ rewardAccount: CardanoRewardAccount(rewardAccount) }, { chainId }));
2438
+ if (!isRegistered) return failCustodyBuild({
2439
+ code: "logic-not-registered",
2440
+ rewardAccount
2441
+ });
2442
+ return {
2443
+ logic,
2444
+ withdrawal: {
2445
+ rewardAccount,
2446
+ quantity: BigNumber.valueOf(withdrawableAmount),
2447
+ redeemer: LOGIC_RUN_REDEEMER
2448
+ }
2449
+ };
2450
+ };
2451
+ /**
2452
+ * The logic the account's control datum names. Lace operates no account
2453
+ * under a logic outside its contract version's catalogue, whose rules it
2454
+ * has not reviewed.
2455
+ */
2456
+ const accountLogicOf = ({ identity, state }) => {
2457
+ const logic = findCustodyLogic(identity.contractVersion, state.logic);
2458
+ return logic.ok ? logic.value : failCustodyBuild({
2459
+ code: "account-not-live",
2460
+ status: "invalid"
2461
+ });
2462
+ };
2463
+ //#endregion
2464
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/custody-validation.ts
2465
+ const cborOf$1 = (data) => PlutusData.fromCore(data).toCbor();
2466
+ /**
2467
+ * What the sponsor service checks the transaction against, from the funding
2468
+ * it leased, the contract version of the account the transaction operates
2469
+ * and the chain.
2470
+ */
2471
+ const sponsorRuleContextOf = (funding, contractVersion, { chainId, clock, now, protocolParameters }) => ({
2472
+ mode: funding.mode === "fee" ? {
2473
+ kind: "fee",
2474
+ feeUtxo: funding.feeUtxo,
2475
+ maxSponsoredLovelace: funding.maxSponsoredLovelace
2476
+ } : { kind: "collateral" },
2477
+ sponsorAddress: funding.sponsorAddress,
2478
+ collateralUtxo: funding.collateralUtxo,
2479
+ contractVersion,
2480
+ networkMagic: chainId.networkMagic,
2481
+ latestValiditySlot: clock.slotAt(funding.validUntil),
2482
+ currentSlot: clock.slotAt(now),
2483
+ collateralPercentage: protocolParameters.collateralPercentage,
2484
+ costModels: protocolParameters.costModels
2485
+ });
2486
+ /**
2487
+ * The largest fee the sponsor's shared collateral UTxO covers, or undefined
2488
+ * without a collateral percentage. The ledger needs collateral of the fee
2489
+ * times the percentage, and the sponsor needs the rest of the UTxO back in
2490
+ * a return output that holds its minimum ADA. For the service's 5 ADA UTxO,
2491
+ * a percentage of 150 and a base return address on Preprod, that is
2492
+ * 2,686,833 lovelace, below the 3,333,333 the UTxO covers without a return.
2493
+ */
2494
+ const collateralFeeCeilingOf = ({ collateralUtxo: [, collateral], sponsorAddress }, { collateralPercentage, coinsPerUtxoByte }) => {
2495
+ if (collateralPercentage <= 0) return void 0;
2496
+ const returned = minAdaRequired({
2497
+ address: sponsorAddress,
2498
+ value: { coins: collateral.value.coins }
2499
+ }, BigInt(coinsPerUtxoByte));
2500
+ const available = collateral.value.coins - returned;
2501
+ return available > 0n ? available * 100n / BigInt(collateralPercentage) : 0n;
2502
+ };
2503
+ const unexpected = (what) => failCustodyBuild({
2504
+ code: "build-failed",
2505
+ message: `The built transaction ${what}`
2506
+ });
2507
+ const isSameGrant = (a, b) => cborOf$1(encodeCustodyGrant(a)) === cborOf$1(encodeCustodyGrant(b));
2508
+ /** Checks the control output and the grant UTxOs the transaction writes. */
2509
+ const checkAccountOutputs = ({ controlOutput, issuedGrants, sweptGrants, grantSpend }, expectation) => {
2510
+ const { control } = expectation;
2511
+ if (!control) {
2512
+ if (controlOutput) unexpected("rewrites the control state");
2513
+ } else if (!controlOutput || cborOf$1(encodeCustodyAccountState(controlOutput.state)) !== cborOf$1(encodeCustodyAccountState(control.state))) unexpected("does not carry the intended account state");
2514
+ else if (controlOutput.lovelace !== control.lovelace) unexpected("does not lock the intended control lovelace");
2515
+ const planned = expectation.issuedGrants ?? [];
2516
+ if (!(issuedGrants.length === planned.length && issuedGrants.every(({ grant, lovelace }, index) => isSameGrant(grant, planned[index].grant) && lovelace === planned[index].lovelace))) unexpected("does not issue the intended grants");
2517
+ const plannedSwept = new Set(expectation.sweptSlots ?? []);
2518
+ if (!(sweptGrants.length === plannedSwept.size && sweptGrants.every(({ grant }) => plannedSwept.has(grant.slot)))) unexpected("does not sweep the intended grants");
2519
+ const plannedSpend = expectation.grantSpend;
2520
+ if (!(!plannedSpend || grantSpend?.grant.slot === plannedSpend.slot && isSameGrant(grantSpend.after, plannedSpend.after))) unexpected("does not spend the intended grant");
2521
+ };
2522
+ /** Checks the value leaving the account, its own tokens aside. */
2523
+ const checkOutflow = ({ netOutflow }, fee, expectation) => {
2524
+ if (!expectation.outflow) return;
2525
+ const { requested, withdrawal } = expectation.outflow;
2526
+ const assets = requested.assets ?? /* @__PURE__ */ new Map();
2527
+ const accountPolicy = expectation.record.accountValidatorHash;
2528
+ const leaving = [...netOutflow.assets].filter(([assetId]) => AssetId.getPolicyId(assetId) !== accountPolicy);
2529
+ if (!(netOutflow.lovelace === requested.coins + fee - withdrawal && leaving.length === assets.size && [...assets].every(([assetId, quantity]) => netOutflow.assets.get(assetId.toLowerCase()) === quantity))) unexpected("pays out more than requested");
2530
+ };
2531
+ /**
2532
+ * Checks the final, balanced transaction before anyone is asked to sign it:
2533
+ * the shared custody inspection (the hosted signer's consent rules), the
2534
+ * intended control output, issued grants, sweeps and grant spend, the
2535
+ * account's net
2536
+ * outflow, the signer, the fee the sponsor's collateral covers, the fee
2537
+ * sponsor service's policy and the protocol limits. Any failure refuses
2538
+ * the build.
2539
+ *
2540
+ * `resolvedInputs` resolves every input, reference input and collateral
2541
+ * input.
2542
+ */
2543
+ const finalizeCustodyBuild = ({ transaction, resolvedInputs }, chain, expectation) => {
2544
+ const inspection = inspectCustodyTransaction(transaction, resolvedInputs, {
2545
+ chainId: chain.chainId,
2546
+ now: chain.now,
2547
+ slotToPosixMs: chain.clock.slotStart,
2548
+ accountRecord: expectation.record,
2549
+ allowGrantSpend: expectation.grantSpend !== void 0,
2550
+ ...expectation.deviceKeyHash && { deviceKeyHash: expectation.deviceKeyHash }
2551
+ });
2552
+ if (!inspection.ok) return failCustodyBuild({
2553
+ code: "inspection-refused",
2554
+ refusals: [...inspection.error.refusals]
2555
+ });
2556
+ const { accounts, requiredSigners, fee, validity } = inspection.value;
2557
+ const [account] = accounts;
2558
+ checkAccountOutputs(account, expectation);
2559
+ checkOutflow(account, fee, expectation);
2560
+ if (requiredSigners.length !== 1 || requiredSigners[0] !== expectation.signer) return unexpected("does not name exactly the intended signer");
2561
+ const { funding } = expectation;
2562
+ const maxFee = funding.mode === "collateral" ? collateralFeeCeilingOf(funding, chain.protocolParameters) : void 0;
2563
+ if (maxFee !== void 0 && fee > maxFee) return failCustodyBuild({
2564
+ code: "collateral-fee-ceiling",
2565
+ fee,
2566
+ maxFee
2567
+ });
2568
+ const sponsorViolations = findCustodySponsorRuleViolations(transaction, resolvedInputs, sponsorRuleContextOf(funding, account.contractVersion, chain));
2569
+ if (sponsorViolations.length > 0) return failCustodyBuild({
2570
+ code: "sponsor-rules",
2571
+ violations: sponsorViolations
2572
+ });
2573
+ try {
2574
+ assertWithinTransactionLimits(transaction, resolvedInputs, chain.protocolParameters);
2575
+ } catch (error) {
2576
+ if (!(error instanceof TransactionLimitsExceededError)) throw error;
2577
+ return failCustodyBuild({
2578
+ code: "transaction-limits",
2579
+ usage: error.usage,
2580
+ exceeded: error.exceeded
2581
+ });
2582
+ }
2583
+ return {
2584
+ transaction: transaction.toCbor(),
2585
+ bodyHash: transaction.getId(),
2586
+ resolvedInputs,
2587
+ accountRecord: expectation.record,
2588
+ fee,
2589
+ invalidHereafter: validity.invalidHereafter.slot
2590
+ };
2591
+ };
2592
+ //#endregion
2593
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/create-custody-account.ts
2594
+ /**
2595
+ * Lovelace a creation leaves unused below the sponsor's per-transaction
2596
+ * limit. It absorbs a fee that comes out slightly higher on the round that
2597
+ * settles the control lovelace, so that round still fits the limit.
2598
+ */
2599
+ const CUSTODY_CREATION_SPONSOR_MARGIN = 100000n;
2600
+ /**
2601
+ * Builds of one creation before the control lovelace settles. The first
2602
+ * build prices the largest fee its execution estimate allows, so it fits
2603
+ * any fee within that estimate; the second claims the headroom the real fee
2604
+ * leaves; the third covers a fee that then moved by more than the margin.
2605
+ */
2606
+ const MAX_HEADROOM_ROUNDS = 3;
2607
+ /**
2608
+ * The first creator index from `startIndex` whose stake credential is not
2609
+ * registered. A registered credential whose address holds the state NFT is
2610
+ * an existing account the caller should reopen; one without it was
2611
+ * registered by someone else first, which blocks creation there forever,
2612
+ * so the scan moves past it.
2613
+ */
2614
+ const findCreatorIndex = async ({ deviceAccountPublicKey, startIndex = 0 }, chain) => {
2615
+ for (let index = startIndex; index < startIndex + 100; index++) {
2616
+ const found = await readCustodyCreatorIndex({
2617
+ contractVersion: CURRENT_CUSTODY_CONTRACT_VERSION,
2618
+ deviceAccountPublicKey,
2619
+ creatorIndex: index
2620
+ }, chain);
2621
+ if (found.status === "unregistered") return {
2622
+ index,
2623
+ key: found.key,
2624
+ identity: found.identity
2625
+ };
2626
+ if (found.status === "account") return failCustodyBuild({
2627
+ code: "account-exists",
2628
+ creatorIndex: index,
2629
+ record: found.record
2630
+ });
2631
+ }
2632
+ return failCustodyBuild({
2633
+ code: "index-exhausted",
2634
+ startIndex,
2635
+ scanned: 100
2636
+ });
2637
+ };
2638
+ const smaller = (a, b) => a < b ? a : b;
2639
+ /**
2640
+ * The control lovelace a creation aims for once its fee is known: what the
2641
+ * sponsor's limit leaves after the fee, the stake deposit and the margin,
2642
+ * within what the fee UTxO can fund, but never less than the initial
2643
+ * state's minimum.
2644
+ */
2645
+ const headroomControlLovelace = ({ maxSponsoredLovelace, deposit, fundable, minimum }, fee) => {
2646
+ const target = smaller(maxSponsoredLovelace - deposit - fee - CUSTODY_CREATION_SPONSOR_MARGIN, fundable);
2647
+ return target > minimum ? target : minimum;
2648
+ };
2649
+ /**
2650
+ * Builds the creation of a custody account in the sponsor's fee mode: the
2651
+ * leased fee UTxO pays the fee, the stake deposit and the control output,
2652
+ * and its change returns to the sponsor in one output. The state NFT is
2653
+ * minted with `CreateAccount`, the stake credential is registered in the
2654
+ * same transaction with a Conway registration certificate, which carries
2655
+ * the `Operate` witness the mint looks for, and the logic the state names
2656
+ * runs through its withdrawal, which validates the new state. The creator
2657
+ * key is the only device and the only required signer, and the state
2658
+ * starts under the current logic at generation 0 with no slot issued, none
2659
+ * revoked and none outstanding.
2660
+ *
2661
+ * The proxy and the logic are read from their parked UTxOs on a network
2662
+ * that has them, and attached elsewhere; the stake script, applied to the
2663
+ * creator key, is always attached. Whatever the logic run draws joins the
2664
+ * control output, so the sponsor pays the same.
2665
+ */
2666
+ const buildCustodyCreation = async (operation, funding, chain) => {
2667
+ const startIndex = operation.startIndex ?? 0;
2668
+ if (!Number.isSafeInteger(startIndex) || startIndex < 0 || startIndex + 100 > 2147483648) return failCustodyBuild({
2669
+ code: "invalid-operation",
2670
+ reason: "invalid-start-index",
2671
+ message: `Invalid creator start index ${startIndex}`
2672
+ });
2673
+ const contractVersion = CURRENT_CUSTODY_CONTRACT_VERSION;
2674
+ const currentLogic = findCustodyLogic(contractVersion, contractVersion.currentLogicHash);
2675
+ if (!currentLogic.ok) throw new Error(`The contract version has no logic ${contractVersion.currentLogicHash}`);
2676
+ const logicRun = await readCustodyLogicRun(currentLogic.value, chain);
2677
+ const { index, key, identity } = await findCreatorIndex(operation, chain);
2678
+ const { protocolParameters: parameters } = chain;
2679
+ const state = transitioned(createInitialCustodyAccountState(key.keyHash, logicRun.logic.hash));
2680
+ const witnesses = custodyScriptWitnesses(contractVersion, chain, [contractVersion.accountValidator, logicRun.logic.script]);
2681
+ const drawn = logicRun.withdrawal.quantity;
2682
+ const deposit = BigInt(parameters.stakeKeyDeposit);
2683
+ const invalidHereafter = validitySlotFor(funding.validUntil, chain);
2684
+ const executionEstimate = custodyExecutionEstimate({
2685
+ createAccount: 1,
2686
+ operate: 1,
2687
+ logic: "arrival"
2688
+ });
2689
+ const largestFee = preEvaluationFeeBound(parameters, estimateWithinLimit(executionEstimate, parameters.maxExecutionUnitsPerTransaction));
2690
+ const bounds = {
2691
+ maxSponsoredLovelace: funding.maxSponsoredLovelace,
2692
+ deposit,
2693
+ fundable: funding.feeUtxo[1].value.coins - changeReserveAt(funding.sponsorAddress, parameters.coinsPerUtxoByte) - deposit - largestFee,
2694
+ minimum: controlLovelaceFor({
2695
+ identity,
2696
+ state,
2697
+ floor: 0n
2698
+ }, parameters.coinsPerUtxoByte)
2699
+ };
2700
+ const buildWith = async (controlLovelace) => buildWithEvaluatedBudgets({
2701
+ chain,
2702
+ funds: {
2703
+ selected: [],
2704
+ remaining: []
2705
+ },
2706
+ plan: () => ({
2707
+ inputs: [{ utxo: funding.feeUtxo }],
2708
+ referenceInputs: witnesses.referenced,
2709
+ outputs: [controlOutputOf(identity, state, controlLovelace + drawn)],
2710
+ changeAddress: funding.sponsorAddress,
2711
+ scripts: [...witnesses.attached, identity.stakeScript],
2712
+ executionEstimate,
2713
+ requiredSigner: key.keyHash,
2714
+ invalidHereafter,
2715
+ collateral: {
2716
+ utxo: funding.collateralUtxo,
2717
+ returnAddress: funding.sponsorAddress
2718
+ },
2719
+ mint: {
2720
+ assets: new Map([[identity.stateNftAssetId, 1n]]),
2721
+ redeemer: encodeCustodyMintRedeemer({ kind: "createAccount" })
2722
+ },
2723
+ registration: {
2724
+ stakeCredential: scriptCredentialOf(identity.stakeScriptHash),
2725
+ deposit,
2726
+ redeemer: encodeCustodyStakeRedeemer()
2727
+ },
2728
+ withdrawals: [logicRun.withdrawal]
2729
+ })
2730
+ });
2731
+ let controlLovelace = headroomControlLovelace(bounds, largestFee);
2732
+ let transaction;
2733
+ for (let round = 1;; round++) {
2734
+ transaction = await buildWith(controlLovelace);
2735
+ const fee = transaction.body().fee();
2736
+ const sponsoredLovelace = fee + deposit + controlLovelace;
2737
+ const isWithinLimit = sponsoredLovelace <= funding.maxSponsoredLovelace;
2738
+ const settled = headroomControlLovelace(bounds, fee);
2739
+ const isLastRound = round >= MAX_HEADROOM_ROUNDS || settled === controlLovelace;
2740
+ if (isWithinLimit && (settled < controlLovelace || isLastRound)) break;
2741
+ if (!isWithinLimit && isLastRound) return failCustodyBuild({
2742
+ code: "sponsor-limit",
2743
+ sponsoredLovelace,
2744
+ maxSponsoredLovelace: funding.maxSponsoredLovelace
2745
+ });
2746
+ controlLovelace = settled;
2747
+ }
2748
+ const record = createCustodyAccountRecord(identity);
2749
+ return {
2750
+ ...finalizeCustodyBuild({
2751
+ transaction,
2752
+ resolvedInputs: resolvedInputsOf(transaction, [
2753
+ funding.feeUtxo,
2754
+ ...witnesses.referenced,
2755
+ funding.collateralUtxo
2756
+ ])
2757
+ }, chain, {
2758
+ record,
2759
+ control: {
2760
+ state,
2761
+ lovelace: controlLovelace + drawn
2762
+ },
2763
+ signer: key.keyHash,
2764
+ deviceKeyHash: key.keyHash,
2765
+ funding
2766
+ }),
2767
+ creation: {
2768
+ creatorIndex: index,
2769
+ identity: {
2770
+ chainId: chain.chainId,
2771
+ accountValidatorHash: contractVersion.accountValidatorHash,
2772
+ creatorKeyHash: identity.creatorKeyHash,
2773
+ stakeScriptHash: identity.stakeScriptHash,
2774
+ address: identity.address,
2775
+ rewardAccount: identity.rewardAccount
2776
+ }
2777
+ }
2778
+ };
2779
+ };
2780
+ //#endregion
2781
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/custody-account.ts
2782
+ /** Every UTxO the classification of a live account holds. */
2783
+ const utxosOf = (state) => [
2784
+ state.controlUtxo,
2785
+ ...state.funds,
2786
+ ...state.reserves.own,
2787
+ ...state.reserves.foreign,
2788
+ ...state.grants.map(({ utxo }) => utxo),
2789
+ ...state.stranded,
2790
+ ...state.anomalies.map(({ utxo }) => utxo)
2791
+ ];
2792
+ /**
2793
+ * Resolves the account an operation runs on. The synced view must show it
2794
+ * live with a confirmed control UTxO, and the chain, read again now, must
2795
+ * hold that same control UTxO. Funds, reserves and grants come from that
2796
+ * fresh read, so a UTxO already spent on chain or still unconfirmed is never
2797
+ * selected; funds and reserves an unsettled transaction of this wallet
2798
+ * spends are left out too, so concurrent builds pick disjoint ones.
2799
+ *
2800
+ * Owner operations are never chained: while the synced control UTxO is the
2801
+ * output of a submitted transaction that has not settled, the build is
2802
+ * refused.
2803
+ */
2804
+ const resolveCustodyAccount = async (accountId, snapshot, chain) => {
2805
+ const { account, accountState, settledUtxos } = snapshot;
2806
+ if (!account || account.blockchainSpecific.chainId.networkMagic !== chain.chainId.networkMagic) return failCustodyBuild({ code: "unknown-account" });
2807
+ const identity = verifyCardanoCustodyAccountIdentity(account.blockchainSpecific);
2808
+ if (!identity.ok) return failCustodyBuild({ code: "unknown-account" });
2809
+ if (accountState?.status !== "live") return failCustodyBuild({
2810
+ code: "account-not-live",
2811
+ status: accountState?.status ?? "unsynced"
2812
+ });
2813
+ const controlKey = utxoKey(accountState.controlUtxo);
2814
+ if (!settledUtxos.some((utxo) => utxoKey(utxo) === controlKey)) return failCustodyBuild({ code: "operation-in-progress" });
2815
+ const onChain = classifyCardanoCustodyAccount({
2816
+ account: account.blockchainSpecific,
2817
+ utxos: await readFromProvider("Custody account UTxOs", () => chain.provider.getUtxosAtAddress({
2818
+ address: CardanoPaymentAddress(identity.value.address),
2819
+ withReferenceScripts: true
2820
+ }, { chainId: chain.chainId })),
2821
+ withdrawableRewards: 0n,
2822
+ protocolParameters: chain.protocolParameters
2823
+ });
2824
+ if (onChain.status !== "live" || utxoKey(onChain.controlUtxo) !== controlKey) return failCustodyBuild({ code: "stale-control" });
2825
+ const inFlight = new Set(utxosOf(accountState).map(utxoKey));
2826
+ const spentInFlight = new Set(settledUtxos.map(utxoKey).filter((key) => !inFlight.has(key)));
2827
+ const isUnspent = (utxo) => !spentInFlight.has(utxoKey(utxo));
2828
+ return {
2829
+ accountId,
2830
+ identity: identity.value,
2831
+ record: createCustodyAccountRecord(identity.value),
2832
+ device: account.blockchainSpecific.device,
2833
+ control: onChain.controlUtxo,
2834
+ state: onChain.state,
2835
+ funds: onChain.funds.filter(isUnspent),
2836
+ reserves: onChain.reserves.own.filter(isUnspent),
2837
+ grants: onChain.grants,
2838
+ spentInFlight
2839
+ };
2840
+ };
2841
+ /**
2842
+ * The device key that signs an owner operation: the bound device, listed in
2843
+ * the current state. An observer holds no device and no owner authority.
2844
+ */
2845
+ const requireListedDevice = ({ device, state }) => {
2846
+ if (!device) return failCustodyBuild({ code: "observer-account" });
2847
+ const keyHash = device.keyHash.toLowerCase();
2848
+ return state.devices.some((listed) => listed.toLowerCase() === keyHash) ? device.keyHash : failCustodyBuild({
2849
+ code: "device-not-listed",
2850
+ keyHash: device.keyHash
2851
+ });
2852
+ };
2853
+ //#endregion
2854
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/custody-requests.ts
2855
+ const outputProblem = (output, identity, coinsPerUtxoByte) => {
2856
+ const address = Address.fromString(output.address);
2857
+ if (address?.getNetworkId() !== identity.chainId.networkId) return "is not an address on the account network";
2858
+ if (address.getType() === AddressType.Byron) return "is a Byron address, which a Plutus transaction cannot pay";
2859
+ if (address.toBech32() === identity.address) return "pays the account itself";
2860
+ const accountPolicy = identity.contractVersion.accountValidatorHash;
2861
+ if ([...output.value.assets?.keys() ?? []].some((assetId) => AssetId.getPolicyId(assetId) === accountPolicy)) return "carries a custody account token";
2862
+ return output.value.coins < minAdaRequired(output, BigInt(coinsPerUtxoByte)) ? "holds less than its minimum ADA" : void 0;
2863
+ };
2864
+ /**
2865
+ * Checks the outputs an owner or a grantee asks the account to pay. Each
2866
+ * goes to a Shelley address on the account's network other than the
2867
+ * account itself, carries no token of a custody account and holds its
2868
+ * minimum ADA: the build never tops a requested amount up from account
2869
+ * funds.
2870
+ */
2871
+ const checkRequestedOutputs = (outputs, identity, coinsPerUtxoByte) => {
2872
+ for (const [outputIndex, output] of outputs.entries()) {
2873
+ const problem = outputProblem(output, identity, coinsPerUtxoByte);
2874
+ if (problem) failCustodyBuild({
2875
+ code: "invalid-operation",
2876
+ reason: "invalid-output",
2877
+ message: `Output ${outputIndex} ${problem}`
2878
+ });
2879
+ }
2880
+ };
2881
+ const invalidMetadata = (message) => failCustodyBuild({
2882
+ code: "invalid-operation",
2883
+ reason: "invalid-metadata",
2884
+ message
2885
+ });
2886
+ /** The largest metadata label the ledger encodes: a 64-bit unsigned integer. */
2887
+ const MAX_METADATA_LABEL = 2n ** 64n - 1n;
2888
+ /**
2889
+ * The metadata an owner asks the transaction to carry: a map of labels to
2890
+ * metadata the ledger accepts, which its serialization checks: text and
2891
+ * bytes hold at most 64 bytes each. An empty map carries nothing.
2892
+ */
2893
+ const checkRequestedMetadata = (metadata) => {
2894
+ if (metadata === void 0) return void 0;
2895
+ if (!(metadata instanceof Map)) return invalidMetadata("The metadata must be a map of labels to metadata");
2896
+ if (metadata.size === 0) return void 0;
2897
+ for (const label of metadata.keys()) if (typeof label !== "bigint" || label < 0n || label > MAX_METADATA_LABEL) return invalidMetadata(`Metadata label ${String(label)} is not a 64-bit unsigned integer`);
2898
+ try {
2899
+ AuxiliaryData.fromCore({ blob: metadata });
2900
+ } catch (error) {
2901
+ return invalidMetadata(`The metadata cannot be encoded: ${error instanceof Error ? error.message : String(error)}`);
2902
+ }
2903
+ return metadata;
2904
+ };
2905
+ const invalidScope = (message) => failCustodyBuild({
2906
+ code: "invalid-operation",
2907
+ reason: "grant-scope",
2908
+ message
2909
+ });
2910
+ /**
2911
+ * The grant a scope request asks to issue in `slot`. The request goes
2912
+ * through the library's strict requested-scope parser, the one the signer
2913
+ * applies to `requestedScopes`, so Lace refuses a scope the signer would
2914
+ * refuse as malformed; its recipients, which must be on the account's
2915
+ * network, become Plutus addresses.
2916
+ */
2917
+ const grantRequestOf = ({ scope: request, slot }, { chainId }, now) => {
2918
+ let serialized;
2919
+ try {
2920
+ serialized = serializeCustodyRequestedGrantScope({
2921
+ ...request,
2922
+ slot
2923
+ });
2924
+ } catch {
2925
+ return invalidScope("scope: not a grant scope");
2926
+ }
2927
+ const parsed = parseCustodyRequestedGrantScope(serialized);
2928
+ if (!parsed.ok) return invalidScope([parsed.error.field, parsed.error.reason].filter(Boolean).join(": "));
2929
+ const { grantee, recipients, ...scope } = parsed.value;
2930
+ return {
2931
+ grantee,
2932
+ now,
2933
+ scope: {
2934
+ asset: scope.asset,
2935
+ perCallCap: scope.perCallCap,
2936
+ cap: scope.cap,
2937
+ lovelacePerCallCap: scope.lovelacePerCallCap,
2938
+ lovelaceCap: scope.lovelaceCap,
2939
+ expiresAt: scope.expiresAt,
2940
+ recipients: recipients.map((text) => {
2941
+ const address = Address.fromBech32(text);
2942
+ const recipient = custodyRecipientFromAddress(address);
2943
+ return recipient.ok && address.getNetworkId() === chainId.networkId ? recipient.value : invalidScope(`recipients: ${text} is not on the account network`);
2944
+ })
2945
+ }
2946
+ };
2947
+ };
2948
+ /** A bech32 stake pool id, refused as an invalid operation when malformed. */
2949
+ const poolIdOf = (poolId) => {
2950
+ try {
2951
+ return PoolId(poolId);
2952
+ } catch {
2953
+ return failCustodyBuild({
2954
+ code: "invalid-operation",
2955
+ reason: "invalid-pool-id",
2956
+ message: `Invalid stake pool id ${poolId}`
2957
+ });
2958
+ }
2959
+ };
2960
+ //#endregion
2961
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/custody-grant-spend.ts
2962
+ /**
2963
+ * The most fund UTxOs one grant spend spends, upstream's `MAX_FUND_INPUTS`.
2964
+ * Every input adds a proxy run that decodes the whole transaction, and the
2965
+ * logic checks each one: a spend over twelve deposits measured 4.28M
2966
+ * memory units on upstream's devnet, a quarter of Preprod's limit.
2967
+ */
2968
+ const MAX_GRANT_SPEND_FUND_INPUTS = 12;
2969
+ /**
2970
+ * What the second build adds to the first build's fee when it lowers the
2971
+ * caps: room for a fee that moves when the caps' encoding or the measured
2972
+ * budgets change between the two builds.
2973
+ */
2974
+ const GRANT_CAP_FEE_MARGIN = 10000n;
2975
+ const SPEND_WITH_GRANT_REDEEMER = encodeCustodyAccountRedeemer({ kind: "spendWithGrant" });
2976
+ const FUND_REDEEMER$1 = encodeCustodyAccountRedeemer({ kind: "fund" });
2977
+ const cborOf = (data) => PlutusData.fromCore(data).toCbor();
2978
+ const FUND_REDEEMER_CBOR = cborOf(FUND_REDEEMER$1);
2979
+ const SPEND_WITH_GRANT_REDEEMER_CBOR = cborOf(SPEND_WITH_GRANT_REDEEMER);
2980
+ const deltaOf = (value, fee = 0n) => ({
2981
+ lovelace: value.coins + fee,
2982
+ assets: new Map([...value.assets].map(([assetId, quantity]) => [assetId.toLowerCase(), quantity]))
2983
+ });
2984
+ /**
2985
+ * Metadata is an owner's to attach: the operation type leaves it out, and a
2986
+ * caller that names it anyway is refused rather than built without it.
2987
+ */
2988
+ const refuseMetadata = (operation) => {
2989
+ if (operation.metadata !== void 0) failCustodyBuild({
2990
+ code: "invalid-operation",
2991
+ reason: "invalid-metadata",
2992
+ message: "A grant spend carries no metadata"
2993
+ });
2994
+ };
2995
+ /** The fee bound the operation names, or the default when it names none. */
2996
+ const feeBoundOf = ({ feeBound }) => {
2997
+ if (feeBound === void 0) return CARDANO_CUSTODY_DEFAULT_GRANT_FEE_BOUND;
2998
+ return typeof feeBound === "bigint" && feeBound > 0n ? feeBound : failCustodyBuild({
2999
+ code: "invalid-operation",
3000
+ reason: "invalid-fee-bound",
3001
+ message: "The fee bound must be a positive number of lovelace"
3002
+ });
3003
+ };
3004
+ /**
3005
+ * The grant UTxO a grant spend spends: the grantee's grant in the named
3006
+ * slot, or its single current, unexpired grant. The grant must be current
3007
+ * against the control state, and no unsettled transaction of this wallet
3008
+ * may already spend its UTxO.
3009
+ */
3010
+ const selectGrant = ({ grantee, slot }, { grants, state, spentInFlight }, { now }) => {
3011
+ const granteeHex = grantee.toLowerCase();
3012
+ const held = grants.filter(({ grant }) => grant.grantee.toLowerCase() === granteeHex);
3013
+ const livenessOf = ({ grant }) => custodyGrantLiveness(grant, state, now);
3014
+ let selected;
3015
+ if (slot === void 0) {
3016
+ const current = held.filter((found) => livenessOf(found) === "current");
3017
+ if (current.length > 1) return failCustodyBuild({
3018
+ code: "grant-spend",
3019
+ reason: "ambiguous-grant",
3020
+ grantee,
3021
+ slots: current.map(({ grant }) => grant.slot)
3022
+ });
3023
+ selected = current[0] ?? held.at(-1);
3024
+ } else selected = held.find(({ grant }) => grant.slot === slot);
3025
+ if (!selected) return failCustodyBuild({
3026
+ code: "grant-spend",
3027
+ reason: "unknown-grant",
3028
+ grantee,
3029
+ ...slot !== void 0 && { slot }
3030
+ });
3031
+ const liveness = livenessOf(selected);
3032
+ if (liveness !== "current") return failCustodyBuild({
3033
+ code: "grant-spend",
3034
+ reason: "dead-grant",
3035
+ slot: selected.grant.slot,
3036
+ liveness
3037
+ });
3038
+ return spentInFlight.has(outpointKey$1(selected.utxo[0])) ? failCustodyBuild({ code: "operation-in-progress" }) : selected;
3039
+ };
3040
+ /** Refuses an output the grant's recipient list does not name, on the account's network. */
3041
+ const checkRecipients = (grant, outputs, networkId) => {
3042
+ const { recipients } = grant.scope;
3043
+ if (recipients.length === 0) return;
3044
+ const allowed = new Set(recipients.flatMap((recipient) => {
3045
+ const address = custodyRecipientToAddress(recipient, networkId);
3046
+ return address.ok ? [address.value.toBech32()] : [];
3047
+ }));
3048
+ for (const [outputIndex, output] of outputs.entries()) {
3049
+ const address = Address.fromString(output.address).toBech32();
3050
+ if (!allowed.has(address)) failCustodyBuild({
3051
+ code: "grant-spend",
3052
+ reason: "recipient-not-allowed",
3053
+ outputIndex
3054
+ });
3055
+ }
3056
+ };
3057
+ /**
3058
+ * Refuses an outflow the grant's scope does not allow. `fee`, when known,
3059
+ * is the part of the lovelace outflow that pays the transaction fee; each
3060
+ * refusal reports it, so a caller can size a grant for the payment and the
3061
+ * fee.
3062
+ */
3063
+ const refuseScope = (grant, leaving, fee) => {
3064
+ const violations = findCustodyScopeViolations(grant.scope, leaving, fee);
3065
+ if (violations.length > 0) failCustodyBuild({
3066
+ code: "grant-spend",
3067
+ reason: "scope",
3068
+ violations
3069
+ });
3070
+ };
3071
+ /**
3072
+ * The upper validity bound of a grant spend: the earlier of the funding
3073
+ * window and the slot holding the grant's expiry, whose start the contract
3074
+ * compares with the expiry. A grant that expires within the current slot
3075
+ * leaves no such slot.
3076
+ */
3077
+ const grantSpendValiditySlot = (grant, funding, chain) => {
3078
+ const windowSlot = validitySlotFor(funding.validUntil, chain);
3079
+ const { expiresAt } = grant.scope;
3080
+ if (expiresAt >= BigInt(chain.clock.slotStart(windowSlot))) return windowSlot;
3081
+ const expirySlot = chain.clock.slotAt(Number(expiresAt));
3082
+ return expirySlot > chain.clock.slotAt(chain.now) ? expirySlot : failCustodyBuild({
3083
+ code: "grant-spend",
3084
+ reason: "dead-grant",
3085
+ slot: grant.slot,
3086
+ liveness: "expired"
3087
+ });
3088
+ };
3089
+ /**
3090
+ * Refuses a spend the fund UTxOs cannot pay however they are combined:
3091
+ * they hold less of a requested asset, or less lovelace, with whatever the
3092
+ * logic run draws, than the outputs and the least fee. It runs before the
3093
+ * fund input cap, because consolidation cannot help an account that is
3094
+ * short of funds.
3095
+ */
3096
+ const refuseShortFunds = (candidates, requested, { leastFee, drawn }) => {
3097
+ const available = totalValueOf(candidates.map(([, output]) => output));
3098
+ if (available.coins + drawn < requested.coins + leastFee || [...requested.assets].some(([assetId, quantity]) => (available.assets.get(assetId) ?? 0n) < quantity)) failCustodyBuild({
3099
+ code: "insufficient-funds",
3100
+ message: "The fund UTxOs hold less than the requested outputs and the least fee"
3101
+ });
3102
+ };
3103
+ const refuseFundInputCount = (funds) => {
3104
+ if (funds.length > MAX_GRANT_SPEND_FUND_INPUTS) failCustodyBuild({
3105
+ code: "grant-spend",
3106
+ reason: "fund-input-cap",
3107
+ maxFundInputs: MAX_GRANT_SPEND_FUND_INPUTS,
3108
+ requiredFundInputs: funds.length
3109
+ });
3110
+ };
3111
+ /**
3112
+ * Refuses a spend whose budgets exceed the per-transaction execution limit,
3113
+ * as a fund input count above what one spend fits: the grant's run is
3114
+ * fixed, so only fewer, larger fund UTxOs help. The count that fits follows
3115
+ * from the grant's and the logic's budgets and the dearest fund budget; the
3116
+ * logic's budget already counts every fund it checked, so the count errs
3117
+ * low.
3118
+ */
3119
+ const refuseOverBudget = (redeemers, { maxExecutionUnitsPerTransaction: limit }) => {
3120
+ const total = redeemers.reduce((sum, { executionUnits }) => ({
3121
+ memory: sum.memory + executionUnits.memory,
3122
+ steps: sum.steps + executionUnits.steps
3123
+ }), {
3124
+ memory: 0,
3125
+ steps: 0
3126
+ });
3127
+ if (total.memory <= limit.memory && total.steps <= limit.steps) return;
3128
+ const fixed = redeemers.filter(({ purpose, data }) => purpose === RedeemerPurpose.withdrawal || cborOf(data) === SPEND_WITH_GRANT_REDEEMER_CBOR).reduce((sum, { executionUnits }) => ({
3129
+ memory: sum.memory + executionUnits.memory,
3130
+ steps: sum.steps + executionUnits.steps
3131
+ }), {
3132
+ memory: 0,
3133
+ steps: 0
3134
+ });
3135
+ const fundBudgets = redeemers.filter(({ data }) => cborOf(data) === FUND_REDEEMER_CBOR).map(({ executionUnits }) => executionUnits);
3136
+ const fundMemory = Math.max(1, ...fundBudgets.map(({ memory }) => memory));
3137
+ const fundSteps = Math.max(1, ...fundBudgets.map(({ steps }) => steps));
3138
+ const fitting = Math.floor(Math.min((limit.memory - fixed.memory) / fundMemory, (limit.steps - fixed.steps) / fundSteps));
3139
+ failCustodyBuild({
3140
+ code: "grant-spend",
3141
+ reason: "fund-input-cap",
3142
+ maxFundInputs: Math.max(0, Math.min(MAX_GRANT_SPEND_FUND_INPUTS, fitting)),
3143
+ requiredFundInputs: fundBudgets.length
3144
+ });
3145
+ };
3146
+ /**
3147
+ * The grant the recreated grant UTxO carries: the caps `grant_after_spend`
3148
+ * allows for the requested outputs and `fee`, and no lower than zero. The
3149
+ * logic accepts any cap from zero to the value for the fee of the
3150
+ * transaction it checks, so a `fee` no lower than that one writes valid
3151
+ * caps.
3152
+ */
3153
+ const grantAfter = (grant, requested, fee) => {
3154
+ const { scope } = custodyGrantAfterSpend(grant, deltaOf(requested, fee));
3155
+ return {
3156
+ ...grant,
3157
+ scope: {
3158
+ ...scope,
3159
+ cap: scope.cap > 0n ? scope.cap : 0n,
3160
+ lovelaceCap: scope.lovelaceCap > 0n ? scope.lovelaceCap : 0n
3161
+ }
3162
+ };
3163
+ };
3164
+ /**
3165
+ * The most fee the outflow carries before it breaks a lovelace cap of the
3166
+ * scope: the lower of a lovelace grant's caps, or of a token grant's
3167
+ * lovelace caps, less the requested lovelace.
3168
+ */
3169
+ const feeRoomOf = ({ scope }, requested) => {
3170
+ const [perCallCap, cap] = isCustodyLovelaceAsset(scope.asset) ? [scope.perCallCap, scope.cap] : [scope.lovelacePerCallCap, scope.lovelaceCap];
3171
+ return (perCallCap < cap ? perCallCap : cap) - requested.coins;
3172
+ };
3173
+ /**
3174
+ * Builds a grant spend for the grantee to sign. It references the
3175
+ * account's control UTxO, spends the grant UTxO with `SpendWithGrant` and
3176
+ * plain fund UTxOs with `Fund`, largest first, never a reserve, one
3177
+ * carrying a reference script, or one in `excludedInputs`, and runs the
3178
+ * logic the control names through its withdrawal. The grant UTxO is
3179
+ * recreated with the same value and the caps lowered by the outflow, the
3180
+ * requested outputs are paid, and the change, with whatever the logic run
3181
+ * draws, returns to the account as a plain output. The account pays the
3182
+ * fee, which counts against the grant's caps; the sponsor contributes only
3183
+ * its collateral. The proxy and the logic are read from their parked UTxOs
3184
+ * on a network that has them, and attached elsewhere.
3185
+ *
3186
+ * The caps go into the datum before the fee is known, and the logic counts
3187
+ * the fee in the outflow it checks against them, both on the final
3188
+ * transaction and on the balanced draft the provider evaluates. So every
3189
+ * draft is balanced at no more than the fee the caps were lowered by, nor
3190
+ * the fee that keeps the outflow within the caps, and two builds run with
3191
+ * evaluated budgets. The first lowers the caps by the fee bound, and a fee
3192
+ * above the bound or the caps is refused. The second lowers them by the
3193
+ * first fee and a small margin, and stands when its fee fits that; the
3194
+ * first build stands whenever the second cannot be built.
3195
+ */
3196
+ const buildCustodyGrantSpend = async ({ operation, funding }, context, chain) => {
3197
+ const { identity, control } = context;
3198
+ const { protocolParameters: parameters } = chain;
3199
+ refuseMetadata(operation);
3200
+ const feeBound = feeBoundOf(operation);
3201
+ const spent = selectGrant(operation, context, chain);
3202
+ const { grant } = spent;
3203
+ checkRequestedOutputs(operation.outputs, identity, parameters.coinsPerUtxoByte);
3204
+ checkRecipients(grant, operation.outputs, identity.chainId.networkId);
3205
+ const requested = totalValueOf(operation.outputs);
3206
+ refuseScope(grant, deltaOf(requested));
3207
+ const invalidHereafter = grantSpendValiditySlot(grant, funding, chain);
3208
+ const grantee = grant.grantee;
3209
+ const logicRun = await readCustodyLogicRun(accountLogicOf(context), chain);
3210
+ const drawn = logicRun.withdrawal.quantity;
3211
+ const { contractVersion } = identity;
3212
+ const witnesses = custodyScriptWitnesses(contractVersion, chain, [contractVersion.accountValidator, logicRun.logic.script]);
3213
+ const excluded = new Set((operation.excludedInputs ?? []).map(outpointKey$1));
3214
+ const candidates = context.funds.filter(([input, output]) => output.scriptReference === void 0 && !excluded.has(outpointKey$1(input)));
3215
+ const planOf = (after) => (funds) => ({
3216
+ inputs: [{
3217
+ utxo: spent.utxo,
3218
+ redeemer: SPEND_WITH_GRANT_REDEEMER
3219
+ }, ...funds.map((utxo) => ({
3220
+ utxo,
3221
+ redeemer: FUND_REDEEMER$1
3222
+ }))],
3223
+ referenceInputs: [control, ...witnesses.referenced],
3224
+ outputs: [grantOutputOf(identity, after, spent.utxo[1].value.coins), ...operation.outputs],
3225
+ changeAddress: identity.address,
3226
+ scripts: witnesses.attached,
3227
+ executionEstimate: custodyExecutionEstimate({
3228
+ spendWithGrant: 1,
3229
+ fund: funds.length,
3230
+ logic: "agent"
3231
+ }),
3232
+ requiredSigner: grantee,
3233
+ invalidHereafter,
3234
+ collateral: {
3235
+ utxo: funding.collateralUtxo,
3236
+ returnAddress: funding.sponsorAddress
3237
+ },
3238
+ withdrawals: [logicRun.withdrawal]
3239
+ });
3240
+ const needed = requested.coins + plannedFeeOf(planOf(grant)([]), parameters) + changeReserveAt(identity.address, parameters.coinsPerUtxoByte) - drawn;
3241
+ const selection = selectFunds(candidates, {
3242
+ coins: needed > 0n ? needed : 0n,
3243
+ assets: requested.assets
3244
+ });
3245
+ if (!selection.covers) refuseShortFunds(candidates, requested, {
3246
+ leastFee: leastFeeOf(planOf(grant)([]), parameters),
3247
+ drawn
3248
+ });
3249
+ const feeRoom = feeRoomOf(grant, requested);
3250
+ /** A build whose caps are lowered by `lowering` and whose drafts fit them. */
3251
+ const build = async (lowering, funds) => {
3252
+ const after = grantAfter(grant, requested, lowering);
3253
+ const transaction = await buildWithEvaluatedBudgets({
3254
+ chain,
3255
+ funds,
3256
+ checkFunds: refuseFundInputCount,
3257
+ plan: planOf(after),
3258
+ maxBalancedFee: lowering < feeRoom ? lowering : feeRoom
3259
+ });
3260
+ return {
3261
+ transaction,
3262
+ after,
3263
+ fee: transaction.body().fee()
3264
+ };
3265
+ };
3266
+ /** Refuses a fee above the bound, or one that takes the outflow over a cap. */
3267
+ const refuseFee = (fee) => fee > feeBound ? failCustodyBuild({
3268
+ code: "grant-spend",
3269
+ reason: "fee-above-bound",
3270
+ fee,
3271
+ feeBound
3272
+ }) : failCustodyBuild({
3273
+ code: "grant-spend",
3274
+ reason: "scope",
3275
+ violations: findCustodyScopeViolations(grant.scope, deltaOf(requested, fee), fee)
3276
+ });
3277
+ const first = await build(feeBound, selection).catch((error) => {
3278
+ if (!(error instanceof MaxBalancedFeeExceeded)) throw error;
3279
+ refuseOverBudget(error.evaluatedRedeemers, parameters);
3280
+ return refuseFee(error.fee);
3281
+ });
3282
+ refuseOverBudget(first.transaction.witnessSet().redeemers()?.toCore() ?? [], parameters);
3283
+ const firstFunds = resolvedInputsOf(first.transaction, candidates);
3284
+ let chosen = first;
3285
+ const tightFee = first.fee + GRANT_CAP_FEE_MARGIN;
3286
+ const { scope: tight } = grantAfter(grant, requested, tightFee);
3287
+ if (tight.cap > first.after.scope.cap || tight.lovelaceCap > first.after.scope.lovelaceCap) {
3288
+ const used = new Set(firstFunds.map(([input]) => outpointKey$1(input)));
3289
+ try {
3290
+ chosen = await build(tightFee, {
3291
+ selected: firstFunds,
3292
+ remaining: candidates.filter(([input]) => !used.has(outpointKey$1(input)))
3293
+ });
3294
+ } catch (error) {
3295
+ if (!(error instanceof MaxBalancedFeeExceeded) && !(error instanceof CustodyBuildFailure)) throw error;
3296
+ }
3297
+ }
3298
+ const { transaction, after } = chosen;
3299
+ return finalizeCustodyBuild({
3300
+ transaction,
3301
+ resolvedInputs: resolvedInputsOf(transaction, [
3302
+ spent.utxo,
3303
+ control,
3304
+ ...witnesses.referenced,
3305
+ ...candidates,
3306
+ funding.collateralUtxo
3307
+ ])
3308
+ }, chain, {
3309
+ record: context.record,
3310
+ grantSpend: {
3311
+ slot: grant.slot,
3312
+ after
3313
+ },
3314
+ signer: grantee,
3315
+ outflow: {
3316
+ requested,
3317
+ withdrawal: drawn
3318
+ },
3319
+ funding
3320
+ });
3321
+ };
3322
+ //#endregion
3323
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/custody-owner-operations.ts
3324
+ /**
3325
+ * The control output headroom, its lovelace above the state's minimum, that
3326
+ * an issue or a replace tops up to. The headroom is the owner's fee reserve:
3327
+ * an owner operation the plain funds cannot pay takes its fee from it, so an
3328
+ * owner can still revoke a grant after its agent drains the plain funds.
3329
+ * It pays two revokes. Where the proxy and the logic are attached, as on
3330
+ * Mainnet, a revoke carries about 12.7 KB, 11,871 bytes of them the two
3331
+ * scripts, at 44 lovelace per byte. At Preprod's prices and the 1.6M to 1.8M
3332
+ * memory units upstream's devnet measured over the largest state, the
3333
+ * control pays a fee of about 0.84 ADA. Before evaluation balancing prices
3334
+ * a revoke at its execution estimate, about 0.99 ADA, which the draft the
3335
+ * provider evaluates must find in the headroom. The first revoke's fee, the
3336
+ * second's draft, the state's growth and the growth margin come to about
3337
+ * 2.04 ADA. On Preprod, where the scripts are referenced at 15 lovelace per
3338
+ * byte, a revoke costs about 0.5 ADA and its draft about 0.64 ADA.
3339
+ */
3340
+ const CUSTODY_OWNER_FEE_RESERVE_TARGET = 2100000n;
3341
+ /**
3342
+ * The control datum bytes the control output keeps lovelace for beyond its
3343
+ * minimum after paying a fee. The largest growth one owner operation makes
3344
+ * is a device key: 30 bytes, its 28 bytes and their header. A revoked slot
3345
+ * adds at most 6, and a counter crossing a CBOR width at most 2. The next
3346
+ * operation's growth is then covered, whoever pays its fee.
3347
+ */
3348
+ const CUSTODY_CONTROL_GROWTH_MARGIN_BYTES = 32;
3349
+ const DEVICE_REDEEMER = encodeCustodyAccountRedeemer({ kind: "device" });
3350
+ const FUND_REDEEMER = encodeCustodyAccountRedeemer({ kind: "fund" });
3351
+ const SWEEP_REDEEMER = encodeCustodyAccountRedeemer({ kind: "sweepGrant" });
3352
+ const ISSUE_REDEEMER = encodeCustodyMintRedeemer({ kind: "issueGrants" });
3353
+ const BURN_REDEEMER = encodeCustodyMintRedeemer({ kind: "burnGrants" });
3354
+ /**
3355
+ * Refuses a grant whose per-call caps cannot pay the smallest spend: one
3356
+ * output at its minimum ADA and the default grant fee bound. Its agent could
3357
+ * never spend it.
3358
+ */
3359
+ const spendableGrant = ({ grant }, { identity }, { protocolParameters }) => isCardanoCustodyGrantExhausted(grant.scope, {
3360
+ protocolParameters,
3361
+ networkId: identity.chainId.networkId
3362
+ }) ? failCustodyBuild({
3363
+ code: "invalid-operation",
3364
+ reason: "grant-scope",
3365
+ message: "The caps cannot pay the smallest spend: one output at its minimum ADA and the grant fee bound"
3366
+ }) : grant;
3367
+ /** What an issue or a replace writes: the state and the grant it issues. */
3368
+ const issueEffectOf = (result, context, chain) => {
3369
+ const issued = transitioned(result);
3370
+ return {
3371
+ state: issued.state,
3372
+ outputs: [],
3373
+ issued: spendableGrant(issued, context, chain)
3374
+ };
3375
+ };
3376
+ /**
3377
+ * The sweep of the dead grants in `slots`. Each must be dead against the
3378
+ * state before the sweep. The validity interval starts no earlier than the
3379
+ * current slot, so a grant dead by expiry alone must expire before the
3380
+ * current slot starts; the interval then starts in the first slot after
3381
+ * the latest such expiry.
3382
+ */
3383
+ const sweepEffectOf = (slots, { state, grants, spentInFlight }, chain) => {
3384
+ const swept = slots.map((slot) => {
3385
+ const found = grants.find(({ grant }) => grant.slot === slot);
3386
+ if (!found) return failCustodyBuild({
3387
+ code: "invalid-operation",
3388
+ reason: "state-transition",
3389
+ error: {
3390
+ code: "grant-not-found",
3391
+ slot
3392
+ }
3393
+ });
3394
+ return spentInFlight.has(outpointKey$1(found.utxo[0])) ? failCustodyBuild({ code: "operation-in-progress" }) : found;
3395
+ });
3396
+ const currentSlotStart = chain.clock.slotStart(chain.clock.slotAt(chain.now));
3397
+ const { state: next, startsAfter } = transitioned(sweepCustodyGrants(state, swept.map(({ grant }) => grant), currentSlotStart));
3398
+ return {
3399
+ state: next,
3400
+ outputs: [],
3401
+ swept: {
3402
+ grants: swept,
3403
+ ...startsAfter !== void 0 && { invalidBefore: firstSlotStartingAfter(startsAfter, chain) }
3404
+ }
3405
+ };
3406
+ };
3407
+ /**
3408
+ * The metadata an operation carries. Only an owner spend carries any: the
3409
+ * other operation types leave it out, and a caller that names it on one
3410
+ * anyway is refused rather than built without it, as a grant spend is.
3411
+ */
3412
+ const metadataOf = (operation) => {
3413
+ const { metadata } = operation;
3414
+ if (metadata !== void 0 && operation.type !== "owner-spend") return failCustodyBuild({
3415
+ code: "invalid-operation",
3416
+ reason: "invalid-metadata",
3417
+ message: `A ${operation.type} carries no metadata`
3418
+ });
3419
+ return checkRequestedMetadata(metadata);
3420
+ };
3421
+ /**
3422
+ * The state an owner operation writes and what else it does. State changes
3423
+ * go through the library's transitions, which refuse what the account could
3424
+ * not use afterwards; withdrawal reads the reward balance now, because the
3425
+ * ledger accepts only the exact balance.
3426
+ */
3427
+ const ownerEffectOf = async (operation, context, chain) => {
3428
+ const { identity, state } = context;
3429
+ const metadata = metadataOf(operation);
3430
+ const grants = context.grants.map(({ grant }) => grant);
3431
+ const requestOf = (scope) => grantRequestOf({
3432
+ scope,
3433
+ slot: state.nextSlot
3434
+ }, identity, chain.now);
3435
+ switch (operation.type) {
3436
+ case "issue-grant": return issueEffectOf(issueCustodyGrant(state, requestOf(operation.scope), grants), context, chain);
3437
+ case "replace-grant": return issueEffectOf(replaceCustodyGrant(state, {
3438
+ ...requestOf(operation.scope),
3439
+ slot: operation.slot
3440
+ }, grants), context, chain);
3441
+ case "revoke-grant": return {
3442
+ state: transitioned(revokeCustodyGrant(state, operation.slot, grants)),
3443
+ outputs: []
3444
+ };
3445
+ case "revoke-all-grants": return {
3446
+ state: revokeAllCustodyGrants(state),
3447
+ outputs: []
3448
+ };
3449
+ case "sweep-grants": return sweepEffectOf(operation.slots, context, chain);
3450
+ case "add-device": return {
3451
+ state: transitioned(addCustodyDevice(state, operation.keyHash, grants)),
3452
+ outputs: []
3453
+ };
3454
+ case "remove-device": return {
3455
+ state: transitioned(removeCustodyDevice(state, operation.keyHash)),
3456
+ outputs: []
3457
+ };
3458
+ case "owner-spend": return {
3459
+ state,
3460
+ outputs: operation.outputs,
3461
+ ...metadata && { metadata }
3462
+ };
3463
+ case "delegate": return {
3464
+ state,
3465
+ outputs: [],
3466
+ poolId: poolIdOf(operation.poolId)
3467
+ };
3468
+ case "withdraw-rewards": {
3469
+ const { withdrawableAmount } = await readFromProvider("Custody reward account", () => chain.provider.getRewardAccountInfo({ rewardAccount: CardanoRewardAccount(identity.rewardAccount) }, { chainId: chain.chainId }));
3470
+ const withdrawal = BigNumber.valueOf(withdrawableAmount);
3471
+ return withdrawal > 0n ? {
3472
+ state,
3473
+ outputs: [],
3474
+ withdrawal
3475
+ } : failCustodyBuild({
3476
+ code: "invalid-operation",
3477
+ reason: "no-rewards",
3478
+ message: "The custody account has no rewards to withdraw"
3479
+ });
3480
+ }
3481
+ }
3482
+ };
3483
+ const sweptGrantsOf = ({ effect }) => effect.swept?.grants ?? [];
3484
+ /**
3485
+ * The lovelace the withdrawals add to the transaction: the rewards an
3486
+ * operation withdraws and whatever the logic run draws. The change or the
3487
+ * control output keeps it at the account address.
3488
+ */
3489
+ const withdrawnOf = ({ effect, logicRun }) => (effect.withdrawal ?? 0n) + logicRun.withdrawal.quantity;
3490
+ /** The transaction's plan with `funds` paying and the control output at `controlLovelace`. */
3491
+ const planOf = (build, funds, controlLovelace) => {
3492
+ const { context, chain, effect, signer, funding, issued, witnesses } = build;
3493
+ const { identity, control } = context;
3494
+ const swept = sweptGrantsOf(build);
3495
+ const isStakeOperation = effect.poolId !== void 0 || effect.withdrawal !== void 0;
3496
+ const mint = issued ? {
3497
+ assets: new Map([[custodyGrantAssetId(context.record, issued.grant.slot), 1n]]),
3498
+ redeemer: ISSUE_REDEEMER
3499
+ } : swept.length > 0 ? {
3500
+ assets: new Map(swept.map(({ token }) => [token, -1n])),
3501
+ redeemer: BURN_REDEEMER
3502
+ } : void 0;
3503
+ return {
3504
+ inputs: [
3505
+ {
3506
+ utxo: control,
3507
+ redeemer: DEVICE_REDEEMER
3508
+ },
3509
+ ...swept.map(({ utxo }) => ({
3510
+ utxo,
3511
+ redeemer: SWEEP_REDEEMER
3512
+ })),
3513
+ ...funds.map((utxo) => ({
3514
+ utxo,
3515
+ redeemer: FUND_REDEEMER
3516
+ }))
3517
+ ],
3518
+ outputs: [
3519
+ controlOutputOf(identity, effect.state, controlLovelace),
3520
+ ...issued ? [issued.output] : [],
3521
+ ...effect.outputs
3522
+ ],
3523
+ referenceInputs: witnesses.referenced,
3524
+ changeAddress: identity.address,
3525
+ scripts: [...witnesses.attached, ...isStakeOperation ? [identity.stakeScript] : []],
3526
+ executionEstimate: custodyExecutionEstimate({
3527
+ device: 1,
3528
+ sweepGrant: swept.length,
3529
+ fund: funds.length,
3530
+ issueGrants: issued ? 1 : 0,
3531
+ burnGrants: swept.length > 0 ? 1 : 0,
3532
+ operate: isStakeOperation ? 1 : 0,
3533
+ logic: "owner"
3534
+ }),
3535
+ requiredSigner: signer,
3536
+ ...effect.swept?.invalidBefore !== void 0 && { invalidBefore: effect.swept.invalidBefore },
3537
+ invalidHereafter: validitySlotFor(funding.validUntil, chain),
3538
+ collateral: {
3539
+ utxo: funding.collateralUtxo,
3540
+ returnAddress: funding.sponsorAddress
3541
+ },
3542
+ ...mint && { mint },
3543
+ ...effect.poolId && { delegation: {
3544
+ stakeCredential: scriptCredentialOf(identity.stakeScriptHash),
3545
+ poolId: effect.poolId,
3546
+ redeemer: encodeCustodyStakeRedeemer()
3547
+ } },
3548
+ withdrawals: [build.logicRun.withdrawal, ...effect.withdrawal ? [{
3549
+ rewardAccount: identity.rewardAccount,
3550
+ quantity: effect.withdrawal,
3551
+ redeemer: encodeCustodyStakeRedeemer()
3552
+ }] : []],
3553
+ ...effect.metadata && { metadata: effect.metadata }
3554
+ };
3555
+ };
3556
+ /**
3557
+ * Moves `amount` lovelace from the change into the control output once the
3558
+ * fee is final. A change left below its minimum ADA joins the control
3559
+ * output whole, when it holds no token.
3560
+ *
3561
+ * @throws `FundShortfall` when the change cannot spare `amount`; once no
3562
+ * fund is left the build fails with `owner-reserve-required`, or with
3563
+ * `insufficient-funds` when there is no change at all
3564
+ */
3565
+ const topUpControl = (transaction, { context, chain, effect, issued }, amount) => {
3566
+ const outputs = [...transaction.body().toCore().outputs];
3567
+ const [control] = outputs;
3568
+ const changeIndex = changeIndexOf(outputs, context.identity.address, 1 + (issued ? 1 : 0) + effect.outputs.length);
3569
+ if (changeIndex < 0) throw new FundShortfall("No change output to top the reserve up from");
3570
+ const change = outputs[changeIndex];
3571
+ const left = change.value.coins - amount;
3572
+ const kept = minAdaRequired(change, BigInt(chain.protocolParameters.coinsPerUtxoByte));
3573
+ const hasTokens = (change.value.assets?.size ?? 0) > 0;
3574
+ if (left >= kept) {
3575
+ outputs[0] = withCoins(control, control.value.coins + amount);
3576
+ outputs[changeIndex] = withCoins(change, left);
3577
+ } else if (!hasTokens && left >= 0n) {
3578
+ outputs[0] = withCoins(control, control.value.coins + change.value.coins);
3579
+ outputs.splice(changeIndex, 1);
3580
+ } else throw new FundShortfall("The change cannot top the owner fee reserve up", {
3581
+ code: "owner-reserve-required",
3582
+ target: CUSTODY_OWNER_FEE_RESERVE_TARGET,
3583
+ shortfall: amount - (hasTokens ? change.value.coins - kept : change.value.coins)
3584
+ });
3585
+ return {
3586
+ transaction: withOutputs(transaction, outputs),
3587
+ controlLovelace: outputs[0].value.coins
3588
+ };
3589
+ };
3590
+ /**
3591
+ * Plain funds, then reserves, largest first, pay the requested outputs, the
3592
+ * issued grant's lovelace, any growth of the control output and the fee, and
3593
+ * the change, rewards included, returns to the account without a datum. A
3594
+ * reserve's remainder becomes plain change. Swept grant lovelace goes to the
3595
+ * control output, and an issue tops the control headroom up to the owner fee
3596
+ * reserve target from the change.
3597
+ *
3598
+ * A fund carrying a reference script is never spent. Anyone can send one to
3599
+ * the account. Where the proxy and the logic are attached, such a fund
3600
+ * holding one of them would make the ledger refuse every owner operation
3601
+ * for providing the script twice, instead of falling back to the control
3602
+ * headroom; anywhere, its script would add to the fee and, for the fee
3603
+ * sponsor, to the languages of the script data hash.
3604
+ */
3605
+ const buildPaidByFunds = async (build) => {
3606
+ const { context, chain, effect, issued, minimum } = build;
3607
+ const { identity } = context;
3608
+ const spendable = context.funds.filter(([, output]) => output.scriptReference === void 0);
3609
+ const { protocolParameters: parameters } = chain;
3610
+ const swept = sweptGrantsOf(build);
3611
+ const controlLovelace = build.spentAccountLovelace > minimum ? build.spentAccountLovelace : minimum;
3612
+ const reserveTarget = minimum + CUSTODY_OWNER_FEE_RESERVE_TARGET;
3613
+ const topUp = issued && reserveTarget > controlLovelace ? reserveTarget - controlLovelace : 0n;
3614
+ const requested = totalValueOf(effect.outputs);
3615
+ const needed = requested.coins + (issued?.output.value.coins ?? 0n) + (controlLovelace - build.spentAccountLovelace) + topUp + plannedFeeOf(planOf(build, [], controlLovelace), parameters) + changeReserveAt(identity.address, parameters.coinsPerUtxoByte) - withdrawnOf(build);
3616
+ let settledLovelace = controlLovelace;
3617
+ return {
3618
+ transaction: await buildWithEvaluatedBudgets({
3619
+ chain,
3620
+ funds: selectFunds(spendable, {
3621
+ coins: needed > 0n ? needed : 0n,
3622
+ assets: requested.assets
3623
+ }, context.reserves),
3624
+ plan: (funds) => planOf(build, funds, controlLovelace),
3625
+ ...topUp > 0n && { settle: (built) => {
3626
+ const settled = topUpControl(built, build, topUp);
3627
+ settledLovelace = settled.controlLovelace;
3628
+ return settled.transaction;
3629
+ } }
3630
+ }),
3631
+ controlLovelace: settledLovelace,
3632
+ candidates: [
3633
+ ...spendable,
3634
+ ...context.reserves,
3635
+ ...swept.map(({ utxo }) => utxo)
3636
+ ]
3637
+ };
3638
+ };
3639
+ const headroomShortfall = () => failCustodyBuild({
3640
+ code: "insufficient-funds",
3641
+ message: "The plain funds and reserves cannot pay the fee, and the control output headroom cannot either"
3642
+ });
3643
+ /**
3644
+ * The control output pays the fee from its headroom: the transaction spends
3645
+ * only the control UTxO and any swept grant UTxOs, and the control output
3646
+ * takes everything they hold and whatever the withdrawals add, less the
3647
+ * fee. It must keep the new state's minimum and the growth margin, which a
3648
+ * headroom below the least fee never keeps, so such a build is refused
3649
+ * before any evaluation.
3650
+ */
3651
+ const buildPaidByControl = async (build) => {
3652
+ const { chain, minimum, spentAccountLovelace } = build;
3653
+ const held = spentAccountLovelace + withdrawnOf(build);
3654
+ const plan = {
3655
+ ...planOf(build, [], held),
3656
+ isFeePaidByFirstOutput: true
3657
+ };
3658
+ const floor = minimum + BigInt(CUSTODY_CONTROL_GROWTH_MARGIN_BYTES) * BigInt(chain.protocolParameters.coinsPerUtxoByte);
3659
+ if (held - leastFeeOf(plan, chain.protocolParameters) < floor) return headroomShortfall();
3660
+ const transaction = await buildWithEvaluatedBudgets({
3661
+ chain,
3662
+ funds: {
3663
+ selected: [],
3664
+ remaining: []
3665
+ },
3666
+ plan: () => plan
3667
+ });
3668
+ const controlLovelace = held - transaction.body().fee();
3669
+ if (controlLovelace < floor) return headroomShortfall();
3670
+ return {
3671
+ transaction,
3672
+ controlLovelace,
3673
+ candidates: sweptGrantsOf(build).map(({ utxo }) => utxo)
3674
+ };
3675
+ };
3676
+ const isInsufficientFunds = (error) => error instanceof CustodyBuildFailure && error.reason.code === "insufficient-funds";
3677
+ /**
3678
+ * Builds an owner operation in the sponsor's collateral mode: the account
3679
+ * pays and the sponsor contributes only its shared collateral. The control
3680
+ * UTxO is spent with `Device` and recreated from the resulting state, the
3681
+ * logic its state names runs through its withdrawal, and the bound device
3682
+ * key is the only required signer. The proxy and the logic are read from
3683
+ * their parked UTxOs on a network that has them, and attached elsewhere; a
3684
+ * stake operation attaches the stake script.
3685
+ *
3686
+ * Plain funds and reserves pay first. When they cannot, an operation that
3687
+ * pays nothing out and issues no grant takes its fee from the control
3688
+ * output's headroom instead.
3689
+ */
3690
+ const buildCustodyOwnerOperation = async ({ operation, funding }, context, chain) => {
3691
+ const signer = requireListedDevice(context);
3692
+ const { identity, control } = context;
3693
+ const { coinsPerUtxoByte } = chain.protocolParameters;
3694
+ const effect = await ownerEffectOf(operation, context, chain);
3695
+ checkRequestedOutputs(effect.outputs, identity, coinsPerUtxoByte);
3696
+ const logicRun = await readCustodyLogicRun(accountLogicOf(context), chain);
3697
+ const { contractVersion } = identity;
3698
+ const swept = effect.swept?.grants ?? [];
3699
+ const { issued: grant } = effect;
3700
+ const issued = grant && {
3701
+ grant,
3702
+ output: grantOutputOf(identity, grant, outputLovelaceFor((lovelace) => grantOutputOf(identity, grant, lovelace), 0n, coinsPerUtxoByte))
3703
+ };
3704
+ const build = {
3705
+ context,
3706
+ chain,
3707
+ effect,
3708
+ signer,
3709
+ funding,
3710
+ ...issued && { issued },
3711
+ minimum: controlLovelaceFor({
3712
+ identity,
3713
+ state: effect.state,
3714
+ floor: 0n
3715
+ }, coinsPerUtxoByte),
3716
+ spentAccountLovelace: swept.reduce((total, { utxo: [, output] }) => total + output.value.coins, control[1].value.coins),
3717
+ logicRun,
3718
+ witnesses: custodyScriptWitnesses(contractVersion, chain, [contractVersion.accountValidator, logicRun.logic.script])
3719
+ };
3720
+ const isControlPayable = effect.outputs.length === 0 && !issued;
3721
+ let paid;
3722
+ try {
3723
+ paid = await buildPaidByFunds(build);
3724
+ } catch (error) {
3725
+ if (!isControlPayable || !isInsufficientFunds(error)) throw error;
3726
+ paid = await buildPaidByControl(build);
3727
+ }
3728
+ const { transaction } = paid;
3729
+ return finalizeCustodyBuild({
3730
+ transaction,
3731
+ resolvedInputs: resolvedInputsOf(transaction, [
3732
+ control,
3733
+ ...paid.candidates,
3734
+ ...build.witnesses.referenced,
3735
+ funding.collateralUtxo
3736
+ ])
3737
+ }, chain, {
3738
+ record: context.record,
3739
+ control: {
3740
+ state: effect.state,
3741
+ lovelace: paid.controlLovelace
3742
+ },
3743
+ issuedGrants: issued ? [{
3744
+ grant: issued.grant,
3745
+ lovelace: issued.output.value.coins
3746
+ }] : [],
3747
+ sweptSlots: swept.map(({ grant }) => grant.slot),
3748
+ signer,
3749
+ deviceKeyHash: signer,
3750
+ outflow: {
3751
+ requested: totalValueOf(effect.outputs),
3752
+ withdrawal: withdrawnOf(build)
3753
+ },
3754
+ funding
3755
+ });
3756
+ };
3757
+ //#endregion
3758
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/build-custody-transaction.ts
3759
+ const ACCOUNT_OPERATION_TYPES = new Set([
3760
+ "add-device",
3761
+ "delegate",
3762
+ "grant-spend",
3763
+ "issue-grant",
3764
+ "owner-spend",
3765
+ "remove-device",
3766
+ "replace-grant",
3767
+ "revoke-all-grants",
3768
+ "revoke-grant",
3769
+ "sweep-grants",
3770
+ "withdraw-rewards"
3771
+ ]);
3772
+ const refuseOperation = (type) => failCustodyBuild({
3773
+ code: "invalid-operation",
3774
+ reason: "unknown-operation",
3775
+ message: `Unknown custody operation ${type} for this request`
3776
+ });
3777
+ const refuseFundingMode = (mode) => failCustodyBuild({
3778
+ code: "invalid-operation",
3779
+ reason: "funding-mode",
3780
+ message: `Funding mode ${mode} does not fit the operation`
3781
+ });
3782
+ /**
3783
+ * Builds the custody transaction a request asks for. Creation runs in the
3784
+ * sponsor's fee mode on the current network. Every operation on an
3785
+ * existing account runs in collateral mode, with the account paying: the
3786
+ * sponsor's fee mode serves creation only.
3787
+ */
3788
+ const buildCustodyTransaction = async (request, sources) => {
3789
+ const chain = await loadCustodyChain(sources);
3790
+ if (!("accountId" in request)) {
3791
+ const { operation, funding } = request;
3792
+ if (operation.type !== "create") return refuseOperation(operation.type);
3793
+ if (funding.mode !== "fee") return refuseFundingMode(funding.mode);
3794
+ return buildCustodyCreation(operation, funding, chain);
3795
+ }
3796
+ const { accountId, operation, funding } = request;
3797
+ if (!ACCOUNT_OPERATION_TYPES.has(operation.type)) return refuseOperation(operation.type);
3798
+ if (funding.mode !== "collateral") return refuseFundingMode(funding.mode);
3799
+ const context = await resolveCustodyAccount(accountId, await sources.snapshot(accountId), chain);
3800
+ return operation.type === "grant-spend" ? buildCustodyGrantSpend({
3801
+ operation,
3802
+ funding
3803
+ }, context, chain) : buildCustodyOwnerOperation({
3804
+ operation,
3805
+ funding
3806
+ }, context, chain);
3807
+ };
3808
+ //#endregion
3809
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/creator-index-probe-side-effect.ts
3810
+ const isSoftIndex = (index) => Number.isSafeInteger(index) && index >= 0 && index < 2147483648;
3811
+ const probeErrorOf = (reason) => {
3812
+ if (reason.code === "unavailable" || reason.code === "build-failed") return reason;
3813
+ if (reason.code === "invalid-operation" && reason.reason === "invalid-device-key") return {
3814
+ ...reason,
3815
+ reason: reason.reason
3816
+ };
3817
+ return {
3818
+ code: "build-failed",
3819
+ message: `Unexpected creator index probe failure: ${reason.code}`
3820
+ };
3821
+ };
3822
+ const resultOf = ({ accountValidatorHash, creatorIndex }, state) => {
3823
+ const probed = {
3824
+ accountValidatorHash,
3825
+ creatorIndex
3826
+ };
3827
+ return state.status === "account" ? {
3828
+ ...probed,
3829
+ status: "account",
3830
+ record: state.record
3831
+ } : {
3832
+ ...probed,
3833
+ status: state.status
3834
+ };
3835
+ };
3836
+ const probe = async (request, { chainId, provider, logger }) => {
3837
+ const contractVersion = findCustodyContractVersion(request.accountValidatorHash);
3838
+ if (!contractVersion.ok) return {
3839
+ ok: false,
3840
+ error: contractVersion.error
3841
+ };
3842
+ if (!isSoftIndex(request.creatorIndex)) return {
3843
+ ok: false,
3844
+ error: {
3845
+ code: "invalid-operation",
3846
+ reason: "invalid-start-index",
3847
+ message: `Invalid creator index ${request.creatorIndex}`
3848
+ }
3849
+ };
3850
+ if (!chainId) return {
3851
+ ok: false,
3852
+ error: {
3853
+ code: "unavailable",
3854
+ message: "Cardano chain id not available"
3855
+ }
3856
+ };
3857
+ try {
3858
+ return {
3859
+ ok: true,
3860
+ result: resultOf(request, await readCustodyCreatorIndex({
3861
+ contractVersion: contractVersion.value,
3862
+ deviceAccountPublicKey: request.deviceAccountPublicKey,
3863
+ creatorIndex: request.creatorIndex
3864
+ }, {
3865
+ chainId,
3866
+ provider
3867
+ }))
3868
+ };
3869
+ } catch (error) {
3870
+ if (error instanceof CustodyBuildFailure) return {
3871
+ ok: false,
3872
+ error: probeErrorOf(error.reason)
3873
+ };
3874
+ logger.error("Custody creator index probe failed unexpectedly", error);
3875
+ return {
3876
+ ok: false,
3877
+ error: {
3878
+ code: "build-failed",
3879
+ message: messageOf(error)
3880
+ }
3881
+ };
3882
+ }
3883
+ };
3884
+ const makeCustodyCreatorIndexProbing = () => ({ cardanoContext: { probeCustodyCreatorIndex$ } }, { cardanoContext }, { cardanoProvider, actions, logger }) => {
3885
+ const answer = async (request) => probe(request, {
3886
+ chainId: await firstValueFrom(cardanoContext.selectChainId$),
3887
+ provider: cardanoProvider,
3888
+ logger
3889
+ });
3890
+ return probeCustodyCreatorIndex$.pipe(mergeMap(({ payload: request }) => from(answer(request)).pipe(map((outcome) => outcome.ok ? actions.cardanoContext.probeCustodyCreatorIndexCompleted({
3891
+ requestId: request.requestId,
3892
+ result: outcome.result
3893
+ }) : actions.cardanoContext.probeCustodyCreatorIndexFailed({
3894
+ requestId: request.requestId,
3895
+ error: outcome.error
3896
+ })))));
3897
+ };
3898
+ //#endregion
3899
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/build-side-effect.ts
3900
+ const failureOf = (error, logger) => {
3901
+ if (error instanceof CustodyBuildFailure) return error.reason;
3902
+ logger.error("Custody transaction build failed unexpectedly", error);
3903
+ return {
3904
+ code: "build-failed",
3905
+ message: messageOf(error)
3906
+ };
3907
+ };
3908
+ const makeCustodyTransactionBuilding = () => ({ cardanoContext: { buildCustodyTransaction$ } }, { cardanoContext, wallets }, { cardanoProvider, actions, logger }) => {
3909
+ const snapshot = async (accountId) => {
3910
+ const [allWallets, accountStateOf, settledUtxos] = await Promise.all([
3911
+ firstValueFrom(wallets.selectAll$),
3912
+ firstValueFrom(cardanoContext.selectCardanoCustodyAccountState$),
3913
+ firstValueFrom(cardanoContext.selectAccountUtxos$)
3914
+ ]);
3915
+ return {
3916
+ account: allWallets.flatMap((wallet) => wallet.accounts).filter(isCardanoCustodyAccount).find((account) => account.accountId === accountId),
3917
+ accountState: accountStateOf(accountId),
3918
+ settledUtxos: settledUtxos[accountId] ?? []
3919
+ };
3920
+ };
3921
+ const build = async (request) => {
3922
+ try {
3923
+ const [chainId, eraSummaries] = await Promise.all([firstValueFrom(cardanoContext.selectChainId$), firstValueFrom(cardanoContext.selectEraSummaries$)]);
3924
+ return {
3925
+ ok: true,
3926
+ result: await buildCustodyTransaction(Serializable.from(request), {
3927
+ chainId,
3928
+ eraSummaries,
3929
+ snapshot,
3930
+ provider: cardanoProvider,
3931
+ now: Date.now()
3932
+ })
3933
+ };
3934
+ } catch (error) {
3935
+ return {
3936
+ ok: false,
3937
+ error: failureOf(error, logger)
3938
+ };
3939
+ }
3940
+ };
3941
+ return buildCustodyTransaction$.pipe(mergeMap(({ payload: { requestId, request } }) => from(build(request)).pipe(map((outcome) => outcome.ok ? actions.cardanoContext.buildCustodyTransactionCompleted({
3942
+ requestId,
3943
+ result: outcome.result
3944
+ }) : actions.cardanoContext.buildCustodyTransactionFailed({
3945
+ requestId,
3946
+ error: outcome.error
3947
+ })))));
3948
+ };
3949
+ const custodyTransactionSideEffects = [makeCustodyTransactionBuilding(), makeCustodyCreatorIndexProbing()];
3950
+ //#endregion
3951
+ //#region ../../packages/lib/cnight-dust-designation/src/constants.ts
3952
+ const LOVELACE_FOR_REGISTRATION = 1586080n;
3953
+ //#endregion
3954
+ //#region ../../packages/lib/cnight-dust-designation/src/errors.ts
3955
+ const Ok = (value) => ({
3956
+ ok: true,
3957
+ value
3958
+ });
3959
+ const Err = (error) => ({
3960
+ ok: false,
3961
+ error
3962
+ });
3963
+ //#endregion
3964
+ //#region ../../packages/lib/cnight-dust-designation/src/value-objects/network-id.vo.ts
3965
+ const CardanoDustNetwork = {
3966
+ mainnet: "mainnet",
3967
+ testnet: "testnet",
3968
+ fromNetworkMagic: (networkMagic) => networkMagic === 764824073 ? CardanoDustNetwork.mainnet : CardanoDustNetwork.testnet,
3969
+ toNetworkId: (network) => network === CardanoDustNetwork.mainnet ? 1 : 0
3970
+ };
3971
+ //#endregion
3972
+ //#region ../../packages/lib/cnight-dust-designation/src/cnight-asset.ts
3973
+ const TESTNET_CNIGHT_POLICY_ID = PolicyId("d2dbff622e509dda256fedbd31ef6e9fd98ed49ad91d5c0e07f68af1");
3974
+ const TESTNET_CNIGHT_ASSET_NAME = AssetName("");
3975
+ const MAINNET_CNIGHT_POLICY_ID = PolicyId("0691b2fecca1ac4f53cb6dfb00b7013e561d1f34403b957cbb5af1fa");
3976
+ /** "NIGHT" in hex — the mainnet cNIGHT token is named NIGHT. */
3977
+ const MAINNET_CNIGHT_ASSET_NAME = AssetName("4e49474854");
3978
+ const getCnightAssetId = (network) => {
3979
+ const policyId = network === CardanoDustNetwork.mainnet ? MAINNET_CNIGHT_POLICY_ID : TESTNET_CNIGHT_POLICY_ID;
3980
+ const assetName = network === CardanoDustNetwork.mainnet ? MAINNET_CNIGHT_ASSET_NAME : TESTNET_CNIGHT_ASSET_NAME;
3981
+ return AssetId.fromParts(policyId, assetName);
3982
+ };
3983
+ //#endregion
3984
+ //#region ../../packages/lib/cnight-dust-designation/src/scripts/mainnet.ts
3985
+ const CNIGHT_GENERATES_DUST_MAINNET_CBOR = "590c8401010029800aba4aba2aba1aba0aab9faab9eaab9dab9cab9a488888888a60022a6600492119657870656374205b782c202e2e726573745d203d206c69737400168a9980124929657870656374205b506169722822222c2031295d203d20746f6b656e73207c3e20746f5f706169727300168a9980124936657870656374206275696c74696e2e6c656e6774685f6f665f62797465617272617928647573745f6164647265737329203c3d20333300168a998012495365787065637420447573744d617070696e67446174756d207b20635f77616c6c65742c20647573745f61646472657373207d203d0a2020202020202020202020202020202020206f75747075745f646174756d00168a998012493065787065637420447573744d617070696e67446174756d207b20635f77616c6c65742c202e2e207d203d20646174756d00164888896600330013009375401f2300d300e001911919800800801912cc00400629422b3001300330100018a518998010011808800a0144039370e90004dc3a40052232330010010032259800800c026264b300130040018800c4cc00c00cc04800900b1808000a01c9b874801122222229800980a003cc040dd51809803c88966002600e60246ea800e264646600200200844b30010018a508acc004cdc79bae30190010038a51899801001180d000a026405c6eb8c058c04cdd5001c4cc0200048cdd798098008022020911192cc004c020006264b3001001801c4c966002003004802401200913259800980d801c01a00a80c0dd7000a0363018001405860286ea80122b300130070018992cc00400600713259800800c01200900480244c9660026036007006802a030375c00280d8c060005016180a1baa0048012022404460246ea800d22223322323298009bab301b0019bac301b301c301c0019bac301b007980b9baa0039bac301b0049bab301b0024888889660026024007133225980099baf30230104c103d87980008acc00401a2a6603c9211b657870656374205b696e7075742c202e2e5d203d20696e7075747300168992cc0040062b300130153020375400313259800800c06e264b30010018992cc00400603b13259800800c07a03d01e80f44c966002605400719800802456600266e24dc6800a408519800802403601c80a2040811a03e809a03e8138dd7000a05430270014094604e00501c80e40720388140c09400502318109baa00180d203c80d406a03501a409864b300132330010010082259800800c528456600266ebcc098c08cdd51813000801c528c4cc008008c09c00502020488992cc00400603b13259800981380144c96600266e3d220100375c6046005159800980b800c4c8c96600264b30010018a99812a48130657870656374205b5f2c202e2e6e66745d203d2076616c7565207c3e20746f5f64696374207c3e20746f5f70616972730016899baf374c60560026e98cc0a4cdd81ba9009374c6605266ec13010140004c010101004bd6f7b63025eb7bdb1810281bab30290018992cc004c060c098dd5000c4c0a8c09cdd5000c54cc09524012165787065637420496e6c696e65446174756d28646174756d29203d20646174756d001640906052605400315330244901266578706563742069735f73696e676c65746f6e2876616c75652c20706f6c6963792c202222290016408c6052604a6ea8cc05c0248cdd7981498131baa30293026375400200466e95200233026375200c97ae080fa04280fa042375a604600301e4090604a0028118dd5981099801002119b8f375c6042002009153301f49016b6578706563740a202020206c6973742e616e79280a202020202020696e707574732c0a202020202020666e28696e7075743a20496e70757429207b20696e7075742e6f75747075745f7265666572656e6365203d3d206f6e655f73686f745f726566207d2c0a202020202900164078604660406ea8c08c01902144c96600266ebcdd31981199bb04c10140003750653001001804520004004444b30010028800c660020073028002acc004cdd7981398121baa302730243754603460486ea8c09c008012266e0000520018800a042400c81292f5bded8c0604266004008466e3cdd71810800802456600330013371e0069111c0691b2fecca1ac4f53cb6dfb00b7013e561d1f34403b957cbb5af1fa00a50a5140791980099b8f003489076d61696e6e657400a50a51407914a080f2294101e19ba548008cc088dd480125eb8101d1bae3021301e375401244646600200200644b300100180f44c96600260080031001899801801981380120403025001408d1598009807801c4c9660026024603c6ea800626464944ca6002003007802a002222598008014400633001003981380156600266ebcc098c08cdd5181318119baa301930233754604c00400b159800800c54cc0852413f657870656374205b4f7574707574207b20616464726573732c2076616c75652c20646174756d2c202e2e207d2c202e2e726573745d203d206f757470757473001689919912cc004cdd7981498131baa30290030088992cc0040062a6604a920133657870656374205b5f2c202e2e6e66745f69645d203d2076616c7565207c3e20746f5f64696374207c3e20746f5f706169727300168acc004cdd79ba6302b0010088992cc004c064c09cdd5000c4c966002003159800980e98141baa0018992cc00400604713259800800c4c9660020030258992cc00400604d026813409a264b300130320038cc0040122b3001337126e3400520428acc006600200901580b20388804c54cc0b12413b65787065637420636865636b5f6175746828635f77616c6c65742c2065787472615f7369676e61746f726965732c207769746864726177616c7329001640ad02840ad027406d02740bc6eb80050321817800a05a302f002812409204902440c0605a0028158c0a4dd5000c089026408a045022811205c302b3028375400315330264912865787065637420496e6c696e65446174756d286f75747075745f646174756d29203d20646174756d001640946054605600715330254901266578706563742061735f64617461286e66745f696429203d3d2065787065637465645f6e6674001640908140dd59814801454cc09124012d65787065637420616464726573732e7061796d656e745f63726564656e7469616c203d3d206f776e5f637265640016408c6050002605000460466ea8c09800502444005020200640906e98cc084cdd81811180f9baa0014c0103a14001004bd6f7b630454cc07524012265787065637420536372697074286f776e5f6861736829203d206f776e5f63726564001640706042603c6ea80262b3001301100389919912cc004c054c080dd5000c4c966002003159800980b18109baa0018992cc00400603713259800800c4c96600200301d8992cc00400603d01e80f407a264b3001302b0038cc004012264b3001301b3027375400313259800cc00401a021011405d15980099b880014800229462b30013301e01123375e60520020071301d0018a5040988132294102619192cc004c074c0a4dd5000c5200089bad302d302a37540028138c966002603a60526ea80062980103d87a8000899198008009bab302e302b375400444b30010018a6103d87a8000899192cc004cdc8a45000018acc004cdc7a44100001899ba548000cc0c0c0b80092f5c114c0103d87a800040ad133004004303200340ac6eb8c0b0004c0bc00502d204e323300100100d2259800800c5300103d87a8000899192cc004cdc8802800c56600266e3c014006266e9520003302f302d0024bd7045300103d87a800040a9133004004303100340a86eb8c0ac004c0b800502c1bae302b3028375400315330264919165787065637420536372697074286f776e5f686173682920617320736372697074203d0a202020202020202020206d7573745f66696e6428696e707574732c20666e28696e70757429207b20696e7075742e6f75747075745f7265666572656e6365203d3d206f7574707574207d292e6f75747075742e616464726573732e7061796d656e745f63726564656e7469616c001640946054604e6ea8c0a8c09cdd5180e98139baa3301900d23375e605660506ea800402603e80a203e8140dd7000a05630280014098605000501c80e40720388148c09800502418111baa00180d203e80d406a03501a409c604860426ea80062a6603e9211a65787065637420536f6d6528646174756d29203d20646174756d00164078604400260446046002603c6ea80262b30013370e9003001c5660026024603a6ea8c084c088c078dd5004c528c5901b45901b2036406c80d8603660360026034603400460306030006602e603000845268a99803a491856616c696461746f722072657475726e65642066616c7365001365640181";
3986
+ //#endregion
3987
+ //#region ../../packages/lib/cnight-dust-designation/src/scripts/testnet.ts
3988
+ const CNIGHT_GENERATES_DUST_TESTNET_CBOR = "590c6801010029800aba4aba2aba1aba0aab9faab9eaab9dab9cab9a488888888a60022a6600492119657870656374205b782c202e2e726573745d203d206c69737400168a9980124929657870656374205b506169722822222c2031295d203d20746f6b656e73207c3e20746f5f706169727300168a9980124936657870656374206275696c74696e2e6c656e6774685f6f665f62797465617272617928647573745f6164647265737329203c3d20333300168a998012495365787065637420447573744d617070696e67446174756d207b20635f77616c6c65742c20647573745f61646472657373207d203d0a2020202020202020202020202020202020206f75747075745f646174756d00168a998012493065787065637420447573744d617070696e67446174756d207b20635f77616c6c65742c202e2e207d203d20646174756d00164888896600330013009375401f2300d300e001911919800800801912cc00400629422b3001300330100018a518998010011808800a0144039370e90004dc3a40052232330010010032259800800c026264b300130040018800c4cc00c00cc04800900b1808000a01c9b874801122222229800980a003cc040dd51809803c88966002600e60246ea800e264646600200200844b30010018a508acc004cdc79bae30190010038a51899801001180d000a026405c6eb8c058c04cdd5001c4cc0200048cdd798098008022020911192cc004c020006264b3001001801c4c966002003004802401200913259800980d801c01a00a80c0dd7000a0363018001405860286ea80122b300130070018992cc00400600713259800800c01200900480244c9660026036007006802a030375c00280d8c060005016180a1baa0048012022404460246ea800d22223322323298009bab301b0019bac301b301c301c0019bac301b007980b9baa0039bac301b0049bab301b0024888889660026024007133225980099baf30230104c103d87980008acc00401a2a6603c9211b657870656374205b696e7075742c202e2e5d203d20696e7075747300168992cc0040062b300130153020375400313259800800c06e264b30010018992cc00400603b13259800800c07a03d01e80f44c966002605400719800802456600266e24dc6800a408519800802403601c80a2040811a03e809a03e8138dd7000a05430270014094604e00501c80e40720388140c09400502318109baa00180d203c80d406a03501a409864b300132330010010082259800800c528456600266ebcc098c08cdd51813000801c528c4cc008008c09c00502020488992cc00400603b13259800981380144c96600266e3d220100375c6046005159800980b800c4c8c96600264b30010018a99812a48130657870656374205b5f2c202e2e6e66745d203d2076616c7565207c3e20746f5f64696374207c3e20746f5f70616972730016899baf374c60560026e98cc0a4cdd81ba9009374c6605266ec13010140004c010101004bd6f7b63025eb7bdb1810281bab30290018992cc004c060c098dd5000c4c0a8c09cdd5000c54cc09524012165787065637420496e6c696e65446174756d28646174756d29203d20646174756d001640906052605400315330244901266578706563742069735f73696e676c65746f6e2876616c75652c20706f6c6963792c202222290016408c6052604a6ea8cc05c0248cdd7981498131baa30293026375400200466e95200233026375200c97ae080fa04280fa042375a604600301e4090604a0028118dd5981099801002119b8f375c6042002009153301f49016b6578706563740a202020206c6973742e616e79280a202020202020696e707574732c0a202020202020666e28696e7075743a20496e70757429207b20696e7075742e6f75747075745f7265666572656e6365203d3d206f6e655f73686f745f726566207d2c0a202020202900164078604660406ea8c08c01902144c96600266ebcdd31981199bb04c10140003750653001001804520004004444b30010028800c660020073028002acc004cdd7981398121baa302730243754603460486ea8c09c008012266e0000520018800a042400c81292f5bded8c0604266004008466e3cdd7181080080246600266e3c00d22011cd2dbff622e509dda256fedbd31ef6e9fd98ed49ad91d5c0e07f68af100a50a51407914a080f0cdd2a4004660446ea40092f5c080e8dd71810980f1baa0092232330010010032259800800c07a264b300130040018800c4cc00c00cc09c0090201812800a0468acc004c03c00e264b30013012301e3754003132324a2653001001803c0150011112cc00400a200319800801cc09c00ab30013375e604c60466ea8c098c08cdd5180c98119baa30260020058acc0040062a660429213f657870656374205b4f7574707574207b20616464726573732c2076616c75652c20646174756d2c202e2e207d2c202e2e726573745d203d206f757470757473001689919912cc004cdd7981498131baa30290030088992cc0040062a6604a920133657870656374205b5f2c202e2e6e66745f69645d203d2076616c7565207c3e20746f5f64696374207c3e20746f5f706169727300168acc004cdd79ba6302b0010088992cc004c064c09cdd5000c4c966002003159800980e98141baa0018992cc00400604713259800800c4c9660020030258992cc00400604d026813409a264b300130320038cc0040122b3001337126e3400520428acc006600200901580b20388804c54cc0b12413b65787065637420636865636b5f6175746828635f77616c6c65742c2065787472615f7369676e61746f726965732c207769746864726177616c7329001640ad02840ad027406d02740bc6eb80050321817800a05a302f002812409204902440c0605a0028158c0a4dd5000c089026408a045022811205c302b3028375400315330264912865787065637420496e6c696e65446174756d286f75747075745f646174756d29203d20646174756d001640946054605600715330254901266578706563742061735f64617461286e66745f696429203d3d2065787065637465645f6e6674001640908140dd59814801454cc09124012d65787065637420616464726573732e7061796d656e745f63726564656e7469616c203d3d206f776e5f637265640016408c6050002605000460466ea8c09800502444005020200640906e98cc084cdd81811180f9baa0014c0103a14001004bd6f7b630454cc07524012265787065637420536372697074286f776e5f6861736829203d206f776e5f63726564001640706042603c6ea80262b3001301100389919912cc004c054c080dd5000c4c966002003159800980b18109baa0018992cc00400603713259800800c4c96600200301d8992cc00400603d01e80f407a264b3001302b0038cc004012264b3001301b3027375400313259800cc00401a021011405d15980099b880014800229462b30013301e01123375e60520020071301d0018a5040988132294102619192cc004c074c0a4dd5000c5200089bad302d302a37540028138c966002603a60526ea80062980103d87a8000899198008009bab302e302b375400444b30010018a6103d87a8000899192cc004cdc8a45000018acc004cdc7a44100001899ba548000cc0c0c0b80092f5c114c0103d87a800040ad133004004303200340ac6eb8c0b0004c0bc00502d204e323300100100d2259800800c5300103d87a8000899192cc004cdc8802800c56600266e3c014006266e9520003302f302d0024bd7045300103d87a800040a9133004004303100340a86eb8c0ac004c0b800502c1bae302b3028375400315330264919165787065637420536372697074286f776e5f686173682920617320736372697074203d0a202020202020202020206d7573745f66696e6428696e707574732c20666e28696e70757429207b20696e7075742e6f75747075745f7265666572656e6365203d3d206f7574707574207d292e6f75747075742e616464726573732e7061796d656e745f63726564656e7469616c001640946054604e6ea8c0a8c09cdd5180e98139baa3301900d23375e605660506ea800402603e80a203e8140dd7000a05630280014098605000501c80e40720388148c09800502418111baa00180d203e80d406a03501a409c604860426ea80062a6603e9211a65787065637420536f6d6528646174756d29203d20646174756d00164078604400260446046002603c6ea80262b30013370e9003001c5660026024603a6ea8c084c088c078dd5004c528c5901b45901b2036406c80d8603660360026034603400460306030006602e603000845268a99803a491856616c696461746f722072657475726e65642066616c7365001365640181";
3989
+ //#endregion
3990
+ //#region ../../packages/lib/cnight-dust-designation/src/scripts/index.ts
3991
+ const cborWrapHexBytes = (payloadHex) => {
3992
+ const lengthBytes = payloadHex.length / 2;
3993
+ let header;
3994
+ if (lengthBytes < 24) header = (64 | lengthBytes).toString(16).padStart(2, "0");
3995
+ else if (lengthBytes < 256) header = "58" + lengthBytes.toString(16).padStart(2, "0");
3996
+ else if (lengthBytes < 65536) header = "59" + lengthBytes.toString(16).padStart(4, "0");
3997
+ else if (lengthBytes < 4294967296) header = "5a" + lengthBytes.toString(16).padStart(8, "0");
3998
+ else throw new Error(`script too large to CBOR-wrap (${lengthBytes} bytes)`);
3999
+ return header + payloadHex;
4000
+ };
4001
+ const getDustGeneratorScriptCbor = (network) => HexBlob(cborWrapHexBytes(network === CardanoDustNetwork.mainnet ? CNIGHT_GENERATES_DUST_MAINNET_CBOR : CNIGHT_GENERATES_DUST_TESTNET_CBOR));
4002
+ const getDustGeneratorPlutusV3Script = (network) => PlutusV3Script.fromCbor(getDustGeneratorScriptCbor(network));
4003
+ //#endregion
4004
+ //#region ../../packages/lib/cnight-dust-designation/src/plutus/datum.ts
4005
+ const encodeCWallet = (cWallet) => {
4006
+ const fields = new PlutusList();
4007
+ const hashBytes = cWallet.kind === "verificationKey" ? cWallet.stakeKeyHash : cWallet.scriptHash;
4008
+ fields.add(PlutusData.newBytes(hashBytes));
4009
+ const alternative = cWallet.kind === "verificationKey" ? 0n : 1n;
4010
+ return PlutusData.newConstrPlutusData(new ConstrPlutusData(alternative, fields));
4011
+ };
4012
+ const encodeDustMappingDatum = (value) => {
4013
+ const fields = new PlutusList();
4014
+ fields.add(encodeCWallet(value.cWallet));
4015
+ fields.add(PlutusData.newBytes(value.dustAddress));
4016
+ return PlutusData.newConstrPlutusData(new ConstrPlutusData(0n, fields));
4017
+ };
4018
+ const dustMappingDatumToCbor = (value) => encodeDustMappingDatum(value).toCbor();
4019
+ var CardanoStakeKeyHashError = class extends Error {
4020
+ code;
4021
+ constructor(message) {
4022
+ super(message);
4023
+ this.name = "CardanoStakeKeyHashError";
4024
+ this.code = "invalid-length";
4025
+ }
4026
+ };
4027
+ const CardanoStakeKeyHash = (bytes) => {
4028
+ if (bytes.length !== 28) throw new CardanoStakeKeyHashError(`CardanoStakeKeyHash expects 28 bytes, got ${bytes.length}`);
4029
+ return bytes;
4030
+ };
4031
+ CardanoStakeKeyHash.toHex = (hash) => Array.from(hash).map((b) => b.toString(16).padStart(2, "0")).join("");
4032
+ CardanoStakeKeyHash.fromHex = (hex) => {
4033
+ const normalised = hex.startsWith("0x") ? hex.slice(2) : hex;
4034
+ if (normalised.length !== 56) throw new CardanoStakeKeyHashError(`CardanoStakeKeyHash hex expects 56 chars, got ${normalised.length}`);
4035
+ if (!/^[0-9a-fA-F]+$/.test(normalised)) throw new CardanoStakeKeyHashError(`CardanoStakeKeyHash hex contains non-hex characters: ${hex}`);
4036
+ const bytes = new Uint8Array(28);
4037
+ for (let index = 0; index < bytes.length; index++) bytes[index] = parseInt(normalised.slice(index * 2, index * 2 + 2), 16);
4038
+ return CardanoStakeKeyHash(bytes);
4039
+ };
4040
+ var MidnightCoinPubkeyError = class extends Error {
4041
+ code;
4042
+ constructor(code, message) {
4043
+ super(message);
4044
+ this.name = "MidnightCoinPubkeyError";
4045
+ this.code = code;
4046
+ }
4047
+ };
4048
+ const MidnightCoinPubkey = (bytes) => {
4049
+ if (bytes.length > 33) throw new MidnightCoinPubkeyError("invalid-length", `MidnightCoinPubkey accepts at most 33 bytes, got ${bytes.length}`);
4050
+ return bytes;
4051
+ };
4052
+ MidnightCoinPubkey.toHex = (pubkey) => Array.from(pubkey).map((b) => b.toString(16).padStart(2, "0")).join("");
4053
+ //#endregion
4054
+ //#region ../../packages/lib/cnight-dust-designation/src/plutus/datum-decode.ts
4055
+ const decodeDustMappingDatum = (cbor) => {
4056
+ let data;
4057
+ try {
4058
+ data = PlutusData.fromCbor(HexBlob(cbor.toString()));
4059
+ } catch {
4060
+ return;
4061
+ }
4062
+ const outer = data.asConstrPlutusData();
4063
+ if (!outer || outer.getAlternative() !== 0n) return void 0;
4064
+ const fields = outer.getData();
4065
+ if (fields.getLength() !== 2) return void 0;
4066
+ const cWalletData = fields.get(0).asConstrPlutusData();
4067
+ if (!cWalletData) return void 0;
4068
+ const cWalletFields = cWalletData.getData();
4069
+ if (cWalletFields.getLength() !== 1) return void 0;
4070
+ const credBytes = cWalletFields.get(0).asBoundedBytes();
4071
+ if (!credBytes || credBytes.length !== 28) return void 0;
4072
+ let cWallet;
4073
+ if (cWalletData.getAlternative() === 0n) cWallet = {
4074
+ kind: "verificationKey",
4075
+ stakeKeyHash: CardanoStakeKeyHash(credBytes)
4076
+ };
4077
+ else if (cWalletData.getAlternative() === 1n) cWallet = {
4078
+ kind: "script",
4079
+ scriptHash: credBytes
4080
+ };
4081
+ else return;
4082
+ const dustBytes = fields.get(1).asBoundedBytes();
4083
+ if (!dustBytes || dustBytes.length > 33) return void 0;
4084
+ let dustAddress;
4085
+ try {
4086
+ dustAddress = MidnightCoinPubkey(dustBytes);
4087
+ } catch {
4088
+ return;
4089
+ }
4090
+ return {
4091
+ cWallet,
4092
+ dustAddress
4093
+ };
4094
+ };
4095
+ /**
4096
+ * True when the datum's c_wallet VerificationKey hash matches the
4097
+ * given stake-key-hash. Used by the designation-detection hook to
4098
+ * find the active account's registration UTxO among the script
4099
+ * address's outputs.
4100
+ */
4101
+ const datumMatchesStakeKey = (datum, stakeKeyHash) => {
4102
+ if (datum.cWallet.kind !== "verificationKey") return false;
4103
+ if (datum.cWallet.stakeKeyHash.length !== stakeKeyHash.length) return false;
4104
+ for (let index = 0; index < stakeKeyHash.length; index++) if (datum.cWallet.stakeKeyHash[index] !== stakeKeyHash[index]) return false;
4105
+ return true;
4106
+ };
4107
+ //#endregion
4108
+ //#region ../../packages/lib/cnight-dust-designation/src/plutus/redeemer.ts
4109
+ const emptyConstr = (alternative) => PlutusData.newConstrPlutusData(new ConstrPlutusData(alternative, new PlutusList()));
4110
+ const dustActionCreate = () => emptyConstr(0n);
4111
+ const dustActionBurn = () => emptyConstr(1n);
4112
+ const dataVoid = () => emptyConstr(0n);
4113
+ const dustActionCreateCbor = () => dustActionCreate().toCbor();
4114
+ const dustActionBurnCbor = () => dustActionBurn().toCbor();
4115
+ const dataVoidCbor = () => dataVoid().toCbor();
4116
+ //#endregion
4117
+ //#region ../../packages/lib/cnight-dust-designation/src/plutus/script-address.ts
4118
+ const getDustGeneratorScriptHash = (network) => getDustGeneratorPlutusV3Script(network).hash();
4119
+ const getDustGeneratorPaymentAddress = (network) => {
4120
+ const scriptHash = getDustGeneratorScriptHash(network);
4121
+ const networkId = CardanoDustNetwork.toNetworkId(network);
4122
+ const credential = {
4123
+ type: CredentialType.ScriptHash,
4124
+ hash: scriptHash
4125
+ };
4126
+ return EnterpriseAddress.fromCredentials(networkId, credential).toAddress().toBech32();
4127
+ };
4128
+ const getDustGeneratorRewardAccount = (network) => {
4129
+ const scriptHash = getDustGeneratorScriptHash(network);
4130
+ const networkId = CardanoDustNetwork.toNetworkId(network);
4131
+ const credential = {
4132
+ type: CredentialType.ScriptHash,
4133
+ hash: scriptHash
4134
+ };
4135
+ return RewardAddress.fromCredentials(networkId, credential).toAddress().toBech32();
4136
+ };
4137
+ const getDustMappingNftPolicyId = (network) => PolicyId(getDustGeneratorScriptHash(network));
4138
+ /**
4139
+ * The DUST-mapping NFT minted by the validator has policy = script
4140
+ * hash and asset_name = empty bytestring. This is the on-chain
4141
+ * identifier callers query for to determine "is this account
4142
+ * designated?".
4143
+ */
4144
+ const getDustMappingNftAssetId = (network) => AssetId.fromParts(getDustMappingNftPolicyId(network), AssetName(""));
4145
+ //#endregion
4146
+ //#region ../../packages/lib/cnight-dust-designation/src/dust-address-decode.ts
4147
+ const COMPACT_MODE_1_MIN = 64n;
4148
+ const COMPACT_MODE_2_MIN = 1n << 14n;
4149
+ const COMPACT_MODE_3_MIN = 1n << 30n;
4150
+ const BLS12_381_SCALAR_MODULUS = BigInt("0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001");
4151
+ const leBytesToScalar = (bytes) => {
4152
+ let scalar = 0n;
4153
+ for (let index = bytes.length - 1; index >= 0; index--) scalar = scalar << 8n | BigInt(bytes[index]);
4154
+ return scalar;
4155
+ };
4156
+ /**
4157
+ * Why `payload` is not the canonical (minimal) SCALE-compact encoding of an
4158
+ * in-field BLS12-381 scalar, or `undefined` when it is.
4159
+ *
4160
+ * Minimality is ours to enforce: neither the Midnight SDK codec nor the
4161
+ * midnight-node pallet checks it, and a padded encoding's claimability is
4162
+ * unknown — so a non-canonical target must never reach the datum.
4163
+ *
4164
+ * Returns a reason instead of throwing so each boundary can surface it in its
4165
+ * own channel: this file throws at the address-string input boundary, the
4166
+ * blueprint builder returns `Err` at the datum-write boundary.
4167
+ */
4168
+ const canonicalDustPayloadDefect = (payload) => {
4169
+ if (payload.length === 0) return "empty payload";
4170
+ const header = payload[0];
4171
+ const mode = header & 3;
4172
+ let scalar;
4173
+ if (mode === 0) {
4174
+ if (payload.length !== 1) return `single-byte mode declared, ${payload.length}-byte payload`;
4175
+ scalar = BigInt(header >> 2);
4176
+ } else if (mode === 1) {
4177
+ if (payload.length !== 2) return `two-byte mode declared, ${payload.length}-byte payload`;
4178
+ scalar = BigInt(header >> 2) + (BigInt(payload[1]) << 6n);
4179
+ if (scalar < COMPACT_MODE_1_MIN) return `${scalar} fits a shorter mode`;
4180
+ } else if (mode === 2) {
4181
+ if (payload.length !== 4) return `four-byte mode declared, ${payload.length}-byte payload`;
4182
+ scalar = BigInt(header >> 2) + (BigInt(payload[1]) << 6n) + (BigInt(payload[2]) << 14n) + (BigInt(payload[3]) << 22n);
4183
+ if (scalar < COMPACT_MODE_2_MIN) return `${scalar} fits a shorter mode`;
4184
+ } else {
4185
+ const declaredScalarBytes = (header >> 2) + 4;
4186
+ if (payload.length !== declaredScalarBytes + 1) return `${declaredScalarBytes}-byte scalar declared, ${payload.length - 1} bytes present`;
4187
+ if (payload[payload.length - 1] === 0) return "most significant scalar byte is zero";
4188
+ scalar = leBytesToScalar(payload.subarray(1));
4189
+ if (scalar < COMPACT_MODE_3_MIN) return `${scalar} fits a shorter mode`;
4190
+ }
4191
+ return scalar >= BLS12_381_SCALAR_MODULUS ? "scalar is outside the BLS12-381 field" : void 0;
4192
+ };
4193
+ //#endregion
4194
+ //#region ../../packages/lib/cnight-dust-designation/src/builders/build-night-designation-tx.ts
4195
+ const NFT_ASSET_NAME = AssetName("");
4196
+ const computeScriptOutputLovelace = ({ scriptAddress, nftAsset, datumCbor, coinsPerUtxoByte }) => {
4197
+ const datum = PlutusData.fromCbor(datumCbor).toCore();
4198
+ const representativeOutput = {
4199
+ address: scriptAddress,
4200
+ value: {
4201
+ coins: LOVELACE_FOR_REGISTRATION,
4202
+ assets: new Map([[nftAsset, 1n]])
4203
+ },
4204
+ datum
4205
+ };
4206
+ const minCoin = computeMinimumCoinQuantity(coinsPerUtxoByte)(representativeOutput);
4207
+ return minCoin < 1586080n ? LOVELACE_FOR_REGISTRATION : minCoin;
4208
+ };
4209
+ const dustPubkeyError = (dustPubkey) => {
4210
+ if (dustPubkey.length > 33) return {
4211
+ code: "dust-address-too-long",
4212
+ message: `dust_address exceeds the on-chain 33-byte limit enforced by the validator.`,
4213
+ actualBytes: dustPubkey.length,
4214
+ maxBytes: 33
4215
+ };
4216
+ const defect = canonicalDustPayloadDefect(dustPubkey);
4217
+ return defect === void 0 ? void 0 : {
4218
+ code: "invalid-dust-payload",
4219
+ message: `dust_address is not a canonical SCALE-compact payload (${defect}).`
4220
+ };
4221
+ };
4222
+ const buildNightDesignationTxBlueprint = (params) => {
4223
+ if (params.cnightUtxos.length === 0) return Err({
4224
+ code: "no-cnight",
4225
+ message: "No cNIGHT in this account."
4226
+ });
4227
+ const scriptAddress = getDustGeneratorPaymentAddress(params.network);
4228
+ const scriptRewardAccount = getDustGeneratorRewardAccount(params.network);
4229
+ const nftPolicyId = getDustMappingNftPolicyId(params.network);
4230
+ const nftAsset = getDustMappingNftAssetId(params.network);
4231
+ const scriptCbor = getDustGeneratorScriptCbor(params.network);
4232
+ const requiredSigners = {
4233
+ paymentKeyHash: params.paymentKeyHash,
4234
+ stakeKeyHash: params.stakeKeyHash
4235
+ };
4236
+ if (params.action.kind === "register") {
4237
+ const dustError = dustPubkeyError(params.action.dustPubkey);
4238
+ if (dustError) return Err(dustError);
4239
+ const datumCbor = dustMappingDatumToCbor({
4240
+ cWallet: {
4241
+ kind: "verificationKey",
4242
+ stakeKeyHash: params.stakeKeyHash
4243
+ },
4244
+ dustAddress: params.action.dustPubkey
4245
+ });
4246
+ return Ok({
4247
+ network: params.network,
4248
+ scriptAddress,
4249
+ scriptRewardAccount,
4250
+ dustMappingNftAssetId: nftAsset,
4251
+ forcedInputs: params.cnightUtxos,
4252
+ scriptOutput: {
4253
+ address: scriptAddress,
4254
+ lovelace: computeScriptOutputLovelace({
4255
+ scriptAddress,
4256
+ nftAsset,
4257
+ datumCbor,
4258
+ coinsPerUtxoByte: params.coinsPerUtxoByte
4259
+ }),
4260
+ nftAsset,
4261
+ nftQuantity: 1n,
4262
+ inlineDatumCbor: datumCbor
4263
+ },
4264
+ mint: {
4265
+ policyId: nftPolicyId,
4266
+ assetName: NFT_ASSET_NAME,
4267
+ quantity: 1n,
4268
+ redeemerCbor: dustActionCreateCbor()
4269
+ },
4270
+ spendRedeemer: null,
4271
+ withdrawal: null,
4272
+ requiredSigners,
4273
+ scriptCbor
4274
+ });
4275
+ }
4276
+ if (params.action.kind === "update") {
4277
+ const dustError = dustPubkeyError(params.action.dustPubkey);
4278
+ if (dustError) return Err(dustError);
4279
+ const datumCbor = dustMappingDatumToCbor({
4280
+ cWallet: {
4281
+ kind: "verificationKey",
4282
+ stakeKeyHash: params.stakeKeyHash
4283
+ },
4284
+ dustAddress: params.action.dustPubkey
4285
+ });
4286
+ return Ok({
4287
+ network: params.network,
4288
+ scriptAddress,
4289
+ scriptRewardAccount,
4290
+ dustMappingNftAssetId: nftAsset,
4291
+ forcedInputs: [...params.cnightUtxos, params.action.registrationUtxo],
4292
+ scriptOutput: {
4293
+ address: scriptAddress,
4294
+ lovelace: computeScriptOutputLovelace({
4295
+ scriptAddress,
4296
+ nftAsset,
4297
+ datumCbor,
4298
+ coinsPerUtxoByte: params.coinsPerUtxoByte
4299
+ }),
4300
+ nftAsset,
4301
+ nftQuantity: 1n,
4302
+ inlineDatumCbor: datumCbor
4303
+ },
4304
+ mint: null,
4305
+ spendRedeemer: {
4306
+ registrationUtxo: params.action.registrationUtxo,
4307
+ redeemerCbor: dataVoidCbor()
4308
+ },
4309
+ withdrawal: {
4310
+ rewardAccount: scriptRewardAccount,
4311
+ lovelace: params.action.scriptWithdrawableLovelace,
4312
+ redeemerCbor: dataVoidCbor()
4313
+ },
4314
+ requiredSigners,
4315
+ scriptCbor
4316
+ });
4317
+ }
4318
+ return Ok({
4319
+ network: params.network,
4320
+ scriptAddress,
4321
+ scriptRewardAccount,
4322
+ dustMappingNftAssetId: nftAsset,
4323
+ forcedInputs: [...params.cnightUtxos, params.action.registrationUtxo],
4324
+ scriptOutput: null,
4325
+ mint: {
4326
+ policyId: nftPolicyId,
4327
+ assetName: NFT_ASSET_NAME,
4328
+ quantity: -1n,
4329
+ redeemerCbor: dustActionBurnCbor()
4330
+ },
4331
+ spendRedeemer: {
4332
+ registrationUtxo: params.action.registrationUtxo,
4333
+ redeemerCbor: dataVoidCbor()
4334
+ },
4335
+ withdrawal: null,
4336
+ requiredSigners,
4337
+ scriptCbor
4338
+ });
4339
+ };
4340
+ //#endregion
4341
+ //#region ../../packages/lib/cnight-dust-designation/src/value-objects/cardano-payment-key-hash.vo.ts
4342
+ var CardanoPaymentKeyHashError = class extends Error {
4343
+ code;
4344
+ constructor(message) {
4345
+ super(message);
4346
+ this.name = "CardanoPaymentKeyHashError";
4347
+ this.code = "invalid-length";
4348
+ }
4349
+ };
4350
+ const CardanoPaymentKeyHash = (bytes) => {
4351
+ if (bytes.length !== 28) throw new CardanoPaymentKeyHashError(`CardanoPaymentKeyHash expects 28 bytes, got ${bytes.length}`);
4352
+ return bytes;
4353
+ };
4354
+ CardanoPaymentKeyHash.toHex = (hash) => Array.from(hash).map((b) => b.toString(16).padStart(2, "0")).join("");
4355
+ CardanoPaymentKeyHash.fromHex = (hex) => {
4356
+ const normalised = hex.startsWith("0x") ? hex.slice(2) : hex;
4357
+ if (normalised.length !== 56) throw new CardanoPaymentKeyHashError(`CardanoPaymentKeyHash hex expects 56 chars, got ${normalised.length}`);
4358
+ if (!/^[0-9a-fA-F]+$/.test(normalised)) throw new CardanoPaymentKeyHashError(`CardanoPaymentKeyHash hex contains non-hex characters: ${hex}`);
4359
+ const bytes = new Uint8Array(28);
4360
+ for (let index = 0; index < bytes.length; index++) bytes[index] = parseInt(normalised.slice(index * 2, index * 2 + 2), 16);
4361
+ return CardanoPaymentKeyHash(bytes);
4362
+ };
4363
+ //#endregion
4364
+ //#region ../../packages/module/blockchain-cardano/src/store/night-designation/account-key-hashes.ts
4365
+ const hexToBytes = (hex) => {
4366
+ if (hex.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(hex)) throw new Error(`Invalid hex string: "${hex}"`);
4367
+ return Uint8Array.from((hex.match(/.{1,2}/g) ?? []).map((byte) => parseInt(byte, 16)));
4368
+ };
4369
+ /**
4370
+ * Read the account's credentials off its primary Cardano address.
4371
+ *
4372
+ * Throws when the account has no Cardano address or its address carries no
4373
+ * stake credential — an enterprise address cannot satisfy the validator's
4374
+ * `check_auth`, so there is no designation to build or index for it.
4375
+ */
4376
+ const resolveAccountKeyHashes = (addresses, accountId) => {
4377
+ const primary = addresses.find((address) => address.accountId === accountId && address.blockchainName === "Cardano");
4378
+ if (!primary) throw new Error("No Cardano addresses found for account");
4379
+ const baseAddress = Address.fromString(primary.address)?.asBase();
4380
+ if (!baseAddress) throw new Error("Account address is not a base (payment+stake) address");
4381
+ return {
4382
+ primaryAddress: PaymentAddress(primary.address),
4383
+ paymentKeyHash: CardanoPaymentKeyHash(hexToBytes(baseAddress.getPaymentCredential().hash)),
4384
+ stakeKeyHash: CardanoStakeKeyHash(hexToBytes(baseAddress.getStakeCredential().hash))
4385
+ };
4386
+ };
4387
+ //#endregion
4388
+ //#region ../../packages/module/blockchain-cardano/src/store/night-designation/build-night-designation-tx.ts
4389
+ const toPlutusData = (cbor) => PlutusData.fromCbor(cbor).toCore();
4390
+ const toKeyHashHex = (bytes) => Array.from(bytes).map((byte) => byte.toString(16).padStart(2, "0")).join("");
4391
+ const sameRef = (a, b) => a.txId === b.txId && a.index === b.index;
4392
+ const buildNightDesignationTx = async (params, dependencies) => {
4393
+ const blueprintResult = buildNightDesignationTxBlueprint({
4394
+ network: params.network,
4395
+ cnightUtxos: params.cnightUtxos,
4396
+ stakeKeyHash: params.stakeKeyHash,
4397
+ paymentKeyHash: params.paymentKeyHash,
4398
+ action: params.action,
4399
+ coinsPerUtxoByte: params.protocolParameters.coinsPerUtxoByte
4400
+ });
4401
+ if (!blueprintResult.ok) return blueprintResult;
4402
+ const blueprint = blueprintResult.value;
4403
+ const builder = new TransactionBuilder(dependencies.networkMagic, params.protocolParameters);
4404
+ builder.setChangeAddress(params.changeAddress).setUnspentOutputs(dependencies.coverUtxos).setCollateralUtxos(dependencies.coverUtxos).setCollateralChangeAddress(params.changeAddress).setPlutusContext({
4405
+ costModels: params.protocolParameters.costModels,
4406
+ txEvaluator: dependencies.txEvaluator,
4407
+ inputResolver: dependencies.inputResolver
4408
+ });
4409
+ builder.attachScript({
4410
+ __type: ScriptType.Plutus,
4411
+ version: PlutusLanguageVersion.V3,
4412
+ bytes: PlutusV3Script.fromCbor(blueprint.scriptCbor).rawBytes()
4413
+ });
4414
+ const registrationRef = blueprint.spendRedeemer?.registrationUtxo[0];
4415
+ for (const utxo of blueprint.forcedInputs) if (blueprint.spendRedeemer && registrationRef && sameRef(utxo[0], registrationRef)) builder.addInput(utxo, { redeemer: toPlutusData(blueprint.spendRedeemer.redeemerCbor) });
4416
+ else builder.addInput(utxo);
4417
+ if (blueprint.mint) {
4418
+ const assetId = AssetId.fromParts(blueprint.mint.policyId, blueprint.mint.assetName);
4419
+ builder.mintAssets(new Map([[assetId, blueprint.mint.quantity]]), toPlutusData(blueprint.mint.redeemerCbor));
4420
+ }
4421
+ if (blueprint.scriptOutput) builder.addOutput({
4422
+ address: blueprint.scriptOutput.address,
4423
+ value: {
4424
+ coins: blueprint.scriptOutput.lovelace,
4425
+ assets: new Map([[blueprint.scriptOutput.nftAsset, blueprint.scriptOutput.nftQuantity]])
4426
+ },
4427
+ datum: toPlutusData(blueprint.scriptOutput.inlineDatumCbor)
4428
+ });
4429
+ if (blueprint.withdrawal) builder.addScriptWithdrawal(blueprint.withdrawal.rewardAccount, blueprint.withdrawal.lovelace, toPlutusData(blueprint.withdrawal.redeemerCbor));
4430
+ builder.addRequiredSigner(toKeyHashHex(blueprint.requiredSigners.paymentKeyHash));
4431
+ builder.addRequiredSigner(toKeyHashHex(blueprint.requiredSigners.stakeKeyHash));
4432
+ builder.setValidityInterval({ invalidHereafter: params.ttlSlot });
4433
+ const transaction = await builder.build();
4434
+ const cbor = transaction.toCbor();
4435
+ const fee = transaction.body().fee();
4436
+ return Ok({
4437
+ cbor,
4438
+ txId: transaction.getId(),
4439
+ fee
4440
+ });
4441
+ };
4442
+ //#endregion
4443
+ //#region ../../packages/module/blockchain-cardano/src/store/night-designation/bounded-ex-units-evaluator.ts
4444
+ const BOUNDED_EX_UNITS_PER_REDEEMER = {
4445
+ memory: 2e6,
4446
+ steps: 7e8
4447
+ };
4448
+ /**
4449
+ * A {@link TxEvaluator} that returns a fixed, conservative budget for
4450
+ * every redeemer in the transaction (no network round-trip).
4451
+ */
4452
+ const boundedExUnitsEvaluator = { evaluate: async (tx) => (tx.witness.redeemers ?? []).map((redeemer) => ({
4453
+ purpose: redeemer.purpose,
4454
+ index: redeemer.index,
4455
+ budget: BOUNDED_EX_UNITS_PER_REDEEMER
4456
+ })) };
4457
+ //#endregion
4458
+ //#region ../../packages/module/blockchain-cardano/src/store/night-designation/provider-ex-units-evaluator.ts
4459
+ /**
4460
+ * Padding on every evaluated budget. The builder rewrites the fee (and the
4461
+ * change output that absorbs it) AFTER evaluation, so the script context the
4462
+ * node executes is not byte-identical to the one it priced. Under-declaring
4463
+ * fails phase-2 and forfeits the collateral, so the difference is paid for
4464
+ * rather than gambled on.
4465
+ */
4466
+ const EX_UNITS_MARGIN_PERCENT = 10;
4467
+ const withMargin = (budget) => ({
4468
+ memory: Math.ceil(budget.memory * (100 + EX_UNITS_MARGIN_PERCENT) / 100),
4469
+ steps: Math.ceil(budget.steps * (100 + EX_UNITS_MARGIN_PERCENT) / 100)
4470
+ });
4471
+ /**
4472
+ * A {@link TxEvaluator} that prices redeemers through the Cardano provider,
4473
+ * leaving any redeemer the provider did not price on the fixed budget
4474
+ * `boundedExUnitsEvaluator` assigns it.
4475
+ */
4476
+ const createProviderExUnitsEvaluator = ({ cardanoProvider, chainId, logger }) => {
4477
+ const evaluate = async (tx) => {
4478
+ try {
4479
+ const result = await firstValueFrom(cardanoProvider.evaluateTx({ tx: Transaction.fromCore(tx).toCbor() }, { chainId }));
4480
+ if (result.isOk()) return result.value;
4481
+ logger.warn("cNIGHT designation: ex-units evaluation declined, using the bounded budget", result.error);
4482
+ } catch (error) {
4483
+ logger.warn("cNIGHT designation: ex-units evaluation failed, using the bounded budget", error);
4484
+ }
4485
+ };
4486
+ return { evaluate: async (tx, resolvedInputs) => {
4487
+ const bounded = await boundedExUnitsEvaluator.evaluate(tx, resolvedInputs);
4488
+ if (bounded.length === 0) return bounded;
4489
+ const evaluated = await evaluate(tx);
4490
+ return bounded.map((entry) => {
4491
+ const match = evaluated?.find((budget) => budget.purpose === entry.purpose && budget.index === entry.index);
4492
+ return match ? {
4493
+ ...entry,
4494
+ budget: withMargin(match.budget)
4495
+ } : entry;
4496
+ });
4497
+ } };
4498
+ };
4499
+ //#endregion
4500
+ //#region ../../packages/module/blockchain-cardano/src/store/night-designation/registration-utxo.ts
4501
+ const decodeDatumOrSkip = (datum) => {
4502
+ try {
4503
+ return decodeDustMappingDatum(PlutusData.fromCore(datum).toCbor());
4504
+ } catch {
4505
+ return;
4506
+ }
4507
+ };
4508
+ /**
4509
+ * Pick the account's registration out of the UTxOs sitting at the dust
4510
+ * generator's script address.
4511
+ *
4512
+ * A registration is identified by the singleton DUST-mapping NFT plus an
4513
+ * inline `DustMappingDatum` whose `c_wallet` verification-key hash is the
4514
+ * account's stake key hash. Undecodable datums belong to other accounts (or
4515
+ * to a future datum revision) and are skipped rather than treated as errors.
4516
+ */
4517
+ const findRegistrationUtxo = (scriptUtxos, nftAssetId, stakeKeyHash) => {
4518
+ for (const utxo of scriptUtxos) {
4519
+ const [, out] = utxo;
4520
+ if (out.value.assets?.get(nftAssetId) !== 1n) continue;
4521
+ if (!out.datum) continue;
4522
+ const datum = decodeDatumOrSkip(out.datum);
4523
+ if (datum && datumMatchesStakeKey(datum, stakeKeyHash)) return {
4524
+ utxo,
4525
+ datum
4526
+ };
4527
+ }
4528
+ };
4529
+ //#endregion
4530
+ //#region ../../packages/module/blockchain-cardano/src/store/night-designation/build-side-effect.ts
4531
+ const TTL_BUFFER_SLOTS = 7200;
4532
+ const LIVE_PENDING_WINDOW_MS = TTL_BUFFER_SLOTS * 1e3 + 6e5;
4533
+ const PENDING_READ_TIMEOUT_MS = 2e3;
4534
+ const NO_PENDING_ACTIVITIES = [];
4535
+ /**
4536
+ * Only `designate` counts: an update/deregister with no confirmed marker
4537
+ * already fails `no-registration-utxo`, and with one the scan already refuses
4538
+ * a designate — so widening this over-blocks for no safety gain.
4539
+ */
4540
+ const hasLivePendingDesignate = (pendingActivities) => pendingActivities.some((activity) => {
4541
+ if (activity.blockchainSpecific?.Cardano?.nightDesignation?.action !== "designate") return false;
4542
+ return Date.now() - activity.timestamp < LIVE_PENDING_WINDOW_MS;
4543
+ });
4544
+ const BUILD_ERROR_KEYS_BY_CODE = {
4545
+ "already-registered": {
4546
+ title: "v2.cnight-designation.build.error.already-registered.title",
4547
+ subtitle: "v2.cnight-designation.build.error.already-registered.subtitle"
4548
+ },
4549
+ "no-cnight": {
4550
+ title: "v2.cnight-designation.build.error.no-cnight.title",
4551
+ subtitle: "v2.cnight-designation.build.error.no-cnight.subtitle"
4552
+ },
4553
+ "no-cardano-utxos": {
4554
+ title: "v2.cnight-designation.build.error.no-cardano-utxos.title",
4555
+ subtitle: "v2.cnight-designation.build.error.no-cardano-utxos.subtitle"
4556
+ },
4557
+ "no-registration-utxo": {
4558
+ title: "v2.cnight-designation.build.error.no-registration-utxo.title",
4559
+ subtitle: "v2.cnight-designation.build.error.no-registration-utxo.subtitle"
4560
+ },
4561
+ "script-stake-unregistered": {
4562
+ title: "v2.cnight-designation.build.error.script-stake-unregistered.title",
4563
+ subtitle: "v2.cnight-designation.build.error.script-stake-unregistered.subtitle"
4564
+ }
4565
+ };
4566
+ const GENERIC_BUILD_ERROR_KEYS = {
4567
+ title: "v2.cnight-designation.build.error.title",
4568
+ subtitle: "v2.cnight-designation.build.error.subtitle"
4569
+ };
4570
+ const errorTranslationKeysForCode = (code) => (code ? BUILD_ERROR_KEYS_BY_CODE[code] : void 0) ?? GENERIC_BUILD_ERROR_KEYS;
4571
+ const failure = (error, code) => ({
4572
+ success: false,
4573
+ error: {
4574
+ name: error.name,
4575
+ message: error.message
4576
+ },
4577
+ errorTranslationKeys: errorTranslationKeysForCode(code)
4578
+ });
4579
+ /**
4580
+ * Resolve the rich cNIGHT action from the serializable `Building` state by
4581
+ * scanning the script address for the registration UTxO whose inline datum is
4582
+ * bound to the account's stake key. Every action needs that answer, not just
4583
+ * update/deregister: Midnight honours neither of two registrations and the
4584
+ * second revokes the first, so a designate over an existing one is refused
4585
+ * here (`already-registered`) rather than signed. For update it also reads the
4586
+ * script reward account so the withdrawal entry matches the amount the ledger
4587
+ * will actually let the tx take.
4588
+ */
4589
+ const resolveAction = async ({ state, network, stakeKeyHash, dependencies, chainId, pendingActivities }) => {
4590
+ const dustPubkey = state.dustPubkeyHex === void 0 ? void 0 : MidnightCoinPubkey(hexToBytes(state.dustPubkeyHex));
4591
+ const scriptAddress = getDustGeneratorPaymentAddress(network);
4592
+ const registration = findRegistrationUtxo(await firstValueFrom(unwrapProviderResult(() => dependencies.cardanoProvider.getUtxosAtAddress({ address: scriptAddress }, { chainId }))), getDustMappingNftAssetId(network), stakeKeyHash);
4593
+ if (state.action === "designate") {
4594
+ if (registration) throw Object.assign(/* @__PURE__ */ new Error("This account already has a cNIGHT designation"), { code: "already-registered" });
4595
+ if (hasLivePendingDesignate(pendingActivities)) throw Object.assign(/* @__PURE__ */ new Error("A cNIGHT designation for this account is still confirming"), { code: "already-registered" });
4596
+ if (!dustPubkey) throw new Error("Missing dust pubkey for designate");
4597
+ return {
4598
+ kind: "register",
4599
+ dustPubkey
4600
+ };
4601
+ }
4602
+ if (!registration) throw Object.assign(/* @__PURE__ */ new Error("No cNIGHT designation registration found for this account"), { code: "no-registration-utxo" });
4603
+ if (state.action === "deregister") return {
4604
+ kind: "deregister",
4605
+ registrationUtxo: registration.utxo
4606
+ };
4607
+ if (!dustPubkey) throw new Error("Missing dust pubkey for update");
4608
+ const scriptRewardAccountInfo = await firstValueFrom(unwrapProviderResult(() => dependencies.cardanoProvider.getRewardAccountInfo({ rewardAccount: CardanoRewardAccount(getDustGeneratorRewardAccount(network)) }, { chainId })));
4609
+ if (!scriptRewardAccountInfo.isRegistered) throw Object.assign(/* @__PURE__ */ new Error("The cNIGHT validator's reward account is not registered on this network"), { code: "script-stake-unregistered" });
4610
+ return {
4611
+ kind: "update",
4612
+ dustPubkey,
4613
+ registrationUtxo: registration.utxo,
4614
+ scriptWithdrawableLovelace: BigNumber.valueOf(scriptRewardAccountInfo.withdrawableAmount)
4615
+ };
4616
+ };
4617
+ const buildDesignation = async (state, dependencies, pendingActivities) => {
4618
+ try {
4619
+ const { accountId } = state;
4620
+ const cardano = dependencies.txExecutorCardano;
4621
+ const [chainId, allAccountUtxos, unspendableUtxos, cardanoAddresses] = await Promise.all([
4622
+ firstValueFrom(cardano.cardanoChainId$),
4623
+ firstValueFrom(cardano.cardanoAccountUtxos$),
4624
+ firstValueFrom(cardano.cardanoAccountUnspendableUtxos$),
4625
+ firstValueFrom(cardano.cardanoAddresses$)
4626
+ ]);
4627
+ if (!chainId) throw new Error("Cardano chain id not available");
4628
+ refuseExtensionAccount(accountId, cardanoAddresses);
4629
+ const [protocolParameters, tip] = await Promise.all([firstValueFrom(unwrapProviderResult(() => dependencies.cardanoProvider.getProtocolParameters({ chainId }))), firstValueFrom(unwrapProviderResult(() => dependencies.cardanoProvider.getTip({ chainId })))]);
4630
+ const network = CardanoDustNetwork.fromNetworkMagic(chainId.networkMagic);
4631
+ const { primaryAddress, paymentKeyHash, stakeKeyHash } = resolveAccountKeyHashes(cardanoAddresses, accountId);
4632
+ const spendable = filterSpendableUtxos(allAccountUtxos[accountId] ?? [], unspendableUtxos[accountId] ?? []);
4633
+ const cnightAssetId = getCnightAssetId(network);
4634
+ const cnightUtxos = spendable.filter(([, out]) => out.value.assets?.has(cnightAssetId));
4635
+ const coverUtxos = spendable.filter(([, out]) => !out.value.assets?.has(cnightAssetId));
4636
+ const action = await resolveAction({
4637
+ state,
4638
+ network,
4639
+ stakeKeyHash,
4640
+ dependencies,
4641
+ chainId,
4642
+ pendingActivities
4643
+ });
4644
+ if (coverUtxos.length === 0) return failure(/* @__PURE__ */ new Error("No ADA-only UTxO available to cover fee and collateral"), "no-cardano-utxos");
4645
+ const result = await buildNightDesignationTx({
4646
+ network,
4647
+ action,
4648
+ cnightUtxos,
4649
+ paymentKeyHash,
4650
+ stakeKeyHash,
4651
+ changeAddress: primaryAddress,
4652
+ ttlSlot: Slot(Number(tip.slot) + TTL_BUFFER_SLOTS),
4653
+ protocolParameters
4654
+ }, {
4655
+ networkMagic: chainId.networkMagic,
4656
+ coverUtxos,
4657
+ txEvaluator: createProviderExUnitsEvaluator({
4658
+ cardanoProvider: dependencies.cardanoProvider,
4659
+ chainId,
4660
+ logger: dependencies.logger
4661
+ }),
4662
+ inputResolver: createInputResolver([
4663
+ ...cnightUtxos,
4664
+ ...coverUtxos,
4665
+ ...action.kind === "register" ? [] : [action.registrationUtxo]
4666
+ ])
4667
+ });
4668
+ if (!result.ok) return failure(new Error(result.error.message), result.error.code);
4669
+ return {
4670
+ success: true,
4671
+ serializedTx: result.value.cbor,
4672
+ fees: [{
4673
+ tokenId: LOVELACE_TOKEN_ID,
4674
+ amount: BigNumber(result.value.fee)
4675
+ }]
4676
+ };
4677
+ } catch (error) {
4678
+ const code = error instanceof InputSelectionError ? "no-cardano-utxos" : error.code;
4679
+ return failure(error instanceof Error ? error : new Error(String(error)), code);
4680
+ }
4681
+ };
4682
+ const makeNightDesignationBuilding = () => (_, stateObservables, dependencies) => firstStateOfStatus(stateObservables.nightDesignationFlow.selectState$, "Building").pipe(switchMap((state) => stateObservables.activities.selectPendingActivitiesByAccount$.pipe(map((byAccount) => byAccount[state.accountId] ?? NO_PENDING_ACTIVITIES), take(1), timeout({
4683
+ first: PENDING_READ_TIMEOUT_MS,
4684
+ with: () => of(NO_PENDING_ACTIVITIES)
4685
+ }), switchMap((pendingActivities) => from(buildDesignation(state, dependencies, pendingActivities)).pipe(map((result) => dependencies.actions.nightDesignationFlow.buildCompleted({
4686
+ accountId: state.accountId,
4687
+ result
4688
+ })))))));
4689
+ //#endregion
4690
+ //#region ../../packages/module/blockchain-cardano/src/store/night-designation/index-side-effect.ts
4691
+ /**
4692
+ * How long a refresh waits for the chain id and addresses it scans with.
4693
+ *
4694
+ * Only a bound, not a deadline: inputs that never arrive must resolve the entry
4695
+ * rather than leave it refreshing forever, with no way back to a retry.
4696
+ */
4697
+ const INPUTS_TIMEOUT_MS = 2e4;
4698
+ const scanInputs$ = (accountId, cardano, inputsTimeoutMs) => combineLatest([cardano.cardanoChainId$, cardano.cardanoAddresses$]).pipe(filter((inputs) => {
4699
+ const [chainId, cardanoAddresses] = inputs;
4700
+ return chainId !== void 0 && cardanoAddresses.some((address) => address.accountId === accountId && isCardanoAddressOfSupportedNetwork(address, chainId));
4701
+ }), take(1), timeout({
4702
+ first: inputsTimeoutMs,
4703
+ with: () => throwError(() => /* @__PURE__ */ new Error("Cardano chain id / addresses never published"))
4704
+ }));
4705
+ const readSnapshot$ = ({ accountId, chainId, cardanoAddresses, dependencies }) => {
4706
+ const { stakeKeyHash } = resolveAccountKeyHashes(cardanoAddresses, accountId);
4707
+ const network = CardanoDustNetwork.fromNetworkMagic(chainId.networkMagic);
4708
+ return forkJoin([unwrapProviderResult(() => dependencies.cardanoProvider.getUtxosAtAddress({ address: getDustGeneratorPaymentAddress(network) }, { chainId })), unwrapProviderResult(() => dependencies.cardanoProvider.getRewardAccountInfo({ rewardAccount: CardanoRewardAccount(getDustGeneratorRewardAccount(network)) }, { chainId })).pipe(map((info) => info.isRegistered), catchError(() => of(void 0)))]).pipe(map(([scriptUtxos, isScriptStakeCredentialRegistered]) => {
4709
+ const registration = findRegistrationUtxo(scriptUtxos, getDustMappingNftAssetId(network), stakeKeyHash);
4710
+ return {
4711
+ scriptStakeCredentialRegistered: isScriptStakeCredentialRegistered,
4712
+ ...registration === void 0 ? {} : { registration: {
4713
+ txId: String(registration.utxo[0].txId),
4714
+ outputIndex: registration.utxo[0].index,
4715
+ dustPubkeyHex: MidnightCoinPubkey.toHex(registration.datum.dustAddress)
4716
+ } }
4717
+ };
4718
+ }));
4719
+ };
4720
+ const makeNightDesignationIndexRefresh = ({ inputsTimeoutMs = INPUTS_TIMEOUT_MS } = {}) => ({ nightDesignationIndex: { refreshRequested$ } }, _stateObservables, dependencies) => refreshRequested$.pipe(groupBy(({ payload }) => payload.accountId), mergeMap((perAccount$) => perAccount$.pipe(exhaustMap(({ payload: { accountId } }) => scanInputs$(accountId, dependencies.txExecutorCardano, inputsTimeoutMs).pipe(switchMap(([chainId, cardanoAddresses]) => readSnapshot$({
4721
+ accountId,
4722
+ chainId,
4723
+ cardanoAddresses,
4724
+ dependencies
4725
+ })), map((snapshot) => dependencies.actions.nightDesignationIndex.refreshCompleted({
4726
+ accountId,
4727
+ snapshot
4728
+ })), catchError(() => of(dependencies.actions.nightDesignationIndex.refreshFailed({ accountId }))))))));
4729
+ /**
4730
+ * How long an entry may stay provisional before the index re-reads anyway.
4731
+ *
4732
+ * The wait is on the submitted transaction's activity, which a dropped
4733
+ * transaction or a stalled account sync can leave pending forever; without a
4734
+ * bound the entry would offer no designation control for the rest of the
4735
+ * session.
4736
+ */
4737
+ const SETTLING_TIMEOUT_MS = 600 * 1e3;
4738
+ /**
4739
+ * Hold the index provisional from submission until the designation
4740
+ * transaction is on chain, then re-read it.
4741
+ *
4742
+ * `Success` is a SUBMIT result — the txId comes back from submission — so the
4743
+ * script address still answers for the state before the designation. Scanning
4744
+ * here would write "not designated" over an account that has just designated,
4745
+ * and every later read would repeat it. The entry is marked settling instead,
4746
+ * and the scan waits for that transaction's activity to stop being pending.
4747
+ */
4748
+ const makeNightDesignationIndexSettling = ({ settlingTimeoutMs = SETTLING_TIMEOUT_MS } = {}) => (_, stateObservables, { actions }) => firstStateOfStatus(stateObservables.nightDesignationFlow.selectState$, "Success").pipe(mergeMap(({ accountId, txId }) => concat(of(actions.nightDesignationIndex.settlingStarted({
4749
+ accountId,
4750
+ txId
4751
+ })), merge(stateObservables.activities.selectAllMap$.pipe(filter((byAccount) => (byAccount[accountId] ?? []).some((activity) => activity.activityId === txId && activity.type !== ActivityType.Pending))), timer(settlingTimeoutMs)).pipe(take(1), mergeMap(() => from([actions.nightDesignationIndex.refreshRequested({ accountId }), actions.nightDesignationIndex.settlingEnded({ accountId })]))))));
4752
+ //#endregion
4753
+ //#region ../../packages/module/blockchain-cardano/src/store/sdk-extensions.ts
4754
+ const sdkExtensionSideEffects = [...[
4755
+ makeNightDesignationBuilding(),
4756
+ makeNightDesignationIndexRefresh(),
4757
+ makeNightDesignationIndexSettling()
4758
+ ], ...custodyTransactionSideEffects];
4759
+ //#endregion
4760
+ //#region ../../packages/module/blockchain-cardano/src/store/side-effects.ts
4761
+ const mainnetMagic = Number(NetworkMagics.Mainnet);
4762
+ const deriveCardanoTestnetOptions = (config) => {
4763
+ const blockfrostConfigs = config.cardanoProvider.blockfrostConfigs;
4764
+ return Object.keys(blockfrostConfigs ?? {}).map((key) => Number(key)).filter((magic) => magic !== mainnetMagic).map(getCardanoTestnetIdByNetworkMagic).filter((id) => Boolean(id)).map((id) => {
4765
+ return {
4766
+ id: CardanoNetworkId(getCardanoChainIdByTestnetId(id).networkMagic),
4767
+ label: id === "preprod" ? "v2.network-status.preprod" : id === "preview" ? "v2.network-status.preview" : "v2.network-status.preprod"
4768
+ };
4769
+ });
4770
+ };
4771
+ const registerCardanoTestnetOptions = (testnetOptions) => (_, { network: { selectTestnetOptions$ } }, { actions }) => selectTestnetOptions$.pipe(filter((options) => !options?.Cardano), map(() => actions.network.setTestnetOptions({
4772
+ blockchainName: "Cardano",
4773
+ options: testnetOptions
4774
+ })));
4775
+ const createCardanoSideEffects = (config) => {
4776
+ return [registerCardanoTestnetOptions(deriveCardanoTestnetOptions(config))];
4777
+ };
4778
+ //#endregion
4779
+ //#region ../../packages/module/blockchain-cardano/src/store/init.ts
4780
+ const redux = async (props, dependencies) => ({
4781
+ sideEffects: [
4782
+ ...createCardanoSideEffects(props.runtime.config),
4783
+ ...composerFlowSideEffects,
4784
+ ...sdkExtensionSideEffects
4785
+ ],
4786
+ sideEffectDependencies: await initializeDependencies(props, dependencies)
4787
+ });
4788
+ //#endregion
4789
+ export { redux as default };