@laceio/sdk 0.1.1 → 0.1.2-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 (388) hide show
  1. package/dist/{TransactionBuilder-DrB3NVTi.cjs → TransactionBuilder-CCjOn7NA.cjs} +42 -0
  2. package/dist/{TransactionBuilder-V6aLx4SV.js → TransactionBuilder-CIzdw18M.js} +1 -1
  3. package/dist/{_polyfill-buffer-patch-BnPVerlG.cjs → _polyfill-buffer-patch-CM8X7JiY.cjs} +21 -2
  4. package/dist/_polyfill-buffer-patch-yNxN4d5E.js +1467 -0
  5. package/dist/_web-crypto-random-bytes-Bo-GTcRS.js +197 -0
  6. package/dist/acc-module-4iIUv6My.cjs +27654 -0
  7. package/dist/acc-module-Cmj8EsIb.js +27624 -0
  8. package/dist/account-BOQruszJ.js +6 -0
  9. package/dist/account-BUOj4pYJ.cjs +11 -0
  10. package/dist/account-call-7wnopBq0.cjs +5 -0
  11. package/dist/account-call-B9d_2Xwi.js +4 -0
  12. package/dist/account-call-Bd4Rpbvt.cjs +323 -0
  13. package/dist/account-call-C3CkDqEC.js +276 -0
  14. package/dist/account-guards-BtjTGxDZ.cjs +42 -0
  15. package/dist/account-guards-D9Lu5T3W.js +36 -0
  16. package/dist/account-tokens-DvkOFVON.js +16 -0
  17. package/dist/account-tokens-nwcvwLu4.cjs +21 -0
  18. package/dist/{activity-type.stable-BJEfeLxL.cjs → activity-type-Ch6ljxBy.cjs} +2 -1
  19. package/dist/{activity-type.stable-C76_hWd7.js → activity-type-GsJGWs-3.js} +2 -1
  20. package/dist/address-B9ntKL8K.js +13761 -0
  21. package/dist/address-BXp-mY8H.cjs +11270 -0
  22. package/dist/{address-book-address-validator-DIUolI-s.js → address-book-address-validator-0J9XAADH.js} +2 -2
  23. package/dist/address-book-address-validator-DK0dnsRS.cjs +41 -0
  24. package/dist/address-book-address-validator-Di_ywo8M.js +39 -0
  25. package/dist/{address-book-address-validator-DNwQGlOB.cjs → address-book-address-validator-Dte_YQ2L.cjs} +2 -2
  26. package/dist/artefact-loader-BWWYAD7E.cjs +183 -0
  27. package/dist/artefact-loader-Dik4HIfv.js +160 -0
  28. package/dist/auto-dismiss-failure-on-success-C-qoJuid.cjs +37 -0
  29. package/dist/auto-dismiss-failure-on-success-s8MGDEIu.js +31 -0
  30. package/dist/bip32-D6wd_QMn.cjs +3851 -0
  31. package/dist/bitcoin-data-provider-BYPSmuZS.js +21 -0
  32. package/dist/bitcoin-data-provider-w1oEDSU3.cjs +26 -0
  33. package/dist/bitcoin.cjs +45 -0
  34. package/dist/bitcoin.d.ts +29906 -1
  35. package/dist/bitcoin.js +20 -0
  36. package/dist/{blockfrost-tx-submit-provider-DPTrapz-.js → blockfrost-tx-submit-provider-CBWQ0wK9.js} +5 -157
  37. package/dist/{blockfrost-tx-submit-provider-BAIzV5o3.cjs → blockfrost-tx-submit-provider-CLNQ5ycR.cjs} +3 -167
  38. package/dist/bn-d3nSOIf7.cjs +2553 -0
  39. package/dist/browser-B1eMbDwh.js +7468 -0
  40. package/dist/bytes.vo-BB7_4JK6.js +23 -0
  41. package/dist/bytes.vo-BNRquqaO.cjs +34 -0
  42. package/dist/cardano-DfPwhwm-.js +3 -0
  43. package/dist/{cardano-network-id.vo-DSxrFk9S.cjs → cardano-network-id.vo-BEDPn6Li.cjs} +1 -1
  44. package/dist/{cardano-network-id.vo-CK1tNQDg.js → cardano-network-id.vo-Cfpvbod9.js} +1 -1
  45. package/dist/cardano.cjs +22 -18
  46. package/dist/cardano.d.ts +16956 -1716
  47. package/dist/cardano.js +16 -11
  48. package/dist/client-DXBLHk7K.cjs +808 -0
  49. package/dist/codecs-Cswkg7Tm.cjs +4051 -0
  50. package/dist/codecs-Dc1Krpbt.js +3903 -0
  51. package/dist/{const-C84qHkjp.js → const-BJfIK_G-.js} +4 -4
  52. package/dist/const-BhYOdznB.cjs +42 -0
  53. package/dist/const-BsnjG2_Q.cjs +32071 -0
  54. package/dist/const-CUnCl5U0.js +13 -0
  55. package/dist/{const-DXk2tFyv.js → const-CjmfjBP3.js} +1 -1
  56. package/dist/{const-BDEg2Trq.cjs → const-D-eZIS4n.cjs} +4 -4
  57. package/dist/const-D0CbQlo4.js +154 -0
  58. package/dist/{const-BY8B3_0_.cjs → const-iwzZEE_w.cjs} +1 -1
  59. package/dist/contract-1R86jhSw.js +171 -0
  60. package/dist/contract-MKLzYVkW.cjs +189 -0
  61. package/dist/create-add-accounts-Brqk84vF.js +102 -0
  62. package/dist/create-add-accounts-DzXIh7Ot.cjs +114 -0
  63. package/dist/{delegation-tx-builder-CRpLP6jH.cjs → delegation-tx-builder-CCjiakoy.cjs} +6 -6
  64. package/dist/{delegation-tx-builder-CGVqlaFE.js → delegation-tx-builder-DZFjHDS8.js} +5 -5
  65. package/dist/{deregistration-tx-builder-B-CrUyX6.js → deregistration-tx-builder-Bf6xAYp4.js} +5 -5
  66. package/dist/{deregistration-tx-builder-DZGwB4cz.cjs → deregistration-tx-builder-CQP35yex.cjs} +6 -6
  67. package/dist/device-commitment-hex.vo-5wFtQvDS.js +26 -0
  68. package/dist/device-commitment-hex.vo-CT3RmAug.cjs +31 -0
  69. package/dist/device-readers-CUHYoYOH.js +145 -0
  70. package/dist/device-readers-CdxS2btd.cjs +168 -0
  71. package/dist/device-readers-D_JZeNL2.cjs +8 -0
  72. package/dist/device-readers-DwSNCGPP.js +4 -0
  73. package/dist/dist-BMi5ZjEq.cjs +43175 -0
  74. package/dist/dist-BtIq4XFn.cjs +535 -0
  75. package/dist/dist-C3SHC_Ho.cjs +1394 -0
  76. package/dist/dist-CykEjYEX.cjs +3814 -0
  77. package/dist/dist-Dcsxh3be.cjs +3452 -0
  78. package/dist/dist-DqJfSlTd.cjs +4413 -0
  79. package/dist/dist-nTZ70qj7.cjs +1306 -0
  80. package/dist/{earn-rewards-tx-builder-BDbTDZmD.js → earn-rewards-tx-builder-Bsga3yFb.js} +5 -5
  81. package/dist/{earn-rewards-tx-builder-D5sUT5v8.cjs → earn-rewards-tx-builder-GuxofYns.cjs} +6 -6
  82. package/dist/enc-key-circuits-B7BjgUL9.js +13 -0
  83. package/dist/enc-key-circuits-BLrZrBVI.cjs +24 -0
  84. package/dist/english-Byb9M0E_.cjs +2057 -0
  85. package/dist/envelopes-BGoO_v8m.js +1621 -0
  86. package/dist/envelopes-CH25R-DN.cjs +1722 -0
  87. package/dist/errors-BIftM8ZN.cjs +688 -0
  88. package/dist/errors-DSq5wUwt.js +466 -0
  89. package/dist/esm-HBSUQw3w.js +384 -0
  90. package/dist/esm-LZuaZjMf.cjs +389 -0
  91. package/dist/experimental.cjs +7349 -0
  92. package/dist/experimental.d.ts +86273 -0
  93. package/dist/experimental.js +7184 -0
  94. package/dist/gated-call-CDCVZIK-.cjs +5 -0
  95. package/dist/gated-call-D-tT7P_t.js +722 -0
  96. package/dist/gated-call-D_VT2quk.js +4 -0
  97. package/dist/gated-call-DgJm1Mi_.cjs +787 -0
  98. package/dist/grant-circuits-DswmW3nI.cjs +71 -0
  99. package/dist/grant-circuits-Jxb5y1HW.js +42 -0
  100. package/dist/grant-rules-By07_eDi.js +6824 -0
  101. package/dist/grant-rules-BzVTWXS7.cjs +7099 -0
  102. package/dist/grant-spend-B3Md9L0n.js +215 -0
  103. package/dist/grant-spend-oWmzRsjH.cjs +217 -0
  104. package/dist/grant-status-B2y49iIu.js +2688 -0
  105. package/dist/grant-status-DOr3t_u8.cjs +2820 -0
  106. package/dist/grants-2BZ-qEVQ.js +5 -0
  107. package/dist/grants-BPoLSiMt.cjs +18 -0
  108. package/dist/grants-BoI5uwSF.cjs +272 -0
  109. package/dist/grants-CtofFu-e.js +213 -0
  110. package/dist/{group-cardano-addresses-by-account-B533oVXR.cjs → group-cardano-addresses-by-account-Bp0g4wEY.cjs} +3 -39
  111. package/dist/{group-cardano-addresses-by-account-RHKoEskR.js → group-cardano-addresses-by-account-SR8MMrZf.js} +5 -35
  112. package/dist/{guarded-transaction-signer-Bxl6_XNd.cjs → guarded-transaction-signer-MFpPg98D.cjs} +17 -18
  113. package/dist/{guarded-transaction-signer-CpKLj8Kp.js → guarded-transaction-signer-P6Rp1faj.js} +8 -15
  114. package/dist/hex-D9ITYbwt.cjs +263 -0
  115. package/dist/hex-mkXcV3pN.js +191 -0
  116. package/dist/http-client-B3N-TMxu.cjs +167 -0
  117. package/dist/http-client-TRlhDZGh.js +155 -0
  118. package/dist/identity-BMLuufva.cjs +263 -0
  119. package/dist/identity-DII6f-h7.js +233 -0
  120. package/dist/in-memory-wallet-integration-B2xzxORj.cjs +20741 -0
  121. package/dist/in-memory-wallet-integration-CBRiPjLO.js +130 -0
  122. package/dist/in-memory-wallet-integration-CULUB5Tf.cjs +240 -0
  123. package/dist/{in-memory-wallet-integration-D-qn65Kx.js → in-memory-wallet-integration-ChIOirMW.js} +5 -2
  124. package/dist/{in-memory-wallet-integration-Croiuopx.js → in-memory-wallet-integration-CljprQQV.js} +7 -104
  125. package/dist/{in-memory-wallet-integration-DVh9sRXa.cjs → in-memory-wallet-integration-Cm6P8sk6.cjs} +3 -2
  126. package/dist/in-memory-wallet-integration-TM9bBphA.js +20738 -0
  127. package/dist/{in-memory-wallet-integration-CnLYeX4-.cjs → in-memory-wallet-integration-t9I00R_V.cjs} +17 -108
  128. package/dist/index.cjs +84 -47
  129. package/dist/index.d.ts +37496 -10033
  130. package/dist/index.js +52 -24
  131. package/dist/{init-CLHuAMB-.js → init-0zzs9aqU.js} +4 -4
  132. package/dist/{init-U8c9yHYx.cjs → init-5KcGcBb5.cjs} +1 -1
  133. package/dist/init-8v_n8boO.js +1177 -0
  134. package/dist/{init-CigestoM.js → init-B3-1ghyx.js} +2 -2
  135. package/dist/init-B8U3ABhS.cjs +107 -0
  136. package/dist/init-BJn-8_hf.js +141 -0
  137. package/dist/init-BPU3j8sT.cjs +6 -0
  138. package/dist/init-BRzH6xu-2.cjs +2797 -0
  139. package/dist/init-BmzfDaKL.js +1142 -0
  140. package/dist/{init-C-jErzp5.cjs → init-BopV9ed4.cjs} +2 -2
  141. package/dist/init-BpHTq2R5.js +105 -0
  142. package/dist/init-BpSITBfF.js +39 -0
  143. package/dist/{init-DX4WyB0R.cjs → init-BrdVzxei.cjs} +3 -3
  144. package/dist/{init-LfyMo-sS.cjs → init-BwkU0ruo.cjs} +2 -2
  145. package/dist/{init-CB__aLD7.cjs → init-C-G32J12.cjs} +2 -2
  146. package/dist/init-C00gw1zi.js +422 -0
  147. package/dist/{init-TkPq_Ny_.js → init-C2RgrfZe.js} +1 -1
  148. package/dist/{init-DcYY6zMD.js → init-C8URA2OX.js} +1 -1
  149. package/dist/init-CDkRynJa.js +2796 -0
  150. package/dist/{init-Cg9XdAru.js → init-CNM5m7M2.js} +4 -4
  151. package/dist/init-CR7BfztQ.cjs +18 -0
  152. package/dist/{init-CSHwHnfg.cjs → init-CSDpdu0Q.cjs} +2 -2
  153. package/dist/{init-aJHl-7AO.js → init-CX0AMQUn.js} +2 -2
  154. package/dist/{init-FMGDET-9.js → init-CfTbSEKy.js} +259 -60
  155. package/dist/{init-CR6tpw1B.js → init-Cigr_tKx.js} +47 -43
  156. package/dist/init-CuW7rHNA.js +3584 -0
  157. package/dist/{init-CFYsq82V.js → init-CyAY4Lzl.js} +1 -1
  158. package/dist/init-Cz0rz5fU.cjs +3585 -0
  159. package/dist/{init-BKhH6mVR.cjs → init-D6BxBs7e.cjs} +1 -1
  160. package/dist/init-D8OeObDF.cjs +1141 -0
  161. package/dist/init-D9g1HXYf.cjs +40 -0
  162. package/dist/{init-CNLGJJRx.js → init-DA7zmptx.js} +1 -1
  163. package/dist/{init-DkIc5nqF.cjs → init-DFMiP2ZH.cjs} +1 -1
  164. package/dist/{init-zSzCjoor.js → init-DFjT8YvT.js} +4 -4
  165. package/dist/init-DGjXwWs2.cjs +143 -0
  166. package/dist/init-DHrCbpkW.cjs +121 -0
  167. package/dist/{init-B64zCLxG.cjs → init-DUF4ar8P.cjs} +4 -4
  168. package/dist/init-DlqJuHhl.js +119 -0
  169. package/dist/{init-y2Ra0vZP.js → init-Dnsv155v.js} +3 -3
  170. package/dist/init-DrRdf5sZ.cjs +1176 -0
  171. package/dist/init-DuYENxci.js +17 -0
  172. package/dist/{init-D6DBUsD3.cjs → init-Dv3zKj0r.cjs} +4 -4
  173. package/dist/{init-BtzMZ7Pk.cjs → init-Dy-05ktr.cjs} +1 -1
  174. package/dist/{init-BsCBAGWf.cjs → init-EP0CUWNK.cjs} +2 -2
  175. package/dist/{init-BJVU54pI.cjs → init-EhzD7a8i.cjs} +1 -1
  176. package/dist/{init-w9GEs73W.js → init-Fw89ZmrR.js} +2 -2
  177. package/dist/init-GsT2AEa4.cjs +424 -0
  178. package/dist/{init-Rxohwpmu.js → init-NCjgwCZy.js} +2 -2
  179. package/dist/{init-DxUHsN53.cjs → init-NdhG_VrF.cjs} +6 -6
  180. package/dist/{init-DDdhguVC.js → init-SZ01tARX2.js} +2 -2
  181. package/dist/{init-BAJJzBza.js → init-Y3mkB4XS.js} +2 -2
  182. package/dist/init-c4l6r-o-.js +5 -0
  183. package/dist/{init-Dku4b2lG.js → init-kEfUTuG2.js} +1 -1
  184. package/dist/{init-C7IeJwQR.cjs → init-lzHt4Cli.cjs} +256 -59
  185. package/dist/{init-anYu6XB7.cjs → init-rmt7-fQE.cjs} +2 -2
  186. package/dist/{init-C4Bkib9u.cjs → init-zU-_9Gsp.cjs} +67 -65
  187. package/dist/{is-retriable-error-BU0e3hVI.js → is-retriable-error-BUiVJDwH.js} +1 -1
  188. package/dist/{is-retriable-error-CeeuHqDa.cjs → is-retriable-error-Dpt860if.cjs} +1 -1
  189. package/dist/jubjub-authoriser-D7zDXu3V.js +86 -0
  190. package/dist/jubjub-authoriser-Dgu6o3Sv.cjs +87 -0
  191. package/dist/key-source-5oUBFiC7.cjs +583 -0
  192. package/dist/key-source-B08HukZg.js +2550 -0
  193. package/dist/keyDerivation-D3k9XyHg.js +4077 -0
  194. package/dist/keyDerivation-u0nBA93T.cjs +274 -0
  195. package/dist/{lace-context-CZcNHr4M.js → lace-context-BZciB9c7.js} +4 -4
  196. package/dist/{lace-context-fJTDghdp.cjs → lace-context-DgR6vLli.cjs} +4 -4
  197. package/dist/lib--iqvW4O_.js +2354 -0
  198. package/dist/lib-Yk6Upl5D.cjs +2359 -0
  199. package/dist/{libsodium-wrappers-UH0LaOUE.js → libsodium-wrappers-BhbOA8dQ.js} +3 -1647
  200. package/dist/midnight-account-id.vo-0x9X-r3l.cjs +10 -0
  201. package/dist/midnight-account-id.vo-DjCAGcqc.js +5 -0
  202. package/dist/midnight-engine-DpemplKO.js +5 -0
  203. package/dist/midnight-engine-xU1wotne.cjs +6 -0
  204. package/dist/midnight-wallet-DIsAlSn6.js +18 -0
  205. package/dist/midnight-wallet-XTZc2li7.cjs +24 -0
  206. package/dist/midnight.cjs +1408 -0
  207. package/dist/midnight.d.ts +28934 -0
  208. package/dist/midnight.js +1352 -0
  209. package/dist/modules-cardano-B1nDUWqy.js +995 -0
  210. package/dist/modules-cardano-BYeYHrRm.cjs +1108 -0
  211. package/dist/node-relay-DWe1U0w2.cjs +49 -0
  212. package/dist/node-relay-h4i5IlAQ.js +48 -0
  213. package/dist/outputs-Bd2yZVjx.js +720 -0
  214. package/dist/outputs-CJoQBIA2.cjs +990 -0
  215. package/dist/pbkdf2-CIW-oWrm.cjs +1337 -0
  216. package/dist/pending-operation-B0_P78Jo.cjs +26 -0
  217. package/dist/pending-operation-BIS6mpjB.js +21 -0
  218. package/dist/providers-FjhM47BH.cjs +388 -0
  219. package/dist/providers-WtiSuxFu.js +365 -0
  220. package/dist/redacted-BmE2aXqr.js +81 -0
  221. package/dist/redacted-CRZJLS3w.cjs +86 -0
  222. package/dist/{retry-config-DC5IMaH7.cjs → retry-config-BaCfVC1H.cjs} +1 -1
  223. package/dist/{retry-config-BcUmMS--.js → retry-config-Dmgt28wM.js} +1 -1
  224. package/dist/safe-buffer-B_vMqJWU.js +43 -0
  225. package/dist/safe-buffer-DYOwEfHB.cjs +48 -0
  226. package/dist/sdk-Cb-gZnS9.js +1241 -0
  227. package/dist/sdk-DffwmX8b.cjs +1439 -0
  228. package/dist/sdk-init-Bln9rFtn.js +4138 -0
  229. package/dist/sdk-init-CYViTJY_.cjs +3841 -0
  230. package/dist/{seam-cardano-CATqBEyK.js → seam-cardano-B_etH4F4.js} +1 -1
  231. package/dist/{bytes.vo-TgiwogWF.js → secret-box-DUR0ACYz.js} +3 -25
  232. package/dist/{bytes.vo-Ck5u40RA.cjs → secret-box-DrjLZuS1.cjs} +1 -35
  233. package/dist/send-proof-progress-C-ljq1x5.js +30 -0
  234. package/dist/send-proof-progress-vmsaSfbz.cjs +47 -0
  235. package/dist/side-effects-BnqYz7Nl.js +768 -0
  236. package/dist/side-effects-CngaeXi0.cjs +804 -0
  237. package/dist/sign-bitcoin-transaction-with-seed-D4z62-jV.js +147 -0
  238. package/dist/sign-bitcoin-transaction-with-seed-DsLYXnCY.cjs +158 -0
  239. package/dist/signer-3fk8lZtE.cjs +7 -0
  240. package/dist/signer-CsL28DrK.cjs +121 -0
  241. package/dist/signer-DGluibtn.js +92 -0
  242. package/dist/signer-RqB5pu3K.js +2 -0
  243. package/dist/signer-factory-BA7WFJVZ.js +282 -0
  244. package/dist/signer-factory-Cwd6o-lG.cjs +281 -0
  245. package/dist/{signer-factory-9URFliA5.js → signer-factory-DObB55D4.js} +6 -3
  246. package/dist/signer-factory-DhZiE03y.cjs +149 -0
  247. package/dist/{signer-factory-BWGUmnvm.cjs → signer-factory-Ds6G2AZk.cjs} +4 -3
  248. package/dist/signer-factory-hzSRaIw2.js +148 -0
  249. package/dist/{slice-CuHUaPZx.cjs → slice-7G2xxYKJ.cjs} +3 -3
  250. package/dist/{slice-DZmQJNV8.cjs → slice-B4CdYoPu.cjs} +2 -2
  251. package/dist/slice-BPNuHKfw.js +9091 -0
  252. package/dist/{slice-DGcNrbPR.cjs → slice-Bg6HEr3K.cjs} +4 -4
  253. package/dist/{slice-DtaO72qY.cjs → slice-Bs2aMERu.cjs} +3 -3
  254. package/dist/{slice-HeHCv3bl.cjs → slice-C34iOOUH.cjs} +7 -7
  255. package/dist/{slice-Do2R2CYW.cjs → slice-C9TT5KhY.cjs} +6 -6
  256. package/dist/{slice-eIwcwKHI.js → slice-CK07EBCo.js} +4 -4
  257. package/dist/{slice-CnyWevM9.js → slice-CU80pbiu.js} +3 -3
  258. package/dist/{slice-CobanYgn.js → slice-CwyYrh1H.js} +2 -2
  259. package/dist/slice-Czfp6t84.cjs +436 -0
  260. package/dist/{slice-d0p6-xj5.js → slice-D-9aPKG-.js} +6 -6
  261. package/dist/{slice-maaskdUn.js → slice-DBpJ2QGf.js} +6 -6
  262. package/dist/{slice-mxLQ_FeY.cjs → slice-DTZTiJle.cjs} +352 -39
  263. package/dist/{slice-zJ9XVYjZ.js → slice-Da426sf9.js} +1 -1
  264. package/dist/{slice-D11m2EKl.cjs → slice-DayJvMRD.cjs} +1 -1
  265. package/dist/slice-DnBWfA-k.cjs +9109 -0
  266. package/dist/{slice-oqm_r1cS.cjs → slice-DnG-yvpI.cjs} +2 -2
  267. package/dist/{slice-BNu73oBa.js → slice-DyNs7oWp.js} +2 -2
  268. package/dist/{slice-CB4SnzMm.js → slice-GwiiJRoq.js} +3 -3
  269. package/dist/slice-oWnc7YVD.js +412 -0
  270. package/dist/{slice-B2PhoWAg.js → slice-qt0uBExh.js} +325 -37
  271. package/dist/slice-t8Gzj55G2.cjs +106 -0
  272. package/dist/slice-t8Q1cHm-.js +88 -0
  273. package/dist/src-BDzYIksE.cjs +20637 -0
  274. package/dist/src-BFoefPv_.js +20755 -0
  275. package/dist/store-B1Lq3cEf.cjs +66 -0
  276. package/dist/store-DuBG8qh5.js +37 -0
  277. package/dist/submit-Bs77dPn_.cjs +413 -0
  278. package/dist/submit-DOv8Z0Vf.js +336 -0
  279. package/dist/submit-bitcoin-tx-D6oYgH5S.js +983 -0
  280. package/dist/submit-bitcoin-tx-goFuRL7K.cjs +1049 -0
  281. package/dist/{sync-operation-utils-BYXLO80m.js → sync-operation-utils-31iFLExW.js} +2 -38
  282. package/dist/{sync-operation-utils-SdPbwFoD.cjs → sync-operation-utils-COIZ66-I.cjs} +1 -67
  283. package/dist/token-id-mapper-Bnds3xKH.cjs +18 -0
  284. package/dist/{token-id-mapper-DsPAL6Nh.cjs → token-id-mapper-CVyQJUKY.cjs} +2 -2
  285. package/dist/token-id-mapper-CnKHQ8DV.js +16 -0
  286. package/dist/{token-id-mapper-B2ivlLv3.js → token-id-mapper-D29Ggv8h.js} +2 -2
  287. package/dist/tokens-Cevp7rdU.cjs +62 -0
  288. package/dist/tokens-Diw4MLRM.js +44 -0
  289. package/dist/types-B8uiJn-X.js +12 -0
  290. package/dist/types-BHC12eSa.js +33 -0
  291. package/dist/types-CJT6XF2c.js +10 -0
  292. package/dist/types-CLRUYpfe.cjs +23 -0
  293. package/dist/types-Cc3mC8u0.cjs +50 -0
  294. package/dist/types-DWbVZpnF.cjs +15 -0
  295. package/dist/unwrap-provider-result-BZSiwabU.js +32 -0
  296. package/dist/unwrap-provider-result-ZqfBsTkk.cjs +39 -0
  297. package/dist/{util-CGzqQ8IV.cjs → util-Bu4W5lF4.cjs} +1 -1
  298. package/dist/{util-BaLzwa32.js → util-Cp5cZkfK.js} +1 -1
  299. package/dist/{utils-o8aMjfln.js → utils-BfBEUvWA.js} +2 -2
  300. package/dist/utils-DFwzEvbv.js +65 -0
  301. package/dist/{utils-5JvjbqH3.cjs → utils-DsElZgCI.cjs} +7 -1
  302. package/dist/utils-IXxR-Djh.cjs +112 -0
  303. package/dist/v8Signatures-CO_Uloy7.cjs +71 -0
  304. package/dist/{vote-delegation-tx-builder-BpEMSti4.cjs → vote-delegation-tx-builder-BUg1S2Ep.cjs} +6 -6
  305. package/dist/{vote-delegation-tx-builder-CmKv6GoG.js → vote-delegation-tx-builder-C6FiS7zI.js} +5 -5
  306. package/dist/watch-CUEvcP-V.js +21997 -0
  307. package/dist/watch-DMF2s-II.cjs +52281 -0
  308. package/package.json +96 -4
  309. package/dist/account-guards.stable-B1DSSh9D.cjs +0 -10
  310. package/dist/account-guards.stable-JRQ5qx2V.js +0 -5
  311. package/dist/codecs-B7X9pAsK.cjs +0 -1945
  312. package/dist/codecs-CC1Xjntz.js +0 -1882
  313. package/dist/init-Bv53hpLM.js +0 -19
  314. package/dist/init-CRbjI7p1.js +0 -3494
  315. package/dist/init-DAna1KCg.cjs +0 -3495
  316. package/dist/init-DGe47B22.cjs +0 -364
  317. package/dist/init-DMtyvYfC.cjs +0 -21
  318. package/dist/init-TkPDD750.js +0 -363
  319. package/dist/modules-cardano-BDvuL4Xx.js +0 -5248
  320. package/dist/modules-cardano-D2G3De2P.cjs +0 -5432
  321. package/dist/slice-CGVHXHDt.cjs +0 -125
  322. package/dist/slice-DhvF7L6X.js +0 -107
  323. /package/dist/{account-id.vo-LvCBVn5r.cjs → account-id.vo-DwqFjK5u.cjs} +0 -0
  324. /package/dist/{account-id.vo-MYSI7YFm.js → account-id.vo-OPz2v-JZ.js} +0 -0
  325. /package/dist/{actions-DdGbj7c7.cjs → actions-B2BJ4RTy.cjs} +0 -0
  326. /package/dist/{actions-9QSQR-GB.js → actions-iLXdgL5Y.js} +0 -0
  327. /package/dist/{apply-vkey-witnesses-ClZbpk5w.js → apply-vkey-witnesses-BKx3lcs0.js} +0 -0
  328. /package/dist/{apply-vkey-witnesses-CLzVSWxl.cjs → apply-vkey-witnesses-ZnFS0jnJ.cjs} +0 -0
  329. /package/dist/{asyncoption-Cw07zPH7.js → asyncoption-CruEYOBI.js} +0 -0
  330. /package/dist/{asyncoption-DYoGuQfP.cjs → asyncoption-i0VbqVTq.cjs} +0 -0
  331. /package/dist/{bignum.vo-B0rK_PQ1.js → bignum.vo-CEXOIew0.js} +0 -0
  332. /package/dist/{bignum.vo-DsG3kNiZ.cjs → bignum.vo-hx2kuqlA.cjs} +0 -0
  333. /package/dist/{bip32ed25519-1ApqeAZk.cjs → bip32ed25519-B7Yat217.cjs} +0 -0
  334. /package/dist/{bip32ed25519-DetAdGwD.js → bip32ed25519-FQ8qvdcq.js} +0 -0
  335. /package/dist/{blake2b-DOEVc57D.js → blake2b-BiQTw3GC.js} +0 -0
  336. /package/dist/{blake2b-B9tftbvG.cjs → blake2b-DEeVUdKR.cjs} +0 -0
  337. /package/dist/{blockchain-network-id.vo-BXpZhvvQ.cjs → blockchain-network-id.vo-BAKEwGjG.cjs} +0 -0
  338. /package/dist/{blockchain-network-id.vo-DNRkuT5B.js → blockchain-network-id.vo-CiVykuqG.js} +0 -0
  339. /package/dist/{blockingWithLatestFrom-C5wp_uA-.js → blockingWithLatestFrom-Cd8c8EPG.js} +0 -0
  340. /package/dist/{blockingWithLatestFrom-DY8OPg0w.cjs → blockingWithLatestFrom-D8kBPBMX.cjs} +0 -0
  341. /package/dist/{cardano-network-id-to-network-type-CrZfUcyP.js → cardano-network-id-to-network-type-BWwzeEtM.js} +0 -0
  342. /package/dist/{cardano-network-id-to-network-type-BDTF-HmK.cjs → cardano-network-id-to-network-type-C0ufJOyE.cjs} +0 -0
  343. /package/dist/{cardano-tx-id.vo-BPhV-AmZ.cjs → cardano-tx-id.vo-TLjJZHCI.cjs} +0 -0
  344. /package/dist/{cardano-tx-id.vo-DF9NzZXk.js → cardano-tx-id.vo-eVnKuzlm.js} +0 -0
  345. /package/dist/{composite-signer-factory-_46QsNBz.js → composite-signer-factory-CR8YHA1y.js} +0 -0
  346. /package/dist/{composite-signer-factory-BpF1mIYA.cjs → composite-signer-factory-m5cVs9h_.cjs} +0 -0
  347. /package/dist/{error-CNIquqT5.cjs → error-Cb3EnD0u.cjs} +0 -0
  348. /package/dist/{error-BCp9NkPi.js → error-DIZOY2Lw.js} +0 -0
  349. /package/dist/{esm-browser-C93Q-EOI.js → esm-browser-MG97cre9.js} +0 -0
  350. /package/dist/{esm-browser-DTc9IK3D.cjs → esm-browser-bweqfBlL.cjs} +0 -0
  351. /package/dist/{feature-flag-key.vo-ofrB6ZVQ.js → feature-flag-key.vo-DN_-4-sG.js} +0 -0
  352. /package/dist/{feature-flag-key.vo-BPz-ZAp6.cjs → feature-flag-key.vo-RlfHL3hq.cjs} +0 -0
  353. /package/dist/{first-state-of-status-OROkXcRv.cjs → first-state-of-status-CbT7hPa4.cjs} +0 -0
  354. /package/dist/{first-state-of-status-DQnMT3ya.js → first-state-of-status-unPynOuM.js} +0 -0
  355. /package/dist/{init-Es3wHW7D.js → init-829HRYcz.js} +0 -0
  356. /package/dist/{init-CKipPE_c.js → init-A-VyGSNW2.js} +0 -0
  357. /package/dist/{init-DFQQ9dTW.cjs → init-Atisw4x2.cjs} +0 -0
  358. /package/dist/{init-C5CEZ1m0.cjs → init-BFcvPHL-.cjs} +0 -0
  359. /package/dist/{init-IAk_CkGa.cjs → init-BgNobbNq.cjs} +0 -0
  360. /package/dist/{init-cqN3uX2j.js → init-atCFur0v.js} +0 -0
  361. /package/dist/{is-redux-persist-state-CTn3WZVh.cjs → is-redux-persist-state-BCFnSm36.cjs} +0 -0
  362. /package/dist/{is-redux-persist-state-Cxcd0_bM.js → is-redux-persist-state-yBfq0h_E.js} +0 -0
  363. /package/dist/{key-value-store-8cMzeNyG.js → key-value-store--W3SrMaw.js} +0 -0
  364. /package/dist/{key-value-store-DUAepXQf.cjs → key-value-store-C2Xt5Emp.cjs} +0 -0
  365. /package/dist/{repo-slice-WxetRhMY.cjs → repo-slice-7X4AYYVm.cjs} +0 -0
  366. /package/dist/{repo-slice-DwyMhCR1.js → repo-slice-RxFm0zDZ.js} +0 -0
  367. /package/dist/{reselect-CRDqmQY9.cjs → reselect-Bd94u0K-.cjs} +0 -0
  368. /package/dist/{reselect-C06X06oX.js → reselect-CGoLzEPv.js} +0 -0
  369. /package/dist/{serializable-B8GahgE1.js → serializable-BHdKIgk3.js} +0 -0
  370. /package/dist/{serializable-CRdnFaLZ.cjs → serializable-BxHkoPdk.cjs} +0 -0
  371. /package/dist/{slice-D11FHQuQ.cjs → slice-BiRsDKOK.cjs} +0 -0
  372. /package/dist/{slice-Be7hj4Iy.js → slice-Bk0Sv05J.js} +0 -0
  373. /package/dist/{slice-FioKeHtA.cjs → slice-Dqw1ecum.cjs} +0 -0
  374. /package/dist/{slice-B87b52iM.js → slice-obCSLm1x.js} +0 -0
  375. /package/dist/{src-vq1wS9sZ.js → src-BlxEp6Mm.js} +0 -0
  376. /package/dist/{src-BTPIvlDI.cjs → src-Cqp0B2M_.cjs} +0 -0
  377. /package/dist/{time.vo-CQ8pSXz2.cjs → time.vo-D3wWRw9L.cjs} +0 -0
  378. /package/dist/{time.vo-DkYYGYp6.js → time.vo-tNheinXm.js} +0 -0
  379. /package/dist/{toEmpty-CgAl3097.js → toEmpty-8Zax1MPd.js} +0 -0
  380. /package/dist/{toEmpty-DAMmNB5r.cjs → toEmpty-CYAbupM4.cjs} +0 -0
  381. /package/dist/{token-id.vo-ClsroxHO.js → token-id.vo-Bvwhr3ZE.js} +0 -0
  382. /package/dist/{token-id.vo-BxG9TG0O.cjs → token-id.vo-DXv5uS9n.cjs} +0 -0
  383. /package/dist/{util-DxzlYNS5.js → util-C8a4fA9p.js} +0 -0
  384. /package/dist/{util-CmdAhheh.cjs → util-CzeB3hfa.cjs} +0 -0
  385. /package/dist/{util-selector-CcK-GAdE.js → util-selector-BSTsxgne.js} +0 -0
  386. /package/dist/{util-selector-BcAncU4L.cjs → util-selector-BWPH68_l.cjs} +0 -0
  387. /package/dist/{while-active-BpppWuCs.js → while-active-B0lWy_6l.js} +0 -0
  388. /package/dist/{while-active-Dhc2LwOm.cjs → while-active-Dsowp0Fi.cjs} +0 -0
@@ -0,0 +1,3585 @@
1
+ require("./rolldown-runtime-Br2OG8yv.cjs");
2
+ const require_util = require("./util-Bu4W5lF4.cjs");
3
+ const require_bignum_vo = require("./bignum.vo-hx2kuqlA.cjs");
4
+ const require_serializable = require("./serializable-BxHkoPdk.cjs");
5
+ require("./retry-config-BaCfVC1H.cjs");
6
+ const require_account = require("./account-BUOj4pYJ.cjs");
7
+ const require_hex = require("./hex-D9ITYbwt.cjs");
8
+ const require_outputs = require("./outputs-CJoQBIA2.cjs");
9
+ const require_tokens = require("./tokens-Cevp7rdU.cjs");
10
+ const require_grant_status = require("./grant-status-DOr3t_u8.cjs");
11
+ const require_identity = require("./identity-BMLuufva.cjs");
12
+ const require_TransactionBuilder = require("./TransactionBuilder-CCjOn7NA.cjs");
13
+ const require_is_retriable_error = require("./is-retriable-error-Dpt860if.cjs");
14
+ const require_unwrap_provider_result = require("./unwrap-provider-result-ZqfBsTkk.cjs");
15
+ let _laceio_cardano_crypto = require("@laceio/cardano/crypto");
16
+ let _laceio_cardano_core = require("@laceio/cardano/core");
17
+ let _laceio_cardano_util = require("@laceio/cardano/util");
18
+ let rxjs = require("rxjs");
19
+ let _laceio_cardano_tx_construction = require("@laceio/cardano/tx-construction");
20
+ //#region ../../packages/lib/cardano-custody/src/analysis/sponsor-rules.ts
21
+ /** The service's limit on the size of a transaction it inspects, in bytes. */
22
+ const MAX_TRANSACTION_BYTES = 16 * 1024;
23
+ /** The service's default limit on the fee a leased fee UTxO pays, in lovelace. */
24
+ const MAX_FEE_LOVELACE = 2000000n;
25
+ /**
26
+ * The most devices a well formed state lists, which bounds the stake scripts
27
+ * a creation has the service derive.
28
+ */
29
+ const MAX_DEVICES = 8;
30
+ const outpointKey = ({ txId, index }) => `${txId.toLowerCase()}#${index}`;
31
+ const toTxIn = ({ txId, index }) => ({
32
+ txId,
33
+ index
34
+ });
35
+ const paymentCredentialOf = (address) => {
36
+ const parsed = _laceio_cardano_core.Cardano.Address.fromString(address);
37
+ return (parsed?.asBase() ?? parsed?.asEnterprise() ?? parsed?.asPointer())?.getPaymentCredential();
38
+ };
39
+ const stakeCredentialOf = (address) => {
40
+ const parsed = _laceio_cardano_core.Cardano.Address.fromString(address);
41
+ return parsed?.asBase()?.getStakeCredential() ?? parsed?.asReward()?.getPaymentCredential();
42
+ };
43
+ const keyHashOf = (credential) => credential?.type === _laceio_cardano_core.Cardano.CredentialType.KeyHash ? credential.hash : void 0;
44
+ const scriptHashOf$1 = (credential) => credential?.type === _laceio_cardano_core.Cardano.CredentialType.ScriptHash ? credential.hash : void 0;
45
+ const sponsorKeysOf = (address) => ({
46
+ paymentKeyHash: keyHashOf(paymentCredentialOf(address)),
47
+ stakeKeyHash: keyHashOf(stakeCredentialOf(address))
48
+ });
49
+ const namesSponsorKey = (credential, { paymentKeyHash, stakeKeyHash }) => credential.type === _laceio_cardano_core.Cardano.CredentialType.KeyHash && (credential.hash === paymentKeyHash || credential.hash === stakeKeyHash);
50
+ const isAccountOutput = (output, { contractVersion }) => scriptHashOf$1(paymentCredentialOf(output.address)) === contractVersion.accountValidatorHash;
51
+ const policyIdOf = (assetId) => _laceio_cardano_core.Cardano.AssetId.getPolicyId(assetId);
52
+ /** The 28-byte hash a datum field holds, or undefined for a field of any other shape. */
53
+ const hashFieldOf = (field) => _laceio_cardano_core.Cardano.util.isPlutusBoundedBytes(field) && field.length === 28 ? (0, _laceio_cardano_crypto.Hash28ByteBase16)(require_hex.bytesToHex(field)) : void 0;
54
+ /** The fields of an inline datum that is constructor 0, or undefined for any other datum. */
55
+ const inlineFieldsOf = (output) => _laceio_cardano_core.Cardano.util.isConstrPlutusData(output.datum) && output.datum.constructor === 0n ? output.datum.fields.items : void 0;
56
+ /** The logic a control datum names: a 28-byte hash in its first field. */
57
+ const logicHashOf = (output) => hashFieldOf(inlineFieldsOf(output)?.[0]);
58
+ /** The devices a control datum lists: a list of 28-byte hashes in its second field. */
59
+ const devicesOf = (output) => {
60
+ const list = inlineFieldsOf(output)?.[1];
61
+ if (!_laceio_cardano_core.Cardano.util.isPlutusList(list)) return void 0;
62
+ const devices = list.items.map(hashFieldOf);
63
+ return devices.every((device) => device !== void 0) ? devices : void 0;
64
+ };
65
+ /**
66
+ * The account token an input holds, when it is a control or a grant UTxO:
67
+ * the output sits at an account address staked to a script and holds a
68
+ * token under the account policy named after that very script, alone or
69
+ * followed by a slot. The validator mints a token only into such an
70
+ * output, so an output shaped otherwise is never read as an account's. The
71
+ * control token wins when an output holds both kinds.
72
+ */
73
+ const accountTokenOf = ({ input, output }, context) => {
74
+ if (output === void 0 || !isAccountOutput(output, context)) return;
75
+ const account = scriptHashOf$1(stakeCredentialOf(output.address));
76
+ if (account === void 0) return void 0;
77
+ const kinds = [...output.value.assets ?? /* @__PURE__ */ new Map()].flatMap(([assetId, quantity]) => {
78
+ if (policyIdOf(assetId) !== context.contractVersion.accountValidatorHash || quantity <= 0n) return [];
79
+ const name = require_tokens.parseCustodyAccountTokenName(_laceio_cardano_core.Cardano.AssetId.getAssetName(assetId));
80
+ return name?.stakeScriptHash === account ? [name.kind] : [];
81
+ });
82
+ if (kinds.length === 0) return void 0;
83
+ return {
84
+ input,
85
+ output,
86
+ account,
87
+ kind: kinds.includes("state-nft") ? "control" : "grant"
88
+ };
89
+ };
90
+ /**
91
+ * The account a control output belongs to: an output at an account address
92
+ * staked to a script, holding exactly one state NFT named after that script.
93
+ * The validator pins the state NFT to such an output, so its datum names
94
+ * the logic the account runs once the transaction lands.
95
+ */
96
+ const controlOutputAccount = (output, context) => {
97
+ if (!isAccountOutput(output, context)) return void 0;
98
+ const account = scriptHashOf$1(stakeCredentialOf(output.address));
99
+ if (account === void 0) return void 0;
100
+ const stateNft = _laceio_cardano_core.Cardano.AssetId.fromParts(_laceio_cardano_core.Cardano.PolicyId(context.contractVersion.accountValidatorHash), _laceio_cardano_core.Cardano.AssetName(account));
101
+ return output.value.assets?.get(stateNft) === 1n ? account : void 0;
102
+ };
103
+ /**
104
+ * Every logic the transaction names: the one each control UTxO it spends or
105
+ * references names, then the one each control output it writes names. A
106
+ * creation names its initial logic in its control output, and an upgrade
107
+ * names the logic it leaves in the control UTxO and the one it arrives at in
108
+ * the control output.
109
+ */
110
+ const namedLogicsOf = ({ spent, referencedControls }, body, context) => [...[...spent.filter(({ kind }) => kind === "control"), ...referencedControls].map(({ input, output, account }) => ({
111
+ logic: logicHashOf(output),
112
+ control: {
113
+ kind: "control-utxo",
114
+ input
115
+ },
116
+ stakeScriptHash: account
117
+ })), ...body.outputs.flatMap((output, outputIndex) => {
118
+ const account = controlOutputAccount(output, context);
119
+ return account === void 0 ? [] : [{
120
+ logic: logicHashOf(output),
121
+ control: {
122
+ kind: "control-output",
123
+ outputIndex
124
+ },
125
+ stakeScriptHash: account
126
+ }];
127
+ })];
128
+ /**
129
+ * A sponsor UTxO is the leased fee UTxO, the shared collateral, or any
130
+ * output locked by the sponsor payment key: the sponsor's signature
131
+ * authorises every one of them.
132
+ */
133
+ const isSponsorInput = ({ input, output }, context, sponsorKeys) => {
134
+ const key = outpointKey(input);
135
+ 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;
136
+ };
137
+ /** Every credential a certificate names, any of which may have to sign. */
138
+ const certificateCredentials = (certificate) => {
139
+ const named = [];
140
+ if ("stakeCredential" in certificate) named.push(certificate.stakeCredential);
141
+ if ("dRep" in certificate && _laceio_cardano_core.Cardano.isDRepCredential(certificate.dRep)) named.push(certificate.dRep);
142
+ if ("dRepCredential" in certificate) named.push(certificate.dRepCredential);
143
+ if ("coldCredential" in certificate) named.push(certificate.coldCredential);
144
+ if ("hotCredential" in certificate) named.push(certificate.hotCredential);
145
+ const poolIds = [];
146
+ if ("poolId" in certificate) poolIds.push(certificate.poolId);
147
+ if ("poolParameters" in certificate) {
148
+ const { id, rewardAccount, owners } = certificate.poolParameters;
149
+ poolIds.push(id);
150
+ named.push(stakeCredentialOf(rewardAccount), ...owners.map((owner) => stakeCredentialOf(owner)));
151
+ }
152
+ for (const poolId of poolIds) named.push({
153
+ type: _laceio_cardano_core.Cardano.CredentialType.KeyHash,
154
+ hash: _laceio_cardano_core.Cardano.PoolId.toKeyHash(poolId)
155
+ });
156
+ return named.filter((credential) => credential !== void 0);
157
+ };
158
+ const LOVELACE_KEY = "";
159
+ /** Lovelace and every asset a list of values holds, keyed by asset id ('' for lovelace). */
160
+ const balanceOf = (values) => {
161
+ const balance = /* @__PURE__ */ new Map();
162
+ const add = (key, quantity) => balance.set(key, (balance.get(key) ?? 0n) + quantity);
163
+ for (const value of values) {
164
+ add(LOVELACE_KEY, value.coins);
165
+ for (const [assetId, quantity] of value.assets ?? []) add(assetId, quantity);
166
+ }
167
+ return balance;
168
+ };
169
+ /** Reads each entry of the CBOR map the reader is at, definite or not. */
170
+ const readMapEntries = (reader, readEntry) => {
171
+ const count = reader.readStartMap();
172
+ for (let read = 0; count === null ? reader.peekState() !== _laceio_cardano_core.Serialization.CborReaderState.EndMap : read < count; read++) readEntry();
173
+ };
174
+ /** The fields of an encoded body or witness set by key, each as the bytes it is stated in. */
175
+ const fieldsOf = (encoded) => {
176
+ const reader = new _laceio_cardano_core.Serialization.CborReader(require_hex.bytesToHex(encoded));
177
+ const fields = /* @__PURE__ */ new Map();
178
+ readMapEntries(reader, () => fields.set(reader.readUInt(), require_hex.bytesToHex(reader.readEncodedValue())));
179
+ return fields;
180
+ };
181
+ const BODY_WITHDRAWALS = 5n;
182
+ const WITNESS_DATUMS = 4n;
183
+ const WITNESS_REDEEMERS = 5n;
184
+ /**
185
+ * The body withdrawals and the witness script data as the transaction's
186
+ * bytes state them, which is what the service reads: the decoded body keeps
187
+ * one withdrawal per reward account, and the script data hash is taken over
188
+ * the bytes the witness set carries.
189
+ */
190
+ const statedOf = (transaction) => {
191
+ const reader = new _laceio_cardano_core.Serialization.CborReader((0, _laceio_cardano_util.HexBlob)(transaction.toCbor()));
192
+ reader.readStartArray();
193
+ const body = fieldsOf(reader.readEncodedValue());
194
+ const witnessSet = fieldsOf(reader.readEncodedValue());
195
+ const withdrawals = [];
196
+ const encodedWithdrawals = body.get(BODY_WITHDRAWALS);
197
+ if (encodedWithdrawals !== void 0) {
198
+ const withdrawalReader = new _laceio_cardano_core.Serialization.CborReader(encodedWithdrawals);
199
+ readMapEntries(withdrawalReader, () => withdrawals.push({
200
+ stakeAddress: _laceio_cardano_core.Cardano.RewardAccount(_laceio_cardano_core.Cardano.Address.fromBytes(require_hex.bytesToHex(withdrawalReader.readByteString())).toBech32()),
201
+ quantity: withdrawalReader.readUInt()
202
+ }));
203
+ }
204
+ return {
205
+ withdrawals,
206
+ witnessScriptData: {
207
+ redeemers: witnessSet.get(WITNESS_REDEEMERS),
208
+ datums: witnessSet.get(WITNESS_DATUMS)
209
+ }
210
+ };
211
+ };
212
+ /**
213
+ * Reads an account creation, or the reason the transaction is none:
214
+ * exactly one token minted under the account policy, named with the 28
215
+ * bytes of a stake script hash, exactly one registration of a script stake
216
+ * credential with its deposit, the token named after that credential and
217
+ * held by exactly one output at an account address staked to it, and that
218
+ * credential the version's stake script of a device the output's datum
219
+ * lists. A datum listing more devices than a well formed state is refused
220
+ * before any script is derived.
221
+ */
222
+ const readCreation = (body, context) => {
223
+ const refuse = (reason) => ({
224
+ rule: "account_transaction",
225
+ reason
226
+ });
227
+ const { accountValidatorHash } = context.contractVersion;
228
+ const minted = [...body.mint ?? /* @__PURE__ */ new Map()].filter(([assetId]) => policyIdOf(assetId) === accountValidatorHash);
229
+ if (minted.length === 0) return refuse("not-account-transaction");
230
+ const [[assetId, quantity]] = minted;
231
+ if (minted.length !== 1 || quantity !== 1n) return refuse("mint-count");
232
+ if (require_tokens.parseCustodyAccountTokenName(_laceio_cardano_core.Cardano.AssetId.getAssetName(assetId))?.kind !== "state-nft") return refuse("state-nft-name-length");
233
+ const registrations = (body.certificates ?? []).filter((certificate) => certificate.__typename === _laceio_cardano_core.Cardano.CertificateType.Registration && certificate.stakeCredential.type === _laceio_cardano_core.Cardano.CredentialType.ScriptHash);
234
+ if (registrations.length !== 1) return refuse("registration-count");
235
+ const [registration] = registrations;
236
+ const stakeScriptHash = registration.stakeCredential.hash;
237
+ if (_laceio_cardano_core.Cardano.AssetId.getAssetName(assetId) !== stakeScriptHash) return refuse("state-nft-name");
238
+ const holders = body.outputs.filter((output) => output.value.assets?.get(assetId) === 1n);
239
+ if (holders.length !== 1 || !isAccountOutput(holders[0], context)) return refuse("control-output-placement");
240
+ const [controlOutput] = holders;
241
+ if (scriptHashOf$1(stakeCredentialOf(controlOutput.address)) !== stakeScriptHash) return refuse("control-output-stake");
242
+ const devices = devicesOf(controlOutput);
243
+ if (devices === void 0) return refuse("control-output-devices");
244
+ if (devices.length > MAX_DEVICES) return {
245
+ rule: "account_transaction",
246
+ reason: "device-count",
247
+ count: devices.length,
248
+ limit: MAX_DEVICES
249
+ };
250
+ const chainId = require_identity.chainIdFromNetworkMagic(context.networkMagic);
251
+ if (!devices.some((device) => require_identity.identityOfValidatedCreator({
252
+ contractVersion: context.contractVersion,
253
+ creatorKeyHash: device,
254
+ chainId
255
+ }).stakeScriptHash === stakeScriptHash)) return {
256
+ rule: "account_transaction",
257
+ reason: "underived-stake-credential",
258
+ stakeScriptHash
259
+ };
260
+ return {
261
+ deposit: registration.deposit,
262
+ controlLovelace: controlOutput.value.coins,
263
+ stakeScriptHash
264
+ };
265
+ };
266
+ /** The accounts whose control UTxO the transaction spends or references. */
267
+ const controlledAccountsOf = ({ spent, referencedControls }) => new Set([...spent, ...referencedControls].filter(({ kind }) => kind === "control").map(({ account }) => account));
268
+ /**
269
+ * Reads what the transaction does to the account contract. A referenced
270
+ * control UTxO serves only an account the transaction operates, so one of
271
+ * any other account is refused first, even beside a creation. A
272
+ * transaction spending no account token is a creation, or no account
273
+ * transaction at all. One spending a token is an operation, and every
274
+ * account it operates needs its control UTxO spent or referenced, as the
275
+ * contract needs on the owner, the sweep and the agent path alike.
276
+ */
277
+ const readAccountTransaction = (body, { spent, referencedControls }, context) => {
278
+ const refuse = (reason, { input, account }) => ({
279
+ kind: "refused",
280
+ violation: {
281
+ rule: "account_transaction",
282
+ reason,
283
+ input,
284
+ stakeScriptHash: account
285
+ }
286
+ });
287
+ const operated = new Set(spent.map(({ account }) => account));
288
+ const foreignControl = referencedControls.find(({ account }) => !operated.has(account));
289
+ if (foreignControl) return refuse("foreign-referenced-control", foreignControl);
290
+ if (spent.length === 0) {
291
+ const creation = readCreation(body, context);
292
+ return "rule" in creation ? {
293
+ kind: "refused",
294
+ violation: creation
295
+ } : {
296
+ kind: "creation",
297
+ creation
298
+ };
299
+ }
300
+ const controlled = controlledAccountsOf({
301
+ spent,
302
+ referencedControls
303
+ });
304
+ const orphan = spent.find(({ account }) => !controlled.has(account));
305
+ return orphan ? refuse("orphan-grant", orphan) : { kind: "operation" };
306
+ };
307
+ /**
308
+ * The stake scripts of the accounts the transaction belongs to: those of
309
+ * the control UTxOs it spends or references, which name them, and the one
310
+ * it registers at creation. A grant UTxO alone names none: an agent spend
311
+ * references the control UTxO of its account.
312
+ */
313
+ const stakeScriptHashesOf = (tokens, reading) => {
314
+ const hashes = controlledAccountsOf(tokens);
315
+ if (reading.kind === "creation") hashes.add(reading.creation.stakeScriptHash);
316
+ return hashes;
317
+ };
318
+ const analyse = (transaction, resolvedInputs, context) => {
319
+ const body = transaction.body().toCore();
320
+ const sponsorKeys = sponsorKeysOf(context.sponsorAddress);
321
+ const resolutions = new Map(resolvedInputs.map(([input, output]) => [outpointKey(input), output]));
322
+ const resolve = (input) => ({
323
+ input: toTxIn(input),
324
+ output: resolutions.get(outpointKey(input))
325
+ });
326
+ const inputs = body.inputs.map(resolve);
327
+ const referenceInputs = (body.referenceInputs ?? []).map(resolve);
328
+ const sponsorInputs = inputs.filter((input) => isSponsorInput(input, context, sponsorKeys));
329
+ const tokens = {
330
+ spent: inputs.filter((input) => !sponsorInputs.includes(input)).flatMap((input) => accountTokenOf(input, context) ?? []),
331
+ referencedControls: referenceInputs.flatMap((input) => accountTokenOf(input, context) ?? []).filter(({ kind }) => kind === "control")
332
+ };
333
+ const reading = readAccountTransaction(body, tokens, context);
334
+ const namedLogics = namedLogicsOf(tokens, body, context);
335
+ const sponsorOutputs = body.outputs.flatMap((output, outputIndex) => output.address === context.sponsorAddress ? [{
336
+ output,
337
+ outputIndex
338
+ }] : []);
339
+ return {
340
+ transaction,
341
+ body,
342
+ witness: transaction.witnessSet().toCore(),
343
+ inputs,
344
+ referenceInputs,
345
+ sponsorInputs,
346
+ sponsorOutputs,
347
+ foreignOutputs: body.outputs.filter((output) => output.address !== context.sponsorAddress && !isAccountOutput(output, context)),
348
+ ...statedOf(transaction),
349
+ reading,
350
+ stakeScriptHashes: stakeScriptHashesOf(tokens, reading),
351
+ namedLogics,
352
+ logicHashes: new Set(namedLogics.flatMap(({ logic }) => logic ?? [])),
353
+ sponsorKeys
354
+ };
355
+ };
356
+ const wellFormed = ({ transaction, body }) => {
357
+ const size = transaction.toCbor().length / 2;
358
+ if (size > MAX_TRANSACTION_BYTES) return {
359
+ rule: "well_formed",
360
+ reason: "too-large",
361
+ size,
362
+ limit: MAX_TRANSACTION_BYTES
363
+ };
364
+ if ((body.proposalProcedures ?? []).length > 0) return {
365
+ rule: "well_formed",
366
+ reason: "proposal-procedures"
367
+ };
368
+ return body.donation === void 0 ? void 0 : {
369
+ rule: "well_formed",
370
+ reason: "treasury-donation"
371
+ };
372
+ };
373
+ /**
374
+ * Fee mode spends the leased fee UTxO and no other sponsor UTxO; collateral
375
+ * mode spends none. An input whose payment credential cannot be read
376
+ * cannot be told from the sponsor's.
377
+ */
378
+ const sponsorInputsRule = ({ inputs, sponsorInputs }, { mode }) => {
379
+ const rule = mode.kind === "fee" ? "uses_leased_fee_input" : "no_sponsor_inputs";
380
+ const feeKey = mode.kind === "fee" ? outpointKey(mode.feeUtxo[0]) : void 0;
381
+ if (mode.kind === "fee" && !inputs.some(({ input }) => outpointKey(input) === feeKey)) return {
382
+ rule: "uses_leased_fee_input",
383
+ reason: "fee-input-missing",
384
+ input: toTxIn(mode.feeUtxo[0])
385
+ };
386
+ const unreadable = inputs.find(({ output }) => output !== void 0 && paymentCredentialOf(output.address) === void 0);
387
+ if (unreadable) return {
388
+ rule,
389
+ reason: "unreadable-input",
390
+ input: unreadable.input
391
+ };
392
+ const extra = sponsorInputs.find(({ input }) => outpointKey(input) !== feeKey);
393
+ return extra ? {
394
+ rule,
395
+ reason: "sponsor-input",
396
+ input: extra.input
397
+ } : void 0;
398
+ };
399
+ /**
400
+ * The transaction is not flagged as failing phase two, and its collateral
401
+ * is exactly the shared collateral UTxO, returned plainly to the sponsor,
402
+ * with the total collateral set within what it holds. The ledger also
403
+ * takes collateral of at least the collateral percentage of the fee, so a
404
+ * fee above what the shared collateral covers is refused: the service
405
+ * would refuse the total it needs, and the ledger any less.
406
+ */
407
+ const sharedCollateral = ({ transaction, body }, context) => {
408
+ const refuse = (reason) => ({
409
+ rule: "uses_shared_collateral",
410
+ reason
411
+ });
412
+ if (!transaction.isValid()) return refuse("phase-two-invalid");
413
+ const collaterals = body.collaterals ?? [];
414
+ if (collaterals.length !== 1 || outpointKey(collaterals[0]) !== outpointKey(context.collateralUtxo[0])) return refuse("collateral-inputs");
415
+ const { collateralReturn, totalCollateral } = body;
416
+ if (!collateralReturn) return refuse("no-collateral-return");
417
+ if (collateralReturn.address !== context.sponsorAddress) return refuse("collateral-return-address");
418
+ if (collateralReturn.datum !== void 0 || collateralReturn.datumHash !== void 0) return refuse("collateral-return-datum");
419
+ if (collateralReturn.scriptReference !== void 0) return refuse("collateral-return-reference-script");
420
+ if (totalCollateral === void 0) return refuse("no-total-collateral");
421
+ const available = context.collateralUtxo[1].value.coins;
422
+ if (totalCollateral > available) return {
423
+ rule: "uses_shared_collateral",
424
+ reason: "total-collateral-exceeds",
425
+ totalCollateral,
426
+ available
427
+ };
428
+ const percentage = BigInt(context.collateralPercentage);
429
+ return body.fee * percentage > available * 100n ? {
430
+ rule: "uses_shared_collateral",
431
+ reason: "fee-exceeds-collateral",
432
+ fee: body.fee,
433
+ limit: available * 100n / percentage
434
+ } : void 0;
435
+ };
436
+ const boundedValidity = ({ body }, context) => {
437
+ const upperBound = body.validityInterval?.invalidHereafter;
438
+ if (upperBound === void 0) return {
439
+ rule: "bounded_validity",
440
+ reason: "no-upper-bound"
441
+ };
442
+ if (upperBound > context.latestValiditySlot) return {
443
+ rule: "bounded_validity",
444
+ reason: "upper-bound-too-late",
445
+ upperBound,
446
+ latest: context.latestValiditySlot
447
+ };
448
+ return upperBound <= context.currentSlot ? {
449
+ rule: "bounded_validity",
450
+ reason: "upper-bound-not-future",
451
+ upperBound,
452
+ current: context.currentSlot
453
+ } : void 0;
454
+ };
455
+ const accountTransaction = ({ reading }) => reading.kind === "refused" ? reading.violation : void 0;
456
+ /**
457
+ * Every logic the transaction names is one the service knows. The proxy
458
+ * runs whatever registered script a control datum names, so the service
459
+ * pays for and lends collateral to no account under rules it has not read,
460
+ * and refuses a control datum that names no script hash at all.
461
+ */
462
+ const knownLogic = ({ namedLogics }, { contractVersion }) => {
463
+ const known = new Set(contractVersion.logics.map(({ hash }) => hash));
464
+ for (const { logic, control, stakeScriptHash } of namedLogics) {
465
+ if (logic === void 0) return {
466
+ rule: "known_logic",
467
+ reason: "logic-missing",
468
+ control,
469
+ stakeScriptHash
470
+ };
471
+ if (!known.has(logic)) return {
472
+ rule: "known_logic",
473
+ reason: "unknown-logic",
474
+ control,
475
+ logic,
476
+ stakeScriptHash
477
+ };
478
+ }
479
+ };
480
+ /**
481
+ * The leased fee UTxO pays for an account creation and nothing else: an
482
+ * operation on an existing account pays its own fee and takes the
483
+ * collateral route, whatever it draws. The fee stays within the service's
484
+ * limit, and the fee UTxO is drawn down by exactly the fee, the
485
+ * registration deposit and the control output, within the sponsored limit,
486
+ * and returns as one plain change output the pool can spend.
487
+ */
488
+ const sponsorOutflowBounded = ({ body, sponsorOutputs, reading }, mode) => {
489
+ const rule = "sponsor_outflow_bounded";
490
+ if (reading.kind !== "creation") return {
491
+ rule,
492
+ reason: "not-creation"
493
+ };
494
+ if (body.fee > MAX_FEE_LOVELACE) return {
495
+ rule,
496
+ reason: "fee-exceeds-limit",
497
+ fee: body.fee,
498
+ limit: MAX_FEE_LOVELACE
499
+ };
500
+ for (const { output, outputIndex } of sponsorOutputs) {
501
+ if (output.datum !== void 0 || output.datumHash !== void 0) return {
502
+ rule,
503
+ reason: "sponsor-output-datum",
504
+ outputIndex
505
+ };
506
+ if (output.scriptReference !== void 0) return {
507
+ rule,
508
+ reason: "sponsor-output-reference-script",
509
+ outputIndex
510
+ };
511
+ }
512
+ if (sponsorOutputs.length !== 1) return {
513
+ rule,
514
+ reason: "sponsor-output-count",
515
+ count: sponsorOutputs.length
516
+ };
517
+ const sponsored = mode.feeUtxo[1].value.coins - sponsorOutputs[0].output.value.coins;
518
+ const { deposit, controlLovelace } = reading.creation;
519
+ const expected = body.fee + deposit + controlLovelace;
520
+ if (sponsored !== expected) return {
521
+ rule,
522
+ reason: "draw-mismatch",
523
+ sponsored,
524
+ expected
525
+ };
526
+ return sponsored > mode.maxSponsoredLovelace ? {
527
+ rule,
528
+ reason: "draw-exceeds-limit",
529
+ sponsored,
530
+ limit: mode.maxSponsoredLovelace
531
+ } : void 0;
532
+ };
533
+ /** In collateral mode nothing pays the sponsor key and nothing draws from its reward account. */
534
+ const sponsorOutflowZero = ({ body, withdrawals, sponsorKeys }) => {
535
+ const outputIndex = body.outputs.findIndex((output) => sponsorKeys.paymentKeyHash !== void 0 && keyHashOf(paymentCredentialOf(output.address)) === sponsorKeys.paymentKeyHash);
536
+ if (outputIndex >= 0) return {
537
+ rule: "sponsor_outflow_zero",
538
+ reason: "pays-sponsor",
539
+ outputIndex
540
+ };
541
+ const withdrawal = withdrawals.find(({ stakeAddress }) => sponsorKeys.stakeKeyHash !== void 0 && keyHashOf(stakeCredentialOf(stakeAddress)) === sponsorKeys.stakeKeyHash);
542
+ return withdrawal ? {
543
+ rule: "sponsor_outflow_zero",
544
+ reason: "sponsor-withdrawal",
545
+ rewardAccount: withdrawal.stakeAddress
546
+ } : void 0;
547
+ };
548
+ const sponsorOutflow = (analysis, { mode }) => mode.kind === "fee" ? sponsorOutflowBounded(analysis, mode) : sponsorOutflowZero(analysis);
549
+ /** Outputs away from the sponsor and the account are covered by what non-sponsor inputs and withdrawals bring. */
550
+ const noSponsorValueElsewhere = ({ withdrawals, inputs, sponsorInputs, foreignOutputs, sponsorKeys }) => {
551
+ const supply = balanceOf(inputs.filter((input) => !sponsorInputs.includes(input)).flatMap(({ output }) => output ? [output.value] : []));
552
+ 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);
553
+ const demand = balanceOf(foreignOutputs.map((output) => output.value));
554
+ for (const [key, demanded] of demand) {
555
+ const supplied = supply.get(key) ?? 0n;
556
+ if (demanded > supplied) return {
557
+ rule: "no_sponsor_value_elsewhere",
558
+ reason: "uncovered-outflow",
559
+ demanded,
560
+ supplied,
561
+ ...key === LOVELACE_KEY ? {} : { assetId: key }
562
+ };
563
+ }
564
+ };
565
+ /**
566
+ * Lace's own rule on reference inputs: one carrying a script must be a
567
+ * version's parked script UTxO on the network, carrying exactly the script
568
+ * parked there, and a parked script's UTxO must carry its script.
569
+ */
570
+ const foreignReferenceScriptOf = (referenceInputs, { contractVersion, networkMagic }) => {
571
+ const parked = new Map((contractVersion.referenceScripts[networkMagic] ?? []).map(({ txIn, scriptHash }) => [outpointKey(txIn), scriptHash]));
572
+ for (const { input, output } of referenceInputs) {
573
+ const script = output?.scriptReference;
574
+ const scriptHash = script && _laceio_cardano_core.Serialization.Script.fromCore(script).hash();
575
+ const expected = parked.get(outpointKey(input));
576
+ if (expected === void 0 ? scriptHash !== void 0 : output !== void 0 && scriptHash !== expected) return {
577
+ rule: "no_foreign_scripts",
578
+ reason: "foreign-reference-script",
579
+ input,
580
+ ...scriptHash && { scriptHash }
581
+ };
582
+ }
583
+ };
584
+ /**
585
+ * Every script the transaction runs or attaches belongs to the account.
586
+ * Inputs and mints use the account proxy alone. A certificate or a vote may
587
+ * name the proxy or the stake script of an account the transaction belongs
588
+ * to. A logic its control datums name may also run through a withdrawal,
589
+ * of zero only, and be attached. No reward account is drawn from twice.
590
+ */
591
+ const noForeignScripts = ({ body, witness, inputs, referenceInputs, withdrawals, stakeScriptHashes, logicHashes }, context) => {
592
+ const rule = "no_foreign_scripts";
593
+ const proxy = context.contractVersion.accountValidatorHash;
594
+ const accountScripts = new Set([proxy, ...stakeScriptHashes]);
595
+ const runnable = new Set([...accountScripts, ...logicHashes]);
596
+ for (const { input, output } of inputs) {
597
+ const scriptHash = output && scriptHashOf$1(paymentCredentialOf(output.address));
598
+ if (scriptHash !== void 0 && scriptHash !== proxy) return {
599
+ rule,
600
+ reason: "foreign-script-input",
601
+ input,
602
+ scriptHash
603
+ };
604
+ }
605
+ for (const assetId of body.mint?.keys() ?? []) {
606
+ const policyId = policyIdOf(assetId);
607
+ if (policyId !== proxy) return {
608
+ rule,
609
+ reason: "foreign-mint",
610
+ policyId
611
+ };
612
+ }
613
+ for (const [certificateIndex, certificate] of (body.certificates ?? []).entries()) {
614
+ const scriptHash = certificateCredentials(certificate).map(scriptHashOf$1).find((hash) => hash !== void 0 && !accountScripts.has(hash));
615
+ if (scriptHash) return {
616
+ rule,
617
+ reason: "foreign-certificate",
618
+ certificateIndex,
619
+ scriptHash
620
+ };
621
+ }
622
+ const drawn = /* @__PURE__ */ new Set();
623
+ for (const { stakeAddress: rewardAccount, quantity } of withdrawals) {
624
+ if (drawn.has(rewardAccount)) return {
625
+ rule,
626
+ reason: "duplicate-withdrawal",
627
+ rewardAccount
628
+ };
629
+ drawn.add(rewardAccount);
630
+ const scriptHash = scriptHashOf$1(stakeCredentialOf(rewardAccount));
631
+ if (scriptHash !== void 0 && !runnable.has(scriptHash)) return {
632
+ rule,
633
+ reason: "foreign-withdrawal",
634
+ rewardAccount
635
+ };
636
+ if (scriptHash !== void 0 && logicHashes.has(scriptHash) && quantity !== 0n) return {
637
+ rule,
638
+ reason: "logic-draw",
639
+ rewardAccount,
640
+ logic: scriptHash,
641
+ quantity
642
+ };
643
+ }
644
+ for (const { voter } of body.votingProcedures ?? []) {
645
+ const scriptHash = scriptHashOf$1(voter.credential);
646
+ if (scriptHash !== void 0 && !accountScripts.has(scriptHash)) return {
647
+ rule,
648
+ reason: "foreign-voter",
649
+ scriptHash
650
+ };
651
+ }
652
+ const scripts = witness.scripts ?? [];
653
+ if (scripts.some((script) => _laceio_cardano_core.Cardano.isNativeScript(script))) return {
654
+ rule,
655
+ reason: "native-script"
656
+ };
657
+ for (const script of scripts) {
658
+ const scriptHash = _laceio_cardano_core.Serialization.Script.fromCore(script).hash();
659
+ if (!runnable.has(scriptHash)) return {
660
+ rule,
661
+ reason: "foreign-attached-script",
662
+ scriptHash
663
+ };
664
+ }
665
+ return foreignReferenceScriptOf(referenceInputs, context);
666
+ };
667
+ /**
668
+ * The Plutus languages of the scripts the transaction attaches, then of the
669
+ * reference scripts its inputs and reference inputs carry: the service's
670
+ * language view covers all of them, not only the scripts that run.
671
+ */
672
+ const languagesOf = ({ witness, inputs, referenceInputs }) => [...(witness.scripts ?? []).flatMap((script) => _laceio_cardano_core.Cardano.isPlutusScript(script) ? [script.version] : []), ...[...inputs, ...referenceInputs].flatMap(({ output }) => output?.scriptReference && _laceio_cardano_core.Cardano.isPlutusScript(output.scriptReference) ? [output.scriptReference.version] : [])];
673
+ /** The redeemers that stand in for a witness set carrying datums and no redeemers. */
674
+ const EMPTY_REDEEMERS = "a0";
675
+ /**
676
+ * The script data hash the body commits to is the one the witness set's
677
+ * redeemers and datums hash to, under the cost models, with the language
678
+ * view of every attached and referenced script. Every script an account
679
+ * runs is Plutus V3, so a transaction carrying any other language is
680
+ * refused before a view is formed.
681
+ */
682
+ const scriptDataHash = (analysis, { costModels }) => {
683
+ const rule = "script_data_hash";
684
+ const declared = analysis.body.scriptIntegrityHash;
685
+ const { redeemers, datums } = analysis.witnessScriptData;
686
+ if (redeemers === void 0 && datums === void 0) return declared === void 0 ? void 0 : {
687
+ rule,
688
+ reason: "unexpected-script-data-hash",
689
+ declared
690
+ };
691
+ if (declared === void 0) return {
692
+ rule,
693
+ reason: "missing-script-data-hash"
694
+ };
695
+ const languages = languagesOf(analysis);
696
+ const foreign = languages.find((language) => language !== _laceio_cardano_core.Cardano.PlutusLanguageVersion.V3);
697
+ if (foreign !== void 0) return {
698
+ rule,
699
+ reason: "non-v3-script",
700
+ language: foreign
701
+ };
702
+ const views = new _laceio_cardano_core.Serialization.Costmdls();
703
+ for (const language of new Set(languages)) {
704
+ const costs = costModels.get(language);
705
+ if (!costs) return {
706
+ rule,
707
+ reason: "missing-cost-model",
708
+ language
709
+ };
710
+ views.insert(new _laceio_cardano_core.Serialization.CostModel(language, costs));
711
+ }
712
+ const computed = _laceio_cardano_crypto.blake2b.hash((0, _laceio_cardano_util.HexBlob)(`${redeemers ?? EMPTY_REDEEMERS}${datums ?? ""}${views.languageViewsEncoding()}`), 32);
713
+ return computed === declared ? void 0 : {
714
+ rule,
715
+ reason: "script-data-hash-mismatch",
716
+ declared,
717
+ computed
718
+ };
719
+ };
720
+ /** Neither sponsor key signs for anything but the sponsor's own inputs. */
721
+ const signers = ({ body, withdrawals, sponsorKeys }) => {
722
+ const keyHash = (body.requiredExtraSignatures ?? []).find((signer) => signer === sponsorKeys.paymentKeyHash || signer === sponsorKeys.stakeKeyHash);
723
+ if (keyHash) return {
724
+ rule: "signers",
725
+ reason: "sponsor-required-signer",
726
+ keyHash
727
+ };
728
+ const withdrawal = withdrawals.find(({ stakeAddress }) => {
729
+ const credential = stakeCredentialOf(stakeAddress);
730
+ return credential !== void 0 && namesSponsorKey(credential, sponsorKeys);
731
+ });
732
+ if (withdrawal) return {
733
+ rule: "signers",
734
+ reason: "sponsor-withdrawal",
735
+ rewardAccount: withdrawal.stakeAddress
736
+ };
737
+ const certificateIndex = (body.certificates ?? []).findIndex((certificate) => certificateCredentials(certificate).some((credential) => namesSponsorKey(credential, sponsorKeys)));
738
+ if (certificateIndex >= 0) return {
739
+ rule: "signers",
740
+ reason: "sponsor-certificate",
741
+ certificateIndex
742
+ };
743
+ return (body.votingProcedures ?? []).some(({ voter }) => namesSponsorKey(voter.credential, sponsorKeys)) ? {
744
+ rule: "signers",
745
+ reason: "sponsor-voter"
746
+ } : void 0;
747
+ };
748
+ /** The service's rules in its check order; it evaluates the scripts between the last two. */
749
+ const RULES = [
750
+ wellFormed,
751
+ sponsorInputsRule,
752
+ sharedCollateral,
753
+ boundedValidity,
754
+ accountTransaction,
755
+ knownLogic,
756
+ sponsorOutflow,
757
+ noSponsorValueElsewhere,
758
+ noForeignScripts,
759
+ scriptDataHash,
760
+ signers
761
+ ];
762
+ /**
763
+ * Every rule of the fee sponsor service's policy that a transaction breaks,
764
+ * at most one per rule and in the service's check order, so the first is
765
+ * the one the service would answer with. Lace checks its custody builds
766
+ * with it before asking the sponsor for a witness, so a refusal from the
767
+ * service points at a real defect rather than a build Lace knew to be wrong.
768
+ *
769
+ * The script evaluation rule is not mirrored: it needs the chain. Of the
770
+ * sponsor's UTxOs, only the leased fee UTxO, the shared collateral and
771
+ * outputs locked by the sponsor payment key are recognised; the service
772
+ * also knows the rest of its pool. The service knows the logics its
773
+ * operator lists, which Lace takes to be the contract version's. Beyond the
774
+ * service's rules, the shared collateral rule refuses a fee above what the
775
+ * shared collateral covers, which the ledger would refuse after the service
776
+ * signed, and the script rule refuses a reference script other than the
777
+ * version's parked scripts on the network.
778
+ *
779
+ * @param resolvedInputs - the outputs the transaction's inputs spend and its
780
+ * reference inputs name; an input or a reference input without one is
781
+ * read as the service reads one the chain does not know, which is never
782
+ * an account's control or grant UTxO and carries no reference script
783
+ */
784
+ const findCustodySponsorRuleViolations = (transaction, resolvedInputs, context) => {
785
+ const analysis = analyse(transaction, resolvedInputs, context);
786
+ return RULES.flatMap((rule) => {
787
+ const violation = rule(analysis, context);
788
+ return violation ? [violation] : [];
789
+ });
790
+ };
791
+ //#endregion
792
+ //#region ../../packages/lib/cardano-custody/src/state/transitions.ts
793
+ /**
794
+ * The state Lace creates an account with: `logic` as its rules, the creator
795
+ * as the only device, generation zero and zero counters. The logic accepts
796
+ * any generation at creation; consent refuses every other initial state.
797
+ *
798
+ * @param logic - the applied hash of the logic the account runs, normally the
799
+ * contract version's `currentLogicHash`
800
+ */
801
+ const createInitialCustodyAccountState = (creatorKeyHash, logic) => {
802
+ if (!require_hex.isHash28Hex(logic)) return require_hex.fail({
803
+ code: "invalid-logic-hash",
804
+ logic
805
+ });
806
+ return require_hex.isHash28Hex(creatorKeyHash) ? require_hex.ok({
807
+ logic,
808
+ devices: [creatorKeyHash],
809
+ grantGeneration: 0n,
810
+ nextSlot: 0n,
811
+ revoked: [],
812
+ outstanding: 0n
813
+ }) : require_hex.fail({
814
+ code: "invalid-device-key-hash",
815
+ device: creatorKeyHash
816
+ });
817
+ };
818
+ /** Checks a grant Lace is about to write: authoring rules, expiry, separation. */
819
+ const checkAuthoredGrant = (state, grant, now) => {
820
+ const violations = require_outputs.findCustodyGrantViolations(grant);
821
+ if (violations.length > 0) return {
822
+ code: "invalid-grant",
823
+ violations
824
+ };
825
+ if (!Number.isFinite(now)) return {
826
+ code: "invalid-now",
827
+ now
828
+ };
829
+ if (require_outputs.isCustodyGrantExpired(grant, now)) return {
830
+ code: "grant-expired",
831
+ slot: grant.slot,
832
+ expiresAt: grant.scope.expiresAt
833
+ };
834
+ if (state.devices.includes(grant.grantee)) return {
835
+ code: "grantee-is-device",
836
+ grantee: grant.grantee
837
+ };
838
+ };
839
+ /**
840
+ * Refuses a grantee that still holds a grant it could spend. One grant per
841
+ * agent key keeps each agent's authority in one place, so a revocation or a
842
+ * replacement always covers all of it. A grant judged against an older
843
+ * state than its own counts too: it is in flight and about to be current.
844
+ */
845
+ const checkGranteeHoldsNoCurrentGrant = (state, grants, { grantee, now }) => {
846
+ const held = grants.find((grant) => {
847
+ if (grant.grantee !== grantee) return false;
848
+ const liveness = require_grant_status.custodyGrantLiveness(grant, state, now);
849
+ return liveness === "current" || liveness === "future-generation";
850
+ });
851
+ return held ? {
852
+ code: "grantee-has-grant",
853
+ grantee,
854
+ slot: held.slot
855
+ } : void 0;
856
+ };
857
+ /**
858
+ * Issues one grant in the state's next slot under its current generation:
859
+ * the next slot and the outstanding count move by one, and the result
860
+ * carries the datum of the new grant UTxO.
861
+ *
862
+ * @param grants - every grant of the account, from its grant UTxOs and from
863
+ * issues still in flight, to keep one current grant per agent key
864
+ */
865
+ const issueCustodyGrant = (state, { grantee, scope, now }, grants) => {
866
+ if (state.nextSlot >= require_tokens.CUSTODY_SLOT_LIMIT) return require_hex.fail({
867
+ code: "slot-space-exhausted",
868
+ nextSlot: state.nextSlot
869
+ });
870
+ const grant = {
871
+ slot: state.nextSlot,
872
+ grantee,
873
+ generation: state.grantGeneration,
874
+ scope
875
+ };
876
+ const error = checkAuthoredGrant(state, grant, now) ?? checkGranteeHoldsNoCurrentGrant(state, grants, {
877
+ grantee,
878
+ now
879
+ });
880
+ if (error) return require_hex.fail(error);
881
+ if (state.outstanding >= BigInt(16)) return require_hex.fail({
882
+ code: "grant-limit",
883
+ limit: 16
884
+ });
885
+ return require_hex.ok({
886
+ state: {
887
+ ...state,
888
+ nextSlot: state.nextSlot + 1n,
889
+ outstanding: state.outstanding + 1n
890
+ },
891
+ grant
892
+ });
893
+ };
894
+ /**
895
+ * Revokes the current grant in `slot` by appending the slot to the revoked
896
+ * list. The grant UTxO stays until a sweep burns its token.
897
+ *
898
+ * @param grants - every grant of the account; the one in `slot` must be current
899
+ */
900
+ const revokeCustodyGrant = (state, slot, grants) => {
901
+ const grant = grants.find((candidate) => candidate.slot === slot);
902
+ if (!grant) return require_hex.fail({
903
+ code: "grant-not-found",
904
+ slot
905
+ });
906
+ if (!require_outputs.isCustodyGrantCurrent(grant, state)) return require_hex.fail({
907
+ code: "grant-not-current",
908
+ slot,
909
+ liveness: require_grant_status.custodyGrantLiveness(grant, state, 0)
910
+ });
911
+ if (state.revoked.length >= 32) return require_hex.fail({
912
+ code: "revoked-list-full",
913
+ limit: 32
914
+ });
915
+ return require_hex.ok({
916
+ ...state,
917
+ revoked: [...state.revoked, slot]
918
+ });
919
+ };
920
+ /**
921
+ * Revokes the current grant in `request.slot` and issues its replacement in
922
+ * the next slot, in one state. Both happen in one transaction; the old
923
+ * grant UTxO stays until a later sweep.
924
+ *
925
+ * @param grants - every grant of the account, as for `issueCustodyGrant`
926
+ */
927
+ const replaceCustodyGrant = (state, { slot, ...request }, grants) => {
928
+ const revoked = revokeCustodyGrant(state, slot, grants);
929
+ return revoked.ok ? issueCustodyGrant(revoked.value, request, grants) : revoked;
930
+ };
931
+ /** Kills every grant: the generation moves on and the revoked list clears. */
932
+ const revokeAllCustodyGrants = (state) => ({
933
+ ...state,
934
+ grantGeneration: state.grantGeneration + 1n,
935
+ revoked: []
936
+ });
937
+ /**
938
+ * Sweeps dead grants: their tokens burn, the outstanding count drops, and
939
+ * their slots leave the revoked list. Pruning is safe because slots are never
940
+ * reissued, and it keeps the list from filling up. Each grant must be dead
941
+ * against `state`, the state before the sweep.
942
+ *
943
+ * @param grants - the grants to sweep, at most `CUSTODY_MAX_GRANT_BATCH`
944
+ * @param now - POSIX milliseconds; a grant dead by expiry alone must have
945
+ * expired strictly before it, so a validity interval can start after the
946
+ * expiry
947
+ */
948
+ const sweepCustodyGrants = (state, grants, now) => {
949
+ if (!Number.isFinite(now)) return require_hex.fail({
950
+ code: "invalid-now",
951
+ now
952
+ });
953
+ if (grants.length === 0) return require_hex.fail({ code: "sweep-empty" });
954
+ if (grants.length > 8) return require_hex.fail({
955
+ code: "sweep-batch-limit",
956
+ count: grants.length,
957
+ limit: 8
958
+ });
959
+ const slots = /* @__PURE__ */ new Set();
960
+ let startsAfter;
961
+ for (const grant of grants) {
962
+ if (slots.has(grant.slot)) return require_hex.fail({
963
+ code: "duplicate-grant-slot",
964
+ slot: grant.slot
965
+ });
966
+ slots.add(grant.slot);
967
+ const liveness = require_grant_status.custodyGrantLiveness(grant, state, now);
968
+ const { expiresAt } = grant.scope;
969
+ if (!(liveness === "superseded" || liveness === "revoked" || liveness === "expired" && expiresAt < BigInt(Math.floor(now)))) return require_hex.fail({
970
+ code: "grant-not-dead",
971
+ slot: grant.slot,
972
+ liveness
973
+ });
974
+ if (liveness === "expired" && (startsAfter === void 0 || expiresAt > startsAfter)) startsAfter = expiresAt;
975
+ }
976
+ return require_hex.ok({
977
+ state: {
978
+ ...state,
979
+ revoked: state.revoked.filter((slot) => !slots.has(slot)),
980
+ outstanding: state.outstanding - BigInt(grants.length)
981
+ },
982
+ ...startsAfter !== void 0 && { startsAfter }
983
+ });
984
+ };
985
+ /**
986
+ * Lists a new device key. Every device has full owner authority, so a key
987
+ * that holds a current grant is refused: removing the device later would
988
+ * leave the grant in force.
989
+ *
990
+ * @param grants - every grant of the account
991
+ */
992
+ const addCustodyDevice = (state, device, grants) => {
993
+ if (!require_hex.isHash28Hex(device)) return require_hex.fail({
994
+ code: "invalid-device-key-hash",
995
+ device
996
+ });
997
+ if (state.devices.includes(device)) return require_hex.fail({
998
+ code: "device-already-listed",
999
+ device
1000
+ });
1001
+ if (state.devices.length >= 8) return require_hex.fail({
1002
+ code: "device-limit",
1003
+ limit: 8
1004
+ });
1005
+ const slots = grants.filter((grant) => grant.grantee === device && require_outputs.isCustodyGrantCurrent(grant, state)).map((grant) => grant.slot);
1006
+ if (slots.length > 0) return require_hex.fail({
1007
+ code: "device-is-grantee",
1008
+ device,
1009
+ slots
1010
+ });
1011
+ return require_hex.ok({
1012
+ ...state,
1013
+ devices: [...state.devices, device]
1014
+ });
1015
+ };
1016
+ /**
1017
+ * Removes a device key. Refuses when no well-formed device key would remain,
1018
+ * because the account would then accept no owner operation ever again. Grants
1019
+ * held by the same key stay in force.
1020
+ */
1021
+ const removeCustodyDevice = (state, device) => {
1022
+ const listed = state.devices.find((existing) => existing === device);
1023
+ if (listed === void 0) return require_hex.fail({
1024
+ code: "device-not-listed",
1025
+ device
1026
+ });
1027
+ const devices = state.devices.filter((existing) => existing !== device);
1028
+ if (!devices.some((remaining) => require_hex.isHash28Hex(remaining))) return require_hex.fail({
1029
+ code: "last-device",
1030
+ device: listed
1031
+ });
1032
+ return require_hex.ok({
1033
+ ...state,
1034
+ devices
1035
+ });
1036
+ };
1037
+ //#endregion
1038
+ //#region ../../packages/contract/cardano-context/src/estimate-signed-tx-size.ts
1039
+ const DUMMY_SIGNATURE = "f".repeat(128);
1040
+ const dummyVkey = (index) => index.toString(16).padStart(64, "0");
1041
+ /**
1042
+ * Byte length of `tx` once signed. Estimated exactly by populating one dummy
1043
+ * vkey witness per unique signer and letting the serializer measure the result
1044
+ * (the cardano-sdk `getTxSize` technique), which counts the real Conway witness
1045
+ * set framing, unlike a hand-summed byte formula.
1046
+ *
1047
+ * `resolvedInputs` must resolve every input so the signer count (input payment
1048
+ * keys plus withdrawal / certificate stake keys) is complete: an incomplete set
1049
+ * would under-count witnesses and under-estimate the size, the dangerous
1050
+ * direction for a size guard.
1051
+ */
1052
+ const estimateSignedTxSize = (tx, resolvedInputs) => {
1053
+ const core = tx.toCore();
1054
+ const signerCount = require_TransactionBuilder.getUniqueSignerKeyHashes(core, resolvedInputs).size;
1055
+ const signatures = /* @__PURE__ */ new Map();
1056
+ for (let index = 0; index < signerCount; index++) signatures.set(dummyVkey(index), DUMMY_SIGNATURE);
1057
+ return _laceio_cardano_core.Serialization.Transaction.fromCore({
1058
+ ...core,
1059
+ witness: {
1060
+ ...core.witness,
1061
+ signatures
1062
+ }
1063
+ }).toCbor().length / 2;
1064
+ };
1065
+ //#endregion
1066
+ //#region ../../packages/contract/cardano-context/src/tx-builder/transaction-limits.ts
1067
+ /**
1068
+ * Conway's cap on the reference-script bytes one transaction reads, summed
1069
+ * over its spent and reference inputs. A ledger constant, not a protocol
1070
+ * parameter.
1071
+ */
1072
+ const MAX_REFERENCE_SCRIPT_SIZE_PER_TX = 204800;
1073
+ /** A built transaction would be refused by the ledger for exceeding a limit. */
1074
+ var TransactionLimitsExceededError = class extends Error {
1075
+ constructor(exceeded, usage, limits) {
1076
+ 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`);
1077
+ this.exceeded = exceeded;
1078
+ this.usage = usage;
1079
+ this.limits = limits;
1080
+ this.name = "TransactionLimitsExceededError";
1081
+ }
1082
+ };
1083
+ /**
1084
+ * Checks a built transaction against the per-transaction execution-unit,
1085
+ * size and reference-script limits, which `TransactionBuilder` does not
1086
+ * enforce. Run it on the final (evaluated) transaction. The size is measured
1087
+ * with one vkey witness per required signer, so `resolvedInputs` must resolve
1088
+ * every input and collateral input, as for `estimateSignedTxSize`. It must
1089
+ * also resolve every reference input, whose scripts count toward the
1090
+ * reference-script limit.
1091
+ *
1092
+ * @returns The measured usage when every limit holds.
1093
+ * @throws `TransactionLimitsExceededError` When any limit is exceeded.
1094
+ */
1095
+ const assertWithinTransactionLimits = (tx, resolvedInputs, limits) => {
1096
+ const redeemers = tx.witnessSet().redeemers()?.toCore() ?? [];
1097
+ const body = tx.body().toCore();
1098
+ const usage = {
1099
+ executionUnits: redeemers.reduce((total, { executionUnits }) => ({
1100
+ memory: total.memory + executionUnits.memory,
1101
+ steps: total.steps + executionUnits.steps
1102
+ }), {
1103
+ memory: 0,
1104
+ steps: 0
1105
+ }),
1106
+ signedTxSize: estimateSignedTxSize(tx, resolvedInputs),
1107
+ referenceScriptSize: require_TransactionBuilder.referenceScriptSize([...body.inputs, ...body.referenceInputs ?? []], resolvedInputs)
1108
+ };
1109
+ const exceeded = [];
1110
+ if (usage.executionUnits.memory > limits.maxExecutionUnitsPerTransaction.memory) exceeded.push("executionMemory");
1111
+ if (usage.executionUnits.steps > limits.maxExecutionUnitsPerTransaction.steps) exceeded.push("executionSteps");
1112
+ if (usage.signedTxSize > limits.maxTxSize) exceeded.push("txSize");
1113
+ if (usage.referenceScriptSize > MAX_REFERENCE_SCRIPT_SIZE_PER_TX) exceeded.push("referenceScriptSize");
1114
+ if (exceeded.length > 0) throw new TransactionLimitsExceededError(exceeded, usage, limits);
1115
+ return usage;
1116
+ };
1117
+ //#endregion
1118
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/build-failure.ts
1119
+ /**
1120
+ * Stops a custody build with a typed reason. The build side effect reports
1121
+ * it as the failure; any other error thrown during a build is a defect and
1122
+ * is reported as `build-failed`.
1123
+ */
1124
+ var CustodyBuildFailure = class extends Error {
1125
+ constructor(reason) {
1126
+ super(`Custody transaction build failed: ${reason.code}`);
1127
+ this.reason = reason;
1128
+ this.name = "CustodyBuildFailure";
1129
+ }
1130
+ };
1131
+ const failCustodyBuild = (reason) => {
1132
+ throw new CustodyBuildFailure(reason);
1133
+ };
1134
+ /**
1135
+ * The build failure a state transition's refusal stands for. A full account
1136
+ * the owner can act on has its own code: sweep dead grants to free the
1137
+ * outstanding count or the revoked list, or wait for a grant to die.
1138
+ */
1139
+ const transitionFailureOf = (error) => {
1140
+ if (error.code === "grant-limit" || error.code === "revoked-list-full") return {
1141
+ code: error.code,
1142
+ limit: error.limit
1143
+ };
1144
+ if (error.code === "grant-not-dead") return {
1145
+ code: "sweep-not-dead",
1146
+ slot: error.slot,
1147
+ liveness: error.liveness
1148
+ };
1149
+ return {
1150
+ code: "invalid-operation",
1151
+ reason: "state-transition",
1152
+ error
1153
+ };
1154
+ };
1155
+ /** What a library transition produced, or the build refused with its reason. */
1156
+ const transitioned = (result) => result.ok ? result.value : failCustodyBuild(transitionFailureOf(result.error));
1157
+ const messageOf = (error) => error instanceof Error ? error.message : String(error);
1158
+ //#endregion
1159
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/custody-execution-estimate.ts
1160
+ /**
1161
+ * Net memory units of one run of each handler: the largest figure the
1162
+ * budget table of contract b48545f's security review ("Resource
1163
+ * exhaustion": `aiken check -D` less each fixture's baseline, over the
1164
+ * largest state) gives for a transaction Lace builds, which issues one
1165
+ * grant, sweeps at most eight and spends at most twelve funds.
1166
+ */
1167
+ const HANDLER_NET_MEMORY = {
1168
+ device: 48e4,
1169
+ spendWithGrant: 4e5,
1170
+ sweepGrant: 43e4,
1171
+ fund: 2e5,
1172
+ issueGrants: 15e4,
1173
+ burnGrants: 34e4,
1174
+ createAccount: 15e4,
1175
+ operate: 16e4
1176
+ };
1177
+ /**
1178
+ * Net memory units of the logic run per path, from the same table, and
1179
+ * what each issued grant, swept grant and fund input adds: the owner path
1180
+ * runs 1.11 M for a revoke, 1.99 M issuing one grant, 1.54 M sweeping one
1181
+ * and 1.21 M with a reserve beside the control; the agent path 1.96 M over
1182
+ * one deposit and 5.94 M over forty.
1183
+ *
1184
+ * Summed per transaction they reach at least the review's figures, given
1185
+ * in brackets: a creation 0.75 M (0.73 M), a revoke 1.59 M (1.33 M), an
1186
+ * issue 2.62 M (2.41 M), a sweep of eight 8.81 M (8.08 M) and an agent
1187
+ * spend over eight funds 4.76 M (4.17 M).
1188
+ */
1189
+ const LOGIC_NET_MEMORY = {
1190
+ path: {
1191
+ agent: 196e4,
1192
+ arrival: 44e4,
1193
+ owner: 111e4
1194
+ },
1195
+ issuedGrant: 88e4,
1196
+ sweptGrant: 43e4,
1197
+ fund: 1e5
1198
+ };
1199
+ /**
1200
+ * What one run is expected to cost on chain, as a multiple of its net
1201
+ * figure. The runner leaves out decoding the script context, which every
1202
+ * run pays and which grows with the transaction. On upstream's devnet with
1203
+ * Preprod's parameters (docs/devnet-evidence.md, "Budget comparison") a
1204
+ * revoke over the largest state measured 1.74 M against 1.10 M net, a
1205
+ * sweep of eight 9.20 M against 8.26 M and an agent spend over twelve
1206
+ * deposits 4.28 M. A transaction that measures above the estimate is
1207
+ * reseeded once at the measurement.
1208
+ */
1209
+ const CUSTODY_EXECUTION_ESTIMATE_FACTOR = 2;
1210
+ /**
1211
+ * CPU steps the estimate charges per memory unit. Every redeemer of the
1212
+ * devnet run measured between 290 and 460 steps per memory unit, the
1213
+ * smallest Fund runs at the top of that range; the large logic and proxy
1214
+ * runs, which dominate every total, stay below 370.
1215
+ */
1216
+ const CUSTODY_ESTIMATE_STEPS_PER_MEMORY_UNIT = 400;
1217
+ /**
1218
+ * What a reseed adds to the measured execution units, in percent. The
1219
+ * measurement already holds the evaluation margin; this one covers the
1220
+ * reseeded transaction measuring differently, since a higher fee changes
1221
+ * its change output.
1222
+ */
1223
+ const CUSTODY_RESEED_MARGIN_PERCENT = 10;
1224
+ const withReseedMargin = (units) => Math.ceil(units * (100 + CUSTODY_RESEED_MARGIN_PERCENT) / 100);
1225
+ 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;
1226
+ /**
1227
+ * The execution units a transaction that runs `runs` is expected to
1228
+ * measure: the handlers' and the logic's net memory units times the
1229
+ * estimate factor, and steps in proportion. It is not capped at the
1230
+ * per-transaction limit.
1231
+ */
1232
+ const custodyExecutionEstimate = (runs) => {
1233
+ const memory = Object.keys(HANDLER_NET_MEMORY).reduce((total, handler) => total + HANDLER_NET_MEMORY[handler] * (runs[handler] ?? 0), logicNetMemory(runs)) * CUSTODY_EXECUTION_ESTIMATE_FACTOR;
1234
+ return {
1235
+ memory,
1236
+ steps: memory * CUSTODY_ESTIMATE_STEPS_PER_MEMORY_UNIT
1237
+ };
1238
+ };
1239
+ /**
1240
+ * The plan's estimate within the per-transaction limit, which no valid
1241
+ * transaction exceeds: what balancing prices a plan's fee at until a
1242
+ * reseed.
1243
+ */
1244
+ const estimateWithinLimit = (estimate, limit) => ({
1245
+ memory: Math.min(estimate.memory, limit.memory),
1246
+ steps: Math.min(estimate.steps, limit.steps)
1247
+ });
1248
+ /** What a build learned from redeemers that measured above `seeded`. */
1249
+ const custodyReseedOf = (evaluated, seeded) => {
1250
+ const sum = evaluated.reduce((total, units) => ({
1251
+ memory: total.memory + units.memory,
1252
+ steps: total.steps + units.steps
1253
+ }), {
1254
+ memory: 0,
1255
+ steps: 0
1256
+ });
1257
+ return {
1258
+ measured: {
1259
+ memory: Math.max(sum.memory, seeded.memory),
1260
+ steps: Math.max(sum.steps, seeded.steps)
1261
+ },
1262
+ redeemers: evaluated.length,
1263
+ dearest: {
1264
+ memory: Math.max(0, ...evaluated.map(({ memory }) => memory)),
1265
+ steps: Math.max(0, ...evaluated.map(({ steps }) => steps))
1266
+ }
1267
+ };
1268
+ };
1269
+ /**
1270
+ * What a reseed prices a plan of `redeemers` redeemers at: the measurement
1271
+ * plus the reseed margin, with each redeemer the plan has beyond the
1272
+ * measured transaction, such as a fund input the selection round added,
1273
+ * priced as the dearest one measured. It is not capped at the
1274
+ * per-transaction limit, so a transaction over the limit still builds and
1275
+ * its limit check reports the overrun.
1276
+ */
1277
+ const reseededExecutionUnits = ({ measured, redeemers: measuredRedeemers, dearest }, redeemers) => {
1278
+ const added = Math.max(0, redeemers - measuredRedeemers);
1279
+ return {
1280
+ memory: withReseedMargin(measured.memory + added * dearest.memory),
1281
+ steps: withReseedMargin(measured.steps + added * dearest.steps)
1282
+ };
1283
+ };
1284
+ //#endregion
1285
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/custody-assembly.ts
1286
+ /**
1287
+ * The account's control output: lovelace and the state NFT only, with the
1288
+ * state encoded afresh as an inline datum.
1289
+ */
1290
+ const controlOutputOf = (identity, state, lovelace) => ({
1291
+ address: identity.address,
1292
+ value: {
1293
+ coins: lovelace,
1294
+ assets: new Map([[identity.stateNftAssetId, 1n]])
1295
+ },
1296
+ datum: require_outputs.encodeCustodyAccountState(state)
1297
+ });
1298
+ /**
1299
+ * A grant UTxO at the account: lovelace and the grant's token only, with
1300
+ * the grant as an inline datum and no reference script.
1301
+ */
1302
+ const grantOutputOf = (identity, grant, lovelace) => ({
1303
+ address: identity.address,
1304
+ value: {
1305
+ coins: lovelace,
1306
+ assets: new Map([[require_tokens.custodyGrantAssetId({
1307
+ accountValidatorHash: identity.contractVersion.accountValidatorHash,
1308
+ stakeScriptHash: identity.stakeScriptHash
1309
+ }, grant.slot), 1n]])
1310
+ },
1311
+ datum: require_outputs.encodeCustodyGrant(grant)
1312
+ });
1313
+ /**
1314
+ * The least lovelace an output holds at its own size, at least `floor`. The
1315
+ * coin's own width changes the size, so the minimum is taken until it
1316
+ * settles.
1317
+ */
1318
+ const outputLovelaceFor = (outputWith, floor, coinsPerUtxoByte) => {
1319
+ let lovelace = floor;
1320
+ for (;;) {
1321
+ const minimum = (0, _laceio_cardano_tx_construction.minAdaRequired)(outputWith(lovelace), BigInt(coinsPerUtxoByte));
1322
+ if (minimum <= lovelace) return lovelace;
1323
+ lovelace = minimum;
1324
+ }
1325
+ };
1326
+ /**
1327
+ * The lovelace a control output carrying `state` holds: at least `floor`
1328
+ * and at least the minimum for its size.
1329
+ */
1330
+ const controlLovelaceFor = ({ identity, state, floor }, coinsPerUtxoByte) => outputLovelaceFor((lovelace) => controlOutputOf(identity, state, lovelace), floor, coinsPerUtxoByte);
1331
+ /** What `units` of execution cost at the protocol's prices. */
1332
+ const executionFeeOf = (units, { prices }) => BigInt(Math.ceil(prices.memory * units.memory + prices.steps * units.steps));
1333
+ /**
1334
+ * The most the builder prices a fee at before evaluation, when it seeds
1335
+ * `seed` execution units: the largest transaction's size fee plus the seed
1336
+ * at the protocol's prices. Balancing must fund that fee from the inputs
1337
+ * before evaluation lowers it.
1338
+ */
1339
+ const preEvaluationFeeBound = (parameters, seed) => BigInt(parameters.minFeeConstant) + BigInt(parameters.minFeeCoefficient) * BigInt(parameters.maxTxSize) + executionFeeOf(seed, parameters);
1340
+ /**
1341
+ * The least fee a plan's transaction pays, whatever its body and its
1342
+ * scripts measure: the size fee of the scripts it attaches and the
1343
+ * reference-script fee of the scripts its reference inputs carry.
1344
+ */
1345
+ const leastFeeOf = (plan, parameters) => {
1346
+ const references = plan.referenceInputs ?? [];
1347
+ return BigInt(parameters.minFeeConstant) + BigInt(parameters.minFeeCoefficient) * BigInt(plan.scripts.reduce((bytes, script) => bytes + script.bytes.length / 2, 0)) + require_TransactionBuilder.referenceScriptFee(require_TransactionBuilder.referenceScriptSize(references.map(([txIn]) => txIn), references), parameters);
1348
+ };
1349
+ /**
1350
+ * The fee a plan is expected to balance at before evaluation, less the
1351
+ * bytes of its body beyond its scripts: its least fee and its seed
1352
+ * execution units at the protocol's prices. Fund selection aims at it; a
1353
+ * selection that falls short gains the next fund UTxO when balancing
1354
+ * fails, before any evaluation.
1355
+ */
1356
+ const plannedFeeOf = (plan, parameters) => leastFeeOf(plan, parameters) + executionFeeOf(estimateWithinLimit(plan.executionEstimate, parameters.maxExecutionUnitsPerTransaction), parameters);
1357
+ const quantityOf = (utxo, assetId) => utxo[1].value.assets?.get(assetId) ?? 0n;
1358
+ const byCoinsDescending = (a, b) => a[1].value.coins === b[1].value.coins ? 0 : a[1].value.coins > b[1].value.coins ? -1 : 1;
1359
+ /**
1360
+ * Large-First selection over fund UTxOs: first the UTxOs holding most of
1361
+ * each requested asset, then the largest by lovelace, with `reserves` after
1362
+ * every fund. When the UTxOs cannot cover the target, everything is
1363
+ * selected, `covers` is false and the build decides: the target holds only
1364
+ * an estimate of the fee.
1365
+ */
1366
+ const selectFunds = (funds, target, reserves = []) => {
1367
+ const remaining = [...[...funds].sort(byCoinsDescending), ...[...reserves].sort(byCoinsDescending)];
1368
+ const selected = [];
1369
+ const take = (utxo) => {
1370
+ remaining.splice(remaining.indexOf(utxo), 1);
1371
+ selected.push(utxo);
1372
+ };
1373
+ for (const [assetId, quantity] of target.assets ?? []) {
1374
+ let covered = selected.reduce((sum, utxo) => sum + quantityOf(utxo, assetId), 0n);
1375
+ while (covered < quantity) {
1376
+ const richest = remaining.reduce((best, utxo) => !best || quantityOf(utxo, assetId) > quantityOf(best, assetId) ? utxo : best, void 0);
1377
+ if (!richest || quantityOf(richest, assetId) === 0n) return {
1378
+ selected: [...selected, ...remaining],
1379
+ remaining: [],
1380
+ covers: false
1381
+ };
1382
+ take(richest);
1383
+ covered += quantityOf(richest, assetId);
1384
+ }
1385
+ }
1386
+ let lovelace = selected.reduce((sum, [, output]) => sum + output.value.coins, 0n);
1387
+ while (lovelace < target.coins && remaining.length > 0) {
1388
+ const [next] = remaining;
1389
+ take(next);
1390
+ lovelace += next[1].value.coins;
1391
+ }
1392
+ return {
1393
+ selected,
1394
+ remaining,
1395
+ covers: lovelace >= target.coins
1396
+ };
1397
+ };
1398
+ /** The sum of outputs' values. */
1399
+ const totalValueOf = (outputs) => {
1400
+ const assets = /* @__PURE__ */ new Map();
1401
+ let coins = 0n;
1402
+ for (const { value } of outputs) {
1403
+ coins += value.coins;
1404
+ for (const [assetId, quantity] of value.assets ?? []) assets.set(assetId, (assets.get(assetId) ?? 0n) + quantity);
1405
+ }
1406
+ return {
1407
+ coins,
1408
+ assets
1409
+ };
1410
+ };
1411
+ /**
1412
+ * The funds can no longer pay for the transaction; one more fund UTxO may.
1413
+ * Once none is left, the build fails with `exhausted`, or with
1414
+ * `insufficient-funds` when it is absent.
1415
+ */
1416
+ var FundShortfall = class extends Error {
1417
+ constructor(message, exhausted) {
1418
+ super(message);
1419
+ this.exhausted = exhausted;
1420
+ this.name = "FundShortfall";
1421
+ }
1422
+ };
1423
+ /**
1424
+ * The evaluated transaction needs a larger fee than its plan balanced at:
1425
+ * the scripts measured above the execution estimate. Building again seeded
1426
+ * from `reseed` funds it.
1427
+ */
1428
+ var ExecutionEstimateShortfall = class extends Error {
1429
+ constructor(reseed) {
1430
+ super("The scripts measure above the execution estimate");
1431
+ this.reseed = reseed;
1432
+ this.name = "ExecutionEstimateShortfall";
1433
+ }
1434
+ };
1435
+ /**
1436
+ * The transaction needs a fee above the most it may be balanced at. `fee`
1437
+ * is the fee it needs, or, when even the fee of its body without execution
1438
+ * is above that most, that fee. `evaluatedRedeemers` holds the budgets the
1439
+ * fee was priced at when an evaluation priced it.
1440
+ */
1441
+ var MaxBalancedFeeExceeded = class extends Error {
1442
+ constructor(fee, evaluatedRedeemers = []) {
1443
+ super(`The transaction needs a fee of ${fee} lovelace, above the most it may be balanced at`);
1444
+ this.fee = fee;
1445
+ this.evaluatedRedeemers = evaluatedRedeemers;
1446
+ this.name = "MaxBalancedFeeExceeded";
1447
+ }
1448
+ };
1449
+ /** The builder balanced a draft above the most fee allowed; nothing evaluated it. */
1450
+ var DraftFeeAboveMax = class extends Error {
1451
+ constructor(fee) {
1452
+ super(`The draft is balanced at ${fee} lovelace, above the most allowed`);
1453
+ this.fee = fee;
1454
+ this.name = "DraftFeeAboveMax";
1455
+ }
1456
+ };
1457
+ /** Stops a draft balanced above `maxFee` before the provider evaluates it. */
1458
+ const withMaxDraftFee = (evaluator, maxFee) => ({ evaluate: async (tx, resolvedInputs) => {
1459
+ if (tx.body.fee > maxFee) throw new DraftFeeAboveMax(tx.body.fee);
1460
+ return evaluator.evaluate(tx, resolvedInputs);
1461
+ } });
1462
+ /**
1463
+ * `seed` scaled so that its execution costs at most `fee` at the protocol's
1464
+ * prices, which are linear in the units.
1465
+ */
1466
+ const seedPricedAt = (seed, fee, parameters) => {
1467
+ const seedFee = executionFeeOf(seed, parameters);
1468
+ if (seedFee === 0n) return seed;
1469
+ const scale = Number(fee) / Number(seedFee);
1470
+ return {
1471
+ memory: Math.floor(seed.memory * scale),
1472
+ steps: Math.floor(seed.steps * scale)
1473
+ };
1474
+ };
1475
+ /**
1476
+ * Seed changes that keep a draft within the most fee it may be balanced
1477
+ * at. A draft lands within a few bytes of the fee its seed aims at, so one
1478
+ * change usually suffices.
1479
+ */
1480
+ const MAX_DRAFT_FEE_ROUNDS = 4;
1481
+ /**
1482
+ * The plain change output a collateral-mode build returns to the account:
1483
+ * the largest output at `address` after the planned ones, without a datum.
1484
+ * -1 when there is none.
1485
+ */
1486
+ const changeIndexOf = (outputs, address, plannedCount) => {
1487
+ let changeIndex = -1;
1488
+ 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;
1489
+ return changeIndex;
1490
+ };
1491
+ const withBody = (transaction, fields) => {
1492
+ const core = transaction.toCore();
1493
+ return _laceio_cardano_core.Serialization.Transaction.fromCore({
1494
+ ...core,
1495
+ body: {
1496
+ ...core.body,
1497
+ ...fields
1498
+ }
1499
+ });
1500
+ };
1501
+ const hasGrown = (rewritten, transaction) => rewritten.body().toCbor().length > transaction.body().toCbor().length;
1502
+ const grewBody = () => failCustodyBuild({
1503
+ code: "build-failed",
1504
+ message: "Moving lovelace between outputs grew the body"
1505
+ });
1506
+ /**
1507
+ * The transaction with `outputs` in place of its own. The fee was priced
1508
+ * for the body's size, so the body must not grow, as it would when a coin
1509
+ * crosses a CBOR width boundary. A smaller body keeps a fee that also pays
1510
+ * for the bytes it dropped.
1511
+ */
1512
+ const withOutputs = (transaction, outputs) => {
1513
+ const rewritten = withBody(transaction, { outputs });
1514
+ return hasGrown(rewritten, transaction) ? grewBody() : rewritten;
1515
+ };
1516
+ /** `output` holding `coins` lovelace. */
1517
+ const withCoins = (output, coins) => ({
1518
+ ...output,
1519
+ value: {
1520
+ ...output.value,
1521
+ coins
1522
+ }
1523
+ });
1524
+ /**
1525
+ * The transaction with `outputs` in place of its own and its fee raised by
1526
+ * `excess`. The declared collateral rises with the fee, taken from the
1527
+ * collateral return, which must keep its minimum ADA.
1528
+ */
1529
+ const withExcessInFee = (transaction, { outputs, excess }, { collateralPercentage, coinsPerUtxoByte }) => {
1530
+ const { fee, totalCollateral, collateralReturn } = transaction.body().toCore();
1531
+ const raised = fee + excess;
1532
+ const required = (raised * BigInt(collateralPercentage) + 99n) / 100n;
1533
+ let collateral = {};
1534
+ if (totalCollateral !== void 0 && totalCollateral < required) {
1535
+ const returned = collateralReturn && withCoins(collateralReturn, collateralReturn.value.coins - (required - totalCollateral));
1536
+ if (!returned || returned.value.coins < (0, _laceio_cardano_tx_construction.minAdaRequired)(returned, BigInt(coinsPerUtxoByte))) return failCustodyBuild({
1537
+ code: "collateral-insufficient",
1538
+ message: `The collateral cannot cover a fee of ${raised} lovelace`
1539
+ });
1540
+ collateral = {
1541
+ totalCollateral: required,
1542
+ collateralReturn: returned
1543
+ };
1544
+ }
1545
+ const rewritten = withBody(transaction, {
1546
+ outputs,
1547
+ fee: raised,
1548
+ ...collateral
1549
+ });
1550
+ return hasGrown(rewritten, transaction) ? grewBody() : rewritten;
1551
+ };
1552
+ /**
1553
+ * Moves the lovelace the builder's fee correction credited to a planned
1554
+ * output back to the change. The correction refunds the largest output at
1555
+ * the change address, and in collateral mode the planned account outputs
1556
+ * share that address: a control output would lock the lovelace for the
1557
+ * account's life, and a grant output must hold what was planned.
1558
+ *
1559
+ * The change takes it unless that grows the body the fee was priced for,
1560
+ * which happens only when its lovelace crosses 2^32; the fee then keeps
1561
+ * the saving instead.
1562
+ *
1563
+ * @throws `FundShortfall` when no change output exists to take it
1564
+ */
1565
+ const restorePlannedOutputs = (transaction, plan, parameters) => {
1566
+ const outputs = [...transaction.body().toCore().outputs];
1567
+ let excess = 0n;
1568
+ for (const [index, planned] of plan.outputs.entries()) {
1569
+ const credited = outputs[index].value.coins - planned.value.coins;
1570
+ if (credited > 0n) {
1571
+ excess += credited;
1572
+ outputs[index] = withCoins(outputs[index], planned.value.coins);
1573
+ }
1574
+ }
1575
+ if (excess === 0n) return transaction;
1576
+ const changeIndex = changeIndexOf(outputs, plan.changeAddress, plan.outputs.length);
1577
+ if (changeIndex < 0) throw new FundShortfall("No change output to return the fee saving to");
1578
+ const credited = [...outputs];
1579
+ credited[changeIndex] = withCoins(outputs[changeIndex], outputs[changeIndex].value.coins + excess);
1580
+ const rewritten = withBody(transaction, { outputs: credited });
1581
+ return hasGrown(rewritten, transaction) ? withExcessInFee(transaction, {
1582
+ outputs,
1583
+ excess
1584
+ }, parameters) : rewritten;
1585
+ };
1586
+ /**
1587
+ * The lovelace the first output of a plan whose first output pays the fee
1588
+ * must hold to balance at the fee the builder settled on.
1589
+ */
1590
+ var FirstOutputCoinsMissed = class extends Error {
1591
+ constructor(coins) {
1592
+ super(`The first output must hold ${coins} lovelace to balance`);
1593
+ this.coins = coins;
1594
+ this.name = "FirstOutputCoinsMissed";
1595
+ }
1596
+ };
1597
+ /**
1598
+ * Coin selection for a plan whose first output pays the fee: it spends
1599
+ * exactly the preselected inputs and returns no change. The balancer prices
1600
+ * each target and asks again until the fee settles; when the settled target
1601
+ * still misses the inputs, the build stops with the first output's lovelace
1602
+ * that meets it.
1603
+ */
1604
+ const firstOutputPaysSelector = (firstOutputCoins) => {
1605
+ let previousTarget;
1606
+ return { select: ({ preSelectedUtxo = [], targetValue }) => {
1607
+ const surplus = preSelectedUtxo.reduce((total, [, output]) => total + output.value.coins, 0n) - targetValue.coins;
1608
+ if (surplus !== 0n && previousTarget === targetValue.coins) throw new FirstOutputCoinsMissed(firstOutputCoins + surplus);
1609
+ previousTarget = targetValue.coins;
1610
+ return {
1611
+ selection: [...preSelectedUtxo],
1612
+ remaining: [],
1613
+ changeOutputs: []
1614
+ };
1615
+ } };
1616
+ };
1617
+ /**
1618
+ * Builds and balances one plan with the shared `TransactionBuilder`. Every
1619
+ * input is preselected, so balancing never adds an input without its
1620
+ * redeemer; the collateral is the sponsor's shared UTxO, returned to the
1621
+ * sponsor with total collateral set.
1622
+ *
1623
+ * @throws `FundShortfall` when the inputs cannot pay for the transaction
1624
+ * @throws `EvaluatedFeeShortfallError` when the scripts measure above `seed`
1625
+ * by more than the balanced fee covers
1626
+ */
1627
+ const buildPlan = async (plan, chain, { evaluator, coinSelector, seed }) => {
1628
+ const parameters = chain.protocolParameters;
1629
+ const referenceInputs = plan.referenceInputs ?? [];
1630
+ const builder = new require_TransactionBuilder.TransactionBuilder(chain.chainId.networkMagic, parameters).setChangeAddress(plan.changeAddress).useCoinSelector(coinSelector).setCollateralUtxos([plan.collateral.utxo]).setCollateralChangeAddress(plan.collateral.returnAddress).setPlutusContext({
1631
+ costModels: parameters.costModels,
1632
+ txEvaluator: evaluator,
1633
+ inputResolver: require_util.createInputResolver([...plan.inputs.map(({ utxo }) => utxo), plan.collateral.utxo]),
1634
+ executionUnitsEstimate: seed
1635
+ }).setValidityInterval({
1636
+ ...plan.invalidBefore !== void 0 && { invalidBefore: plan.invalidBefore },
1637
+ invalidHereafter: plan.invalidHereafter
1638
+ }).addRequiredSigner(plan.requiredSigner);
1639
+ for (const { utxo, redeemer } of plan.inputs) builder.addInput(utxo, redeemer ? { redeemer } : void 0);
1640
+ for (const utxo of referenceInputs) builder.addReferenceInput(utxo);
1641
+ for (const output of plan.outputs) builder.addOutput(output);
1642
+ for (const script of plan.scripts) builder.attachScript(script);
1643
+ if (plan.mint) builder.mintAssets(plan.mint.assets, plan.mint.redeemer);
1644
+ if (plan.registration) {
1645
+ const { stakeCredential, deposit, redeemer } = plan.registration;
1646
+ builder.addNewStakeAddressCertificate(stakeCredential, deposit, { redeemer });
1647
+ }
1648
+ if (plan.delegation) {
1649
+ const { poolId, stakeCredential, redeemer } = plan.delegation;
1650
+ builder.addStakeDelegationCertificate(poolId, stakeCredential, { redeemer });
1651
+ }
1652
+ for (const { rewardAccount, quantity, redeemer } of plan.withdrawals ?? []) builder.addScriptWithdrawal(rewardAccount, quantity, redeemer);
1653
+ for (const [label, metadatum] of plan.metadata ?? []) builder.setMetadata(label, metadatum);
1654
+ try {
1655
+ return await builder.build();
1656
+ } catch (error) {
1657
+ if (error instanceof require_TransactionBuilder.InputSelectionError) throw new FundShortfall(error.message);
1658
+ if (error instanceof require_TransactionBuilder.InsufficientCollateralError) return failCustodyBuild({
1659
+ code: "collateral-insufficient",
1660
+ message: error.message
1661
+ });
1662
+ throw error;
1663
+ }
1664
+ };
1665
+ /**
1666
+ * Rounds of a plan whose first output pays the fee. The first round finds
1667
+ * the fee the builder settles on, the second lands on it; a third covers a
1668
+ * coin that crossed a CBOR width between them.
1669
+ */
1670
+ const MAX_FIRST_OUTPUT_ROUNDS = 3;
1671
+ /**
1672
+ * Builds a plan whose first output pays the fee. The builder balances at a
1673
+ * fee priced for the seed execution units, so the first output is set to
1674
+ * what the inputs hold beyond that fee and the other outputs; once
1675
+ * evaluation lowers the fee, the builder refunds the saving to it, the
1676
+ * largest output at the change address.
1677
+ *
1678
+ * @throws `FundShortfall` when the inputs cannot pay the fee the builder
1679
+ * balances at
1680
+ */
1681
+ const buildFeePaidByFirstOutput = async (plan, chain, { evaluator, seed }) => {
1682
+ const [first, ...rest] = plan.outputs;
1683
+ let coins = first.value.coins;
1684
+ for (let round = 1;; round++) try {
1685
+ return await buildPlan({
1686
+ ...plan,
1687
+ outputs: [withCoins(first, coins), ...rest]
1688
+ }, chain, {
1689
+ evaluator,
1690
+ coinSelector: firstOutputPaysSelector(coins),
1691
+ seed
1692
+ });
1693
+ } catch (error) {
1694
+ if (!(error instanceof FirstOutputCoinsMissed) || round >= MAX_FIRST_OUTPUT_ROUNDS) throw error;
1695
+ if (error.coins < 0n) throw new FundShortfall("The first output cannot pay the fee the builder balances at");
1696
+ coins = error.coins;
1697
+ }
1698
+ };
1699
+ /** How many redeemers a plan's transaction carries. */
1700
+ const redeemerCountOf = (plan) => plan.inputs.filter(({ redeemer }) => redeemer !== void 0).length + [
1701
+ plan.mint,
1702
+ plan.registration,
1703
+ plan.delegation
1704
+ ].filter((part) => part !== void 0).length + (plan.withdrawals?.length ?? 0);
1705
+ /**
1706
+ * Builds a plan, adding the next fund UTxO each time the selected ones
1707
+ * cannot pay for it. Once none is left, it refuses with the shortfall's
1708
+ * reason, `insufficient-funds` by default. Planned outputs keep their
1709
+ * lovelace unless the first output pays the fee.
1710
+ *
1711
+ * With `maxBalancedFee`, a draft balanced above it gets a seed priced at
1712
+ * that much less, and an evaluated transaction that needs more than its
1713
+ * draft's fee gets a seed priced at what it needs, while that fits.
1714
+ *
1715
+ * @throws `ExecutionEstimateShortfall` when the scripts measure above the
1716
+ * seed by more than the balanced fee covers
1717
+ * @throws `MaxBalancedFeeExceeded` when the transaction needs a fee above
1718
+ * `maxBalancedFee`
1719
+ */
1720
+ const assembleWithFunds = async ({ chain, funds, plan, evaluator, reseed, checkFunds, settle, maxBalancedFee }) => {
1721
+ const parameters = chain.protocolParameters;
1722
+ let { selected, remaining } = funds;
1723
+ let seedFeeLimit;
1724
+ let draftFeeRounds = 0;
1725
+ for (;;) {
1726
+ checkFunds?.(selected);
1727
+ const planned = plan(selected);
1728
+ const fullSeed = reseed ? reseededExecutionUnits(reseed, redeemerCountOf(planned)) : estimateWithinLimit(planned.executionEstimate, parameters.maxExecutionUnitsPerTransaction);
1729
+ const seed = seedFeeLimit === void 0 ? fullSeed : seedPricedAt(fullSeed, seedFeeLimit, parameters);
1730
+ const planEvaluator = maxBalancedFee === void 0 ? evaluator(selected) : withMaxDraftFee(evaluator(selected), maxBalancedFee);
1731
+ try {
1732
+ const built = planned.isFeePaidByFirstOutput ? await buildFeePaidByFirstOutput(planned, chain, {
1733
+ evaluator: planEvaluator,
1734
+ seed
1735
+ }) : restorePlannedOutputs(await buildPlan(planned, chain, {
1736
+ evaluator: planEvaluator,
1737
+ coinSelector: new require_TransactionBuilder.LargeFirstCoinSelector(),
1738
+ seed
1739
+ }), planned, parameters);
1740
+ return {
1741
+ transaction: settle ? settle(built) : built,
1742
+ funds: {
1743
+ selected,
1744
+ remaining
1745
+ }
1746
+ };
1747
+ } catch (error) {
1748
+ if (error instanceof DraftFeeAboveMax && maxBalancedFee !== void 0) {
1749
+ const seedFee = executionFeeOf(seed, parameters);
1750
+ const limit = seedFee - (error.fee - maxBalancedFee);
1751
+ if (limit < 0n) throw new MaxBalancedFeeExceeded(error.fee - seedFee);
1752
+ if (++draftFeeRounds > MAX_DRAFT_FEE_ROUNDS) throw new MaxBalancedFeeExceeded(error.fee);
1753
+ seedFeeLimit = limit;
1754
+ continue;
1755
+ }
1756
+ if (error instanceof require_TransactionBuilder.EvaluatedFeeShortfallError) {
1757
+ if (maxBalancedFee !== void 0 && error.requiredFee > maxBalancedFee) throw new MaxBalancedFeeExceeded(error.requiredFee, error.evaluatedRedeemers);
1758
+ if (seedFeeLimit !== void 0 && ++draftFeeRounds <= MAX_DRAFT_FEE_ROUNDS) {
1759
+ seedFeeLimit = executionFeeOf(seed, parameters) + error.requiredFee - error.balancedFee;
1760
+ continue;
1761
+ }
1762
+ throw new ExecutionEstimateShortfall(custodyReseedOf(error.evaluatedRedeemers.map(({ executionUnits }) => executionUnits), seed));
1763
+ }
1764
+ if (!(error instanceof FundShortfall)) throw error;
1765
+ if (remaining.length === 0) return failCustodyBuild(error.exhausted ?? {
1766
+ code: "insufficient-funds",
1767
+ message: error.message
1768
+ });
1769
+ selected = [...selected, remaining[0]];
1770
+ remaining = remaining.slice(1);
1771
+ }
1772
+ }
1773
+ };
1774
+ const EVALUATION_MARGIN_PERCENT = 10n;
1775
+ /**
1776
+ * Pads a measured budget. The fee correction after evaluation changes the
1777
+ * body the scripts see, so the final transaction is not the one measured.
1778
+ */
1779
+ const withEvaluationMargin = ({ memory, steps }) => ({
1780
+ memory: Number((BigInt(memory) * (100n + EVALUATION_MARGIN_PERCENT) + 99n) / 100n),
1781
+ steps: Number((BigInt(steps) * (100n + EVALUATION_MARGIN_PERCENT) + 99n) / 100n)
1782
+ });
1783
+ const atLeast = (budget, floor) => floor ? {
1784
+ memory: Math.max(budget.memory, floor.memory),
1785
+ steps: Math.max(budget.steps, floor.steps)
1786
+ } : budget;
1787
+ const redeemerKey = ({ purpose, index }) => `${purpose}:${index}`;
1788
+ /** A redeemer the provider did not evaluate fails, as it does for the sponsor service. */
1789
+ const unmeasured = ({ purpose, index }) => failCustodyBuild({
1790
+ code: "script-evaluation-failed",
1791
+ message: `The provider did not evaluate redeemer ${purpose}#${index}`
1792
+ });
1793
+ /**
1794
+ * Every redeemer whose declared budget is below what the provider measures,
1795
+ * with the measurement.
1796
+ */
1797
+ const findUnderDeclaredBudgets = (transaction, measured) => {
1798
+ return (transaction.witnessSet().redeemers()?.toCore() ?? []).flatMap(({ purpose, index, executionUnits }) => {
1799
+ const measurement = measured.find((entry) => entry.purpose === purpose && entry.index === index) ?? unmeasured({
1800
+ purpose,
1801
+ index
1802
+ });
1803
+ return measurement.budget.memory > executionUnits.memory || measurement.budget.steps > executionUnits.steps ? [measurement] : [];
1804
+ });
1805
+ };
1806
+ /**
1807
+ * The outputs a transaction's inputs, reference inputs and collateral
1808
+ * resolve to, in that order, from the UTxOs the build chose them from.
1809
+ */
1810
+ const resolvedInputsOf = (transaction, candidates) => {
1811
+ const body = transaction.body().toCore();
1812
+ const byOutpoint = new Map(candidates.map((utxo) => [require_util.outpointKey(utxo[0]), utxo]));
1813
+ return [
1814
+ ...body.inputs,
1815
+ ...body.referenceInputs ?? [],
1816
+ ...body.collaterals ?? []
1817
+ ].map((input) => byOutpoint.get(require_util.outpointKey(input))).filter((utxo) => utxo !== void 0);
1818
+ };
1819
+ /** A lovelace amount whose CBOR width every change output reaches. */
1820
+ const CHANGE_COIN_WIDTH_PROBE = 1000000n;
1821
+ /** The minimum ADA of a lovelace-only change output at an address. */
1822
+ const changeReserveAt = (address, coinsPerUtxoByte) => (0, _laceio_cardano_tx_construction.minAdaRequired)({
1823
+ address,
1824
+ value: { coins: CHANGE_COIN_WIDTH_PROBE }
1825
+ }, BigInt(coinsPerUtxoByte));
1826
+ const scriptCredentialOf = (hash) => ({
1827
+ type: _laceio_cardano_core.Cardano.CredentialType.ScriptHash,
1828
+ hash
1829
+ });
1830
+ //#endregion
1831
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/custody-chain.ts
1832
+ /**
1833
+ * The slot clock of the era history. A time or slot before the first era
1834
+ * is read in the first era: custody transactions only name slots near the
1835
+ * present.
1836
+ */
1837
+ const createSlotClock = (eraSummaries) => {
1838
+ const eraOf = (isStarted) => eraSummaries.filter(isStarted).at(-1) ?? eraSummaries[0];
1839
+ return {
1840
+ slotAt: (posixMs) => {
1841
+ const era = eraOf(({ start }) => start.time.getTime() <= posixMs);
1842
+ return _laceio_cardano_core.Cardano.Slot(era.start.slot + Math.floor((posixMs - era.start.time.getTime()) / era.parameters.slotLength));
1843
+ },
1844
+ slotStart: (slot) => {
1845
+ const era = eraOf(({ start }) => start.slot <= slot);
1846
+ return era.start.time.getTime() + (slot - era.start.slot) * era.parameters.slotLength;
1847
+ }
1848
+ };
1849
+ };
1850
+ /**
1851
+ * One provider read, retried while transient. A failure stops the build as
1852
+ * `unavailable`, since nothing about the custody account is known yet.
1853
+ */
1854
+ const readFromProvider = async (what, request) => {
1855
+ try {
1856
+ return await (0, rxjs.firstValueFrom)(require_unwrap_provider_result.unwrapProviderResult(request));
1857
+ } catch (error) {
1858
+ return failCustodyBuild({
1859
+ code: "unavailable",
1860
+ message: `${what}: ${messageOf(error)}`
1861
+ });
1862
+ }
1863
+ };
1864
+ const loadCustodyChain = async ({ chainId, eraSummaries, provider, now }) => {
1865
+ if (!chainId) return failCustodyBuild({
1866
+ code: "unavailable",
1867
+ message: "Cardano chain id not available"
1868
+ });
1869
+ if (!eraSummaries || eraSummaries.length === 0) return failCustodyBuild({
1870
+ code: "unavailable",
1871
+ message: "Cardano era summaries not available"
1872
+ });
1873
+ return {
1874
+ chainId,
1875
+ protocolParameters: await readFromProvider("Cardano protocol parameters", () => provider.getProtocolParameters({ chainId })),
1876
+ clock: createSlotClock(eraSummaries),
1877
+ now,
1878
+ provider
1879
+ };
1880
+ };
1881
+ /**
1882
+ * What the provider measures every redeemer of a transaction at. There is
1883
+ * no fallback budget: a script that fails is a build that fails, with the
1884
+ * provider's message. A transient failure, such as a rate limit or a
1885
+ * dropped connection, is retried like every other read and then reported
1886
+ * as `unavailable`, since it says nothing about the scripts.
1887
+ */
1888
+ const evaluateCustodyTransaction = async (transaction, { provider, chainId }) => {
1889
+ const tx = transaction.toCbor();
1890
+ try {
1891
+ return await (0, rxjs.firstValueFrom)(require_unwrap_provider_result.unwrapProviderResult(() => provider.evaluateTx({ tx }, { chainId })));
1892
+ } catch (error) {
1893
+ return failCustodyBuild(require_is_retriable_error.isRetriableError(error) ? {
1894
+ code: "unavailable",
1895
+ message: `Transaction evaluation: ${messageOf(error)}`
1896
+ } : {
1897
+ code: "script-evaluation-failed",
1898
+ message: messageOf(error)
1899
+ });
1900
+ }
1901
+ };
1902
+ /**
1903
+ * The first slot that starts strictly after `posixMs`. A sweep of a grant
1904
+ * dead by expiry alone sets it as the lower validity bound, because the
1905
+ * validator needs the start of that bound to be after the expiry.
1906
+ */
1907
+ const firstSlotStartingAfter = (posixMs, { clock }) => _laceio_cardano_core.Cardano.Slot(clock.slotAt(Number(posixMs)) + 1);
1908
+ /**
1909
+ * The upper validity bound a funding window allows: the slot holding
1910
+ * `validUntil`, which starts no later than it. The window must leave a slot
1911
+ * after the current one.
1912
+ */
1913
+ const validitySlotFor = (validUntil, { clock, now }) => {
1914
+ const slot = Number.isFinite(validUntil) ? clock.slotAt(validUntil) : 0;
1915
+ return slot > clock.slotAt(now) ? _laceio_cardano_core.Cardano.Slot(slot) : failCustodyBuild({
1916
+ code: "validity-window",
1917
+ validUntil
1918
+ });
1919
+ };
1920
+ //#endregion
1921
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/custody-creator-index.ts
1922
+ /**
1923
+ * Reads what the chain holds at one creator index of one contract version:
1924
+ * whether the stake credential of the creator key is registered and, when
1925
+ * it is, whether the account address holds the state NFT. A registered
1926
+ * credential without the NFT was registered by someone else first, which
1927
+ * blocks creation at that index forever.
1928
+ */
1929
+ const readCustodyCreatorIndex = async ({ contractVersion, deviceAccountPublicKey, creatorIndex }, chain) => {
1930
+ const key = await require_grant_status.deriveCustodyDeviceKey(deviceAccountPublicKey, creatorIndex);
1931
+ if (!key.ok) return failCustodyBuild({
1932
+ code: "invalid-operation",
1933
+ reason: "invalid-device-key",
1934
+ message: `Cannot derive custody device key ${creatorIndex}: ${key.error.code}`
1935
+ });
1936
+ const identity = require_identity.deriveCustodyAccountIdentity({
1937
+ contractVersion,
1938
+ creatorKeyHash: key.value.keyHash,
1939
+ chainId: chain.chainId
1940
+ });
1941
+ if (!identity.ok) return failCustodyBuild({
1942
+ code: "unavailable",
1943
+ message: `Cannot derive a custody account on this chain: ${identity.error.code}`
1944
+ });
1945
+ const { rewardAccount, address, stateNftAssetId } = identity.value;
1946
+ const { isRegistered } = await readFromProvider("Custody reward account", () => chain.provider.getRewardAccountInfo({ rewardAccount: require_util.CardanoRewardAccount(rewardAccount) }, { chainId: chain.chainId }));
1947
+ if (!isRegistered) return {
1948
+ status: "unregistered",
1949
+ key: key.value,
1950
+ identity: identity.value
1951
+ };
1952
+ return (await readFromProvider("Custody account UTxOs", () => chain.provider.getUtxosAtAddress({ address: require_util.CardanoPaymentAddress(address) }, { chainId: chain.chainId }))).some(([, output]) => output.value.assets?.has(stateNftAssetId)) ? {
1953
+ status: "account",
1954
+ record: require_grant_status.createCustodyAccountRecord(identity.value)
1955
+ } : { status: "pre-registered" };
1956
+ };
1957
+ //#endregion
1958
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/custody-evaluation.ts
1959
+ /** Rebuilds allowed after the final transaction measures above its budgets. */
1960
+ const MAX_EVALUATION_ROUNDS = 3;
1961
+ /**
1962
+ * Prices every redeemer at what the provider measures on the draft, with a
1963
+ * margin, and at least at a floor an earlier round measured on final bytes.
1964
+ */
1965
+ const createProviderEvaluator = (chain, floors) => ({ evaluate: async (tx) => {
1966
+ const measured = await evaluateCustodyTransaction(_laceio_cardano_core.Serialization.Transaction.fromCore(tx), chain);
1967
+ return (tx.witness.redeemers ?? []).map(({ purpose, index }) => {
1968
+ const measurement = measured.find((entry) => entry.purpose === purpose && entry.index === index);
1969
+ if (!measurement) return unmeasured({
1970
+ purpose,
1971
+ index
1972
+ });
1973
+ return {
1974
+ purpose,
1975
+ index,
1976
+ budget: atLeast(withEvaluationMargin(measurement.budget), floors.get(redeemerKey({
1977
+ purpose,
1978
+ index
1979
+ })))
1980
+ };
1981
+ });
1982
+ } });
1983
+ /**
1984
+ * Builds a transaction whose budgets come from provider evaluation, then
1985
+ * evaluates the final bytes. Every redeemer must declare at least what the
1986
+ * final transaction measures, which is what the sponsor service checks
1987
+ * before it signs; a shortfall raises the budget and rebuilds.
1988
+ *
1989
+ * Balancing prices the fee at the plan's execution estimate, so the funds,
1990
+ * or the control output that pays, need to cover little more than the real
1991
+ * fee. When the scripts measure above the estimate by more than the
1992
+ * balanced fee covers, the plan is built once more, seeded at the
1993
+ * measurement plus a margin: a second selection round, which may add fund
1994
+ * UTxOs.
1995
+ *
1996
+ * @throws `MaxBalancedFeeExceeded` when `maxBalancedFee` is set and the
1997
+ * transaction needs a fee above it
1998
+ */
1999
+ const buildWithEvaluatedBudgets = async (params) => {
2000
+ let floors = /* @__PURE__ */ new Map();
2001
+ let funds = params.funds;
2002
+ let reseed;
2003
+ for (let round = 1;;) {
2004
+ let built;
2005
+ try {
2006
+ built = await assembleWithFunds({
2007
+ ...params,
2008
+ funds,
2009
+ reseed,
2010
+ evaluator: () => createProviderEvaluator(params.chain, floors)
2011
+ });
2012
+ } catch (error) {
2013
+ if (!(error instanceof ExecutionEstimateShortfall)) throw error;
2014
+ if (reseed) return failCustodyBuild({
2015
+ code: "script-evaluation-failed",
2016
+ message: "The scripts measure above the execution estimate after a reseed"
2017
+ });
2018
+ reseed = error.reseed;
2019
+ continue;
2020
+ }
2021
+ funds = built.funds;
2022
+ const underDeclared = findUnderDeclaredBudgets(built.transaction, await evaluateCustodyTransaction(built.transaction, params.chain));
2023
+ if (underDeclared.length === 0) return built.transaction;
2024
+ if (round >= MAX_EVALUATION_ROUNDS) return failCustodyBuild({
2025
+ code: "script-evaluation-failed",
2026
+ message: `Declared budgets stay below the measured ones after ${round} rounds`
2027
+ });
2028
+ round++;
2029
+ floors = new Map([...floors, ...underDeclared.map((entry) => [redeemerKey(entry), withEvaluationMargin(entry.budget)])]);
2030
+ }
2031
+ };
2032
+ //#endregion
2033
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/custody-scripts.ts
2034
+ const scriptHashOf = (script) => _laceio_cardano_core.Serialization.Script.fromCore(script).hash();
2035
+ /**
2036
+ * The catalogue pins each parked UTxO's address and lovelace with its
2037
+ * outref, so the builder prices its script without a chain lookup; the
2038
+ * provider's evaluation of every build resolves the outref on chain, so a
2039
+ * UTxO the chain no longer holds fails the build before anyone signs.
2040
+ */
2041
+ const custodyScriptWitnesses = (version, { chainId }, scripts) => {
2042
+ const parked = new Map(require_hex.custodyReferenceScriptUtxos(version, chainId.networkMagic).map((utxo) => [scriptHashOf(utxo[1].scriptReference), utxo]));
2043
+ const attached = [];
2044
+ const referenced = [];
2045
+ for (const script of scripts) {
2046
+ const reference = parked.get(scriptHashOf(script));
2047
+ if (reference) referenced.push(reference);
2048
+ else attached.push(script);
2049
+ }
2050
+ return {
2051
+ attached,
2052
+ referenced
2053
+ };
2054
+ };
2055
+ const LOGIC_RUN_REDEEMER = require_grant_status.encodeCustodyLogicRedeemer();
2056
+ /**
2057
+ * Reads the logic's reward account once per transaction, so every build
2058
+ * round of it draws the same amount. A credential that is not registered
2059
+ * fails the build as `logic-not-registered`: the ledger would refuse the
2060
+ * withdrawal only at submission, after the sponsor and the device signed.
2061
+ * Registration is read, not delegation: the logic delegates to no pool.
2062
+ */
2063
+ const readCustodyLogicRun = async (logic, { chainId, provider }) => {
2064
+ const rewardAccount = _laceio_cardano_core.Cardano.RewardAccount.fromCredential({
2065
+ type: _laceio_cardano_core.Cardano.CredentialType.ScriptHash,
2066
+ hash: logic.hash
2067
+ }, chainId.networkId);
2068
+ const { isRegistered, withdrawableAmount } = await readFromProvider("Custody logic reward account", () => provider.getRewardAccountInfo({ rewardAccount: require_util.CardanoRewardAccount(rewardAccount) }, { chainId }));
2069
+ if (!isRegistered) return failCustodyBuild({
2070
+ code: "logic-not-registered",
2071
+ rewardAccount
2072
+ });
2073
+ return {
2074
+ logic,
2075
+ withdrawal: {
2076
+ rewardAccount,
2077
+ quantity: require_bignum_vo.BigNumber.valueOf(withdrawableAmount),
2078
+ redeemer: LOGIC_RUN_REDEEMER
2079
+ }
2080
+ };
2081
+ };
2082
+ /**
2083
+ * The logic the account's control datum names. Lace operates no account
2084
+ * under a logic outside its contract version's catalogue, whose rules it
2085
+ * has not reviewed.
2086
+ */
2087
+ const accountLogicOf = ({ identity, state }) => {
2088
+ const logic = require_hex.findCustodyLogic(identity.contractVersion, state.logic);
2089
+ return logic.ok ? logic.value : failCustodyBuild({
2090
+ code: "account-not-live",
2091
+ status: "invalid"
2092
+ });
2093
+ };
2094
+ //#endregion
2095
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/custody-validation.ts
2096
+ const cborOf$1 = (data) => _laceio_cardano_core.Serialization.PlutusData.fromCore(data).toCbor();
2097
+ /**
2098
+ * What the sponsor service checks the transaction against, from the funding
2099
+ * it leased, the contract version of the account the transaction operates
2100
+ * and the chain.
2101
+ */
2102
+ const sponsorRuleContextOf = (funding, contractVersion, { chainId, clock, now, protocolParameters }) => ({
2103
+ mode: funding.mode === "fee" ? {
2104
+ kind: "fee",
2105
+ feeUtxo: funding.feeUtxo,
2106
+ maxSponsoredLovelace: funding.maxSponsoredLovelace
2107
+ } : { kind: "collateral" },
2108
+ sponsorAddress: funding.sponsorAddress,
2109
+ collateralUtxo: funding.collateralUtxo,
2110
+ contractVersion,
2111
+ networkMagic: chainId.networkMagic,
2112
+ latestValiditySlot: clock.slotAt(funding.validUntil),
2113
+ currentSlot: clock.slotAt(now),
2114
+ collateralPercentage: protocolParameters.collateralPercentage,
2115
+ costModels: protocolParameters.costModels
2116
+ });
2117
+ /**
2118
+ * The largest fee the sponsor's shared collateral UTxO covers, or undefined
2119
+ * without a collateral percentage. The ledger needs collateral of the fee
2120
+ * times the percentage, and the sponsor needs the rest of the UTxO back in
2121
+ * a return output that holds its minimum ADA. For the service's 5 ADA UTxO,
2122
+ * a percentage of 150 and a base return address on Preprod, that is
2123
+ * 2,686,833 lovelace, below the 3,333,333 the UTxO covers without a return.
2124
+ */
2125
+ const collateralFeeCeilingOf = ({ collateralUtxo: [, collateral], sponsorAddress }, { collateralPercentage, coinsPerUtxoByte }) => {
2126
+ if (collateralPercentage <= 0) return void 0;
2127
+ const returned = (0, _laceio_cardano_tx_construction.minAdaRequired)({
2128
+ address: sponsorAddress,
2129
+ value: { coins: collateral.value.coins }
2130
+ }, BigInt(coinsPerUtxoByte));
2131
+ const available = collateral.value.coins - returned;
2132
+ return available > 0n ? available * 100n / BigInt(collateralPercentage) : 0n;
2133
+ };
2134
+ const unexpected = (what) => failCustodyBuild({
2135
+ code: "build-failed",
2136
+ message: `The built transaction ${what}`
2137
+ });
2138
+ const isSameGrant = (a, b) => cborOf$1(require_outputs.encodeCustodyGrant(a)) === cborOf$1(require_outputs.encodeCustodyGrant(b));
2139
+ /** Checks the control output and the grant UTxOs the transaction writes. */
2140
+ const checkAccountOutputs = ({ controlOutput, issuedGrants, sweptGrants, grantSpend }, expectation) => {
2141
+ const { control } = expectation;
2142
+ if (!control) {
2143
+ if (controlOutput) unexpected("rewrites the control state");
2144
+ } else if (!controlOutput || cborOf$1(require_outputs.encodeCustodyAccountState(controlOutput.state)) !== cborOf$1(require_outputs.encodeCustodyAccountState(control.state))) unexpected("does not carry the intended account state");
2145
+ else if (controlOutput.lovelace !== control.lovelace) unexpected("does not lock the intended control lovelace");
2146
+ const planned = expectation.issuedGrants ?? [];
2147
+ 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");
2148
+ const plannedSwept = new Set(expectation.sweptSlots ?? []);
2149
+ if (!(sweptGrants.length === plannedSwept.size && sweptGrants.every(({ grant }) => plannedSwept.has(grant.slot)))) unexpected("does not sweep the intended grants");
2150
+ const plannedSpend = expectation.grantSpend;
2151
+ if (!(!plannedSpend || grantSpend?.grant.slot === plannedSpend.slot && isSameGrant(grantSpend.after, plannedSpend.after))) unexpected("does not spend the intended grant");
2152
+ };
2153
+ /** Checks the value leaving the account, its own tokens aside. */
2154
+ const checkOutflow = ({ netOutflow }, fee, expectation) => {
2155
+ if (!expectation.outflow) return;
2156
+ const { requested, withdrawal } = expectation.outflow;
2157
+ const assets = requested.assets ?? /* @__PURE__ */ new Map();
2158
+ const accountPolicy = expectation.record.accountValidatorHash;
2159
+ const leaving = [...netOutflow.assets].filter(([assetId]) => _laceio_cardano_core.Cardano.AssetId.getPolicyId(assetId) !== accountPolicy);
2160
+ 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");
2161
+ };
2162
+ /**
2163
+ * Checks the final, balanced transaction before anyone is asked to sign it:
2164
+ * the shared custody inspection (the hosted signer's consent rules), the
2165
+ * intended control output, issued grants, sweeps and grant spend, the
2166
+ * account's net
2167
+ * outflow, the signer, the fee the sponsor's collateral covers, the fee
2168
+ * sponsor service's policy and the protocol limits. Any failure refuses
2169
+ * the build.
2170
+ *
2171
+ * `resolvedInputs` resolves every input, reference input and collateral
2172
+ * input.
2173
+ */
2174
+ const finalizeCustodyBuild = ({ transaction, resolvedInputs }, chain, expectation) => {
2175
+ const inspection = require_grant_status.inspectCustodyTransaction(transaction, resolvedInputs, {
2176
+ chainId: chain.chainId,
2177
+ now: chain.now,
2178
+ slotToPosixMs: chain.clock.slotStart,
2179
+ accountRecord: expectation.record,
2180
+ allowGrantSpend: expectation.grantSpend !== void 0,
2181
+ ...expectation.deviceKeyHash && { deviceKeyHash: expectation.deviceKeyHash }
2182
+ });
2183
+ if (!inspection.ok) return failCustodyBuild({
2184
+ code: "inspection-refused",
2185
+ refusals: [...inspection.error.refusals]
2186
+ });
2187
+ const { accounts, requiredSigners, fee, validity } = inspection.value;
2188
+ const [account] = accounts;
2189
+ checkAccountOutputs(account, expectation);
2190
+ checkOutflow(account, fee, expectation);
2191
+ if (requiredSigners.length !== 1 || requiredSigners[0] !== expectation.signer) return unexpected("does not name exactly the intended signer");
2192
+ const { funding } = expectation;
2193
+ const maxFee = funding.mode === "collateral" ? collateralFeeCeilingOf(funding, chain.protocolParameters) : void 0;
2194
+ if (maxFee !== void 0 && fee > maxFee) return failCustodyBuild({
2195
+ code: "collateral-fee-ceiling",
2196
+ fee,
2197
+ maxFee
2198
+ });
2199
+ const sponsorViolations = findCustodySponsorRuleViolations(transaction, resolvedInputs, sponsorRuleContextOf(funding, account.contractVersion, chain));
2200
+ if (sponsorViolations.length > 0) return failCustodyBuild({
2201
+ code: "sponsor-rules",
2202
+ violations: sponsorViolations
2203
+ });
2204
+ try {
2205
+ assertWithinTransactionLimits(transaction, resolvedInputs, chain.protocolParameters);
2206
+ } catch (error) {
2207
+ if (!(error instanceof TransactionLimitsExceededError)) throw error;
2208
+ return failCustodyBuild({
2209
+ code: "transaction-limits",
2210
+ usage: error.usage,
2211
+ exceeded: error.exceeded
2212
+ });
2213
+ }
2214
+ return {
2215
+ transaction: transaction.toCbor(),
2216
+ bodyHash: transaction.getId(),
2217
+ resolvedInputs,
2218
+ accountRecord: expectation.record,
2219
+ fee,
2220
+ invalidHereafter: validity.invalidHereafter.slot
2221
+ };
2222
+ };
2223
+ //#endregion
2224
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/create-custody-account.ts
2225
+ /**
2226
+ * Lovelace a creation leaves unused below the sponsor's per-transaction
2227
+ * limit. It absorbs a fee that comes out slightly higher on the round that
2228
+ * settles the control lovelace, so that round still fits the limit.
2229
+ */
2230
+ const CUSTODY_CREATION_SPONSOR_MARGIN = 100000n;
2231
+ /**
2232
+ * Builds of one creation before the control lovelace settles. The first
2233
+ * build prices the largest fee its execution estimate allows, so it fits
2234
+ * any fee within that estimate; the second claims the headroom the real fee
2235
+ * leaves; the third covers a fee that then moved by more than the margin.
2236
+ */
2237
+ const MAX_HEADROOM_ROUNDS = 3;
2238
+ /**
2239
+ * The first creator index from `startIndex` whose stake credential is not
2240
+ * registered. A registered credential whose address holds the state NFT is
2241
+ * an existing account the caller should reopen; one without it was
2242
+ * registered by someone else first, which blocks creation there forever,
2243
+ * so the scan moves past it.
2244
+ */
2245
+ const findCreatorIndex = async ({ deviceAccountPublicKey, startIndex = 0 }, chain) => {
2246
+ for (let index = startIndex; index < startIndex + 100; index++) {
2247
+ const found = await readCustodyCreatorIndex({
2248
+ contractVersion: require_hex.CURRENT_CUSTODY_CONTRACT_VERSION,
2249
+ deviceAccountPublicKey,
2250
+ creatorIndex: index
2251
+ }, chain);
2252
+ if (found.status === "unregistered") return {
2253
+ index,
2254
+ key: found.key,
2255
+ identity: found.identity
2256
+ };
2257
+ if (found.status === "account") return failCustodyBuild({
2258
+ code: "account-exists",
2259
+ creatorIndex: index,
2260
+ record: found.record
2261
+ });
2262
+ }
2263
+ return failCustodyBuild({
2264
+ code: "index-exhausted",
2265
+ startIndex,
2266
+ scanned: 100
2267
+ });
2268
+ };
2269
+ const smaller = (a, b) => a < b ? a : b;
2270
+ /**
2271
+ * The control lovelace a creation aims for once its fee is known: what the
2272
+ * sponsor's limit leaves after the fee, the stake deposit and the margin,
2273
+ * within what the fee UTxO can fund, but never less than the initial
2274
+ * state's minimum.
2275
+ */
2276
+ const headroomControlLovelace = ({ maxSponsoredLovelace, deposit, fundable, minimum }, fee) => {
2277
+ const target = smaller(maxSponsoredLovelace - deposit - fee - CUSTODY_CREATION_SPONSOR_MARGIN, fundable);
2278
+ return target > minimum ? target : minimum;
2279
+ };
2280
+ /**
2281
+ * Builds the creation of a custody account in the sponsor's fee mode: the
2282
+ * leased fee UTxO pays the fee, the stake deposit and the control output,
2283
+ * and its change returns to the sponsor in one output. The state NFT is
2284
+ * minted with `CreateAccount`, the stake credential is registered in the
2285
+ * same transaction with a Conway registration certificate, which carries
2286
+ * the `Operate` witness the mint looks for, and the logic the state names
2287
+ * runs through its withdrawal, which validates the new state. The creator
2288
+ * key is the only device and the only required signer, and the state
2289
+ * starts under the current logic at generation 0 with no slot issued, none
2290
+ * revoked and none outstanding.
2291
+ *
2292
+ * The proxy and the logic are read from their parked UTxOs on a network
2293
+ * that has them, and attached elsewhere; the stake script, applied to the
2294
+ * creator key, is always attached. Whatever the logic run draws joins the
2295
+ * control output, so the sponsor pays the same.
2296
+ */
2297
+ const buildCustodyCreation = async (operation, funding, chain) => {
2298
+ const startIndex = operation.startIndex ?? 0;
2299
+ if (!Number.isSafeInteger(startIndex) || startIndex < 0 || startIndex + 100 > 2147483648) return failCustodyBuild({
2300
+ code: "invalid-operation",
2301
+ reason: "invalid-start-index",
2302
+ message: `Invalid creator start index ${startIndex}`
2303
+ });
2304
+ const contractVersion = require_hex.CURRENT_CUSTODY_CONTRACT_VERSION;
2305
+ const currentLogic = require_hex.findCustodyLogic(contractVersion, contractVersion.currentLogicHash);
2306
+ if (!currentLogic.ok) throw new Error(`The contract version has no logic ${contractVersion.currentLogicHash}`);
2307
+ const logicRun = await readCustodyLogicRun(currentLogic.value, chain);
2308
+ const { index, key, identity } = await findCreatorIndex(operation, chain);
2309
+ const { protocolParameters: parameters } = chain;
2310
+ const state = transitioned(createInitialCustodyAccountState(key.keyHash, logicRun.logic.hash));
2311
+ const witnesses = custodyScriptWitnesses(contractVersion, chain, [contractVersion.accountValidator, logicRun.logic.script]);
2312
+ const drawn = logicRun.withdrawal.quantity;
2313
+ const deposit = BigInt(parameters.stakeKeyDeposit);
2314
+ const invalidHereafter = validitySlotFor(funding.validUntil, chain);
2315
+ const executionEstimate = custodyExecutionEstimate({
2316
+ createAccount: 1,
2317
+ operate: 1,
2318
+ logic: "arrival"
2319
+ });
2320
+ const largestFee = preEvaluationFeeBound(parameters, estimateWithinLimit(executionEstimate, parameters.maxExecutionUnitsPerTransaction));
2321
+ const bounds = {
2322
+ maxSponsoredLovelace: funding.maxSponsoredLovelace,
2323
+ deposit,
2324
+ fundable: funding.feeUtxo[1].value.coins - changeReserveAt(funding.sponsorAddress, parameters.coinsPerUtxoByte) - deposit - largestFee,
2325
+ minimum: controlLovelaceFor({
2326
+ identity,
2327
+ state,
2328
+ floor: 0n
2329
+ }, parameters.coinsPerUtxoByte)
2330
+ };
2331
+ const buildWith = async (controlLovelace) => buildWithEvaluatedBudgets({
2332
+ chain,
2333
+ funds: {
2334
+ selected: [],
2335
+ remaining: []
2336
+ },
2337
+ plan: () => ({
2338
+ inputs: [{ utxo: funding.feeUtxo }],
2339
+ referenceInputs: witnesses.referenced,
2340
+ outputs: [controlOutputOf(identity, state, controlLovelace + drawn)],
2341
+ changeAddress: funding.sponsorAddress,
2342
+ scripts: [...witnesses.attached, identity.stakeScript],
2343
+ executionEstimate,
2344
+ requiredSigner: key.keyHash,
2345
+ invalidHereafter,
2346
+ collateral: {
2347
+ utxo: funding.collateralUtxo,
2348
+ returnAddress: funding.sponsorAddress
2349
+ },
2350
+ mint: {
2351
+ assets: new Map([[identity.stateNftAssetId, 1n]]),
2352
+ redeemer: require_grant_status.encodeCustodyMintRedeemer({ kind: "createAccount" })
2353
+ },
2354
+ registration: {
2355
+ stakeCredential: scriptCredentialOf(identity.stakeScriptHash),
2356
+ deposit,
2357
+ redeemer: require_grant_status.encodeCustodyStakeRedeemer()
2358
+ },
2359
+ withdrawals: [logicRun.withdrawal]
2360
+ })
2361
+ });
2362
+ let controlLovelace = headroomControlLovelace(bounds, largestFee);
2363
+ let transaction;
2364
+ for (let round = 1;; round++) {
2365
+ transaction = await buildWith(controlLovelace);
2366
+ const fee = transaction.body().fee();
2367
+ const sponsoredLovelace = fee + deposit + controlLovelace;
2368
+ const isWithinLimit = sponsoredLovelace <= funding.maxSponsoredLovelace;
2369
+ const settled = headroomControlLovelace(bounds, fee);
2370
+ const isLastRound = round >= MAX_HEADROOM_ROUNDS || settled === controlLovelace;
2371
+ if (isWithinLimit && (settled < controlLovelace || isLastRound)) break;
2372
+ if (!isWithinLimit && isLastRound) return failCustodyBuild({
2373
+ code: "sponsor-limit",
2374
+ sponsoredLovelace,
2375
+ maxSponsoredLovelace: funding.maxSponsoredLovelace
2376
+ });
2377
+ controlLovelace = settled;
2378
+ }
2379
+ const record = require_grant_status.createCustodyAccountRecord(identity);
2380
+ return {
2381
+ ...finalizeCustodyBuild({
2382
+ transaction,
2383
+ resolvedInputs: resolvedInputsOf(transaction, [
2384
+ funding.feeUtxo,
2385
+ ...witnesses.referenced,
2386
+ funding.collateralUtxo
2387
+ ])
2388
+ }, chain, {
2389
+ record,
2390
+ control: {
2391
+ state,
2392
+ lovelace: controlLovelace + drawn
2393
+ },
2394
+ signer: key.keyHash,
2395
+ deviceKeyHash: key.keyHash,
2396
+ funding
2397
+ }),
2398
+ creation: {
2399
+ creatorIndex: index,
2400
+ identity: {
2401
+ chainId: chain.chainId,
2402
+ accountValidatorHash: contractVersion.accountValidatorHash,
2403
+ creatorKeyHash: identity.creatorKeyHash,
2404
+ stakeScriptHash: identity.stakeScriptHash,
2405
+ address: identity.address,
2406
+ rewardAccount: identity.rewardAccount
2407
+ }
2408
+ }
2409
+ };
2410
+ };
2411
+ //#endregion
2412
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/custody-account.ts
2413
+ /** Every UTxO the classification of a live account holds. */
2414
+ const utxosOf = (state) => [
2415
+ state.controlUtxo,
2416
+ ...state.funds,
2417
+ ...state.reserves.own,
2418
+ ...state.reserves.foreign,
2419
+ ...state.grants.map(({ utxo }) => utxo),
2420
+ ...state.stranded,
2421
+ ...state.anomalies.map(({ utxo }) => utxo)
2422
+ ];
2423
+ /**
2424
+ * Resolves the account an operation runs on. The synced view must show it
2425
+ * live with a confirmed control UTxO, and the chain, read again now, must
2426
+ * hold that same control UTxO. Funds, reserves and grants come from that
2427
+ * fresh read, so a UTxO already spent on chain or still unconfirmed is never
2428
+ * selected; funds and reserves an unsettled transaction of this wallet
2429
+ * spends are left out too, so concurrent builds pick disjoint ones.
2430
+ *
2431
+ * Owner operations are never chained: while the synced control UTxO is the
2432
+ * output of a submitted transaction that has not settled, the build is
2433
+ * refused.
2434
+ */
2435
+ const resolveCustodyAccount = async (accountId, snapshot, chain) => {
2436
+ const { account, accountState, settledUtxos } = snapshot;
2437
+ if (!account || account.blockchainSpecific.chainId.networkMagic !== chain.chainId.networkMagic) return failCustodyBuild({ code: "unknown-account" });
2438
+ const identity = require_identity.verifyCardanoCustodyAccountIdentity(account.blockchainSpecific);
2439
+ if (!identity.ok) return failCustodyBuild({ code: "unknown-account" });
2440
+ if (accountState?.status !== "live") return failCustodyBuild({
2441
+ code: "account-not-live",
2442
+ status: accountState?.status ?? "unsynced"
2443
+ });
2444
+ const controlKey = require_util.utxoKey(accountState.controlUtxo);
2445
+ if (!settledUtxos.some((utxo) => require_util.utxoKey(utxo) === controlKey)) return failCustodyBuild({ code: "operation-in-progress" });
2446
+ const onChain = require_grant_status.classifyCardanoCustodyAccount({
2447
+ account: account.blockchainSpecific,
2448
+ utxos: await readFromProvider("Custody account UTxOs", () => chain.provider.getUtxosAtAddress({
2449
+ address: require_util.CardanoPaymentAddress(identity.value.address),
2450
+ withReferenceScripts: true
2451
+ }, { chainId: chain.chainId })),
2452
+ withdrawableRewards: 0n,
2453
+ protocolParameters: chain.protocolParameters
2454
+ });
2455
+ if (onChain.status !== "live" || require_util.utxoKey(onChain.controlUtxo) !== controlKey) return failCustodyBuild({ code: "stale-control" });
2456
+ const inFlight = new Set(utxosOf(accountState).map(require_util.utxoKey));
2457
+ const spentInFlight = new Set(settledUtxos.map(require_util.utxoKey).filter((key) => !inFlight.has(key)));
2458
+ const isUnspent = (utxo) => !spentInFlight.has(require_util.utxoKey(utxo));
2459
+ return {
2460
+ accountId,
2461
+ identity: identity.value,
2462
+ record: require_grant_status.createCustodyAccountRecord(identity.value),
2463
+ device: account.blockchainSpecific.device,
2464
+ control: onChain.controlUtxo,
2465
+ state: onChain.state,
2466
+ funds: onChain.funds.filter(isUnspent),
2467
+ reserves: onChain.reserves.own.filter(isUnspent),
2468
+ grants: onChain.grants,
2469
+ spentInFlight
2470
+ };
2471
+ };
2472
+ /**
2473
+ * The device key that signs an owner operation: the bound device, listed in
2474
+ * the current state. An observer holds no device and no owner authority.
2475
+ */
2476
+ const requireListedDevice = ({ device, state }) => {
2477
+ if (!device) return failCustodyBuild({ code: "observer-account" });
2478
+ const keyHash = device.keyHash.toLowerCase();
2479
+ return state.devices.some((listed) => listed.toLowerCase() === keyHash) ? device.keyHash : failCustodyBuild({
2480
+ code: "device-not-listed",
2481
+ keyHash: device.keyHash
2482
+ });
2483
+ };
2484
+ //#endregion
2485
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/custody-requests.ts
2486
+ const outputProblem = (output, identity, coinsPerUtxoByte) => {
2487
+ const address = _laceio_cardano_core.Cardano.Address.fromString(output.address);
2488
+ if (address?.getNetworkId() !== identity.chainId.networkId) return "is not an address on the account network";
2489
+ if (address.getType() === _laceio_cardano_core.Cardano.AddressType.Byron) return "is a Byron address, which a Plutus transaction cannot pay";
2490
+ if (address.toBech32() === identity.address) return "pays the account itself";
2491
+ const accountPolicy = identity.contractVersion.accountValidatorHash;
2492
+ if ([...output.value.assets?.keys() ?? []].some((assetId) => _laceio_cardano_core.Cardano.AssetId.getPolicyId(assetId) === accountPolicy)) return "carries a custody account token";
2493
+ return output.value.coins < (0, _laceio_cardano_tx_construction.minAdaRequired)(output, BigInt(coinsPerUtxoByte)) ? "holds less than its minimum ADA" : void 0;
2494
+ };
2495
+ /**
2496
+ * Checks the outputs an owner or a grantee asks the account to pay. Each
2497
+ * goes to a Shelley address on the account's network other than the
2498
+ * account itself, carries no token of a custody account and holds its
2499
+ * minimum ADA: the build never tops a requested amount up from account
2500
+ * funds.
2501
+ */
2502
+ const checkRequestedOutputs = (outputs, identity, coinsPerUtxoByte) => {
2503
+ for (const [outputIndex, output] of outputs.entries()) {
2504
+ const problem = outputProblem(output, identity, coinsPerUtxoByte);
2505
+ if (problem) failCustodyBuild({
2506
+ code: "invalid-operation",
2507
+ reason: "invalid-output",
2508
+ message: `Output ${outputIndex} ${problem}`
2509
+ });
2510
+ }
2511
+ };
2512
+ const invalidMetadata = (message) => failCustodyBuild({
2513
+ code: "invalid-operation",
2514
+ reason: "invalid-metadata",
2515
+ message
2516
+ });
2517
+ /** The largest metadata label the ledger encodes: a 64-bit unsigned integer. */
2518
+ const MAX_METADATA_LABEL = 2n ** 64n - 1n;
2519
+ /**
2520
+ * The metadata an owner asks the transaction to carry: a map of labels to
2521
+ * metadata the ledger accepts, which its serialization checks: text and
2522
+ * bytes hold at most 64 bytes each. An empty map carries nothing.
2523
+ */
2524
+ const checkRequestedMetadata = (metadata) => {
2525
+ if (metadata === void 0) return void 0;
2526
+ if (!(metadata instanceof Map)) return invalidMetadata("The metadata must be a map of labels to metadata");
2527
+ if (metadata.size === 0) return void 0;
2528
+ 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`);
2529
+ try {
2530
+ _laceio_cardano_core.Serialization.AuxiliaryData.fromCore({ blob: metadata });
2531
+ } catch (error) {
2532
+ return invalidMetadata(`The metadata cannot be encoded: ${error instanceof Error ? error.message : String(error)}`);
2533
+ }
2534
+ return metadata;
2535
+ };
2536
+ const invalidScope = (message) => failCustodyBuild({
2537
+ code: "invalid-operation",
2538
+ reason: "grant-scope",
2539
+ message
2540
+ });
2541
+ /**
2542
+ * The grant a scope request asks to issue in `slot`. The request goes
2543
+ * through the library's strict requested-scope parser, the one the signer
2544
+ * applies to `requestedScopes`, so Lace refuses a scope the signer would
2545
+ * refuse as malformed; its recipients, which must be on the account's
2546
+ * network, become Plutus addresses.
2547
+ */
2548
+ const grantRequestOf = ({ scope: request, slot }, { chainId }, now) => {
2549
+ let serialized;
2550
+ try {
2551
+ serialized = require_grant_status.serializeCustodyRequestedGrantScope({
2552
+ ...request,
2553
+ slot
2554
+ });
2555
+ } catch {
2556
+ return invalidScope("scope: not a grant scope");
2557
+ }
2558
+ const parsed = require_grant_status.parseCustodyRequestedGrantScope(serialized);
2559
+ if (!parsed.ok) return invalidScope([parsed.error.field, parsed.error.reason].filter(Boolean).join(": "));
2560
+ const { grantee, recipients, ...scope } = parsed.value;
2561
+ return {
2562
+ grantee,
2563
+ now,
2564
+ scope: {
2565
+ asset: scope.asset,
2566
+ perCallCap: scope.perCallCap,
2567
+ cap: scope.cap,
2568
+ lovelacePerCallCap: scope.lovelacePerCallCap,
2569
+ lovelaceCap: scope.lovelaceCap,
2570
+ expiresAt: scope.expiresAt,
2571
+ recipients: recipients.map((text) => {
2572
+ const address = _laceio_cardano_core.Cardano.Address.fromBech32(text);
2573
+ const recipient = require_outputs.custodyRecipientFromAddress(address);
2574
+ return recipient.ok && address.getNetworkId() === chainId.networkId ? recipient.value : invalidScope(`recipients: ${text} is not on the account network`);
2575
+ })
2576
+ }
2577
+ };
2578
+ };
2579
+ /** A bech32 stake pool id, refused as an invalid operation when malformed. */
2580
+ const poolIdOf = (poolId) => {
2581
+ try {
2582
+ return _laceio_cardano_core.Cardano.PoolId(poolId);
2583
+ } catch {
2584
+ return failCustodyBuild({
2585
+ code: "invalid-operation",
2586
+ reason: "invalid-pool-id",
2587
+ message: `Invalid stake pool id ${poolId}`
2588
+ });
2589
+ }
2590
+ };
2591
+ //#endregion
2592
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/custody-grant-spend.ts
2593
+ /**
2594
+ * The most fund UTxOs one grant spend spends, upstream's `MAX_FUND_INPUTS`.
2595
+ * Every input adds a proxy run that decodes the whole transaction, and the
2596
+ * logic checks each one: a spend over twelve deposits measured 4.28M
2597
+ * memory units on upstream's devnet, a quarter of Preprod's limit.
2598
+ */
2599
+ const MAX_GRANT_SPEND_FUND_INPUTS = 12;
2600
+ /**
2601
+ * What the second build adds to the first build's fee when it lowers the
2602
+ * caps: room for a fee that moves when the caps' encoding or the measured
2603
+ * budgets change between the two builds.
2604
+ */
2605
+ const GRANT_CAP_FEE_MARGIN = 10000n;
2606
+ const SPEND_WITH_GRANT_REDEEMER = require_grant_status.encodeCustodyAccountRedeemer({ kind: "spendWithGrant" });
2607
+ const FUND_REDEEMER$1 = require_grant_status.encodeCustodyAccountRedeemer({ kind: "fund" });
2608
+ const cborOf = (data) => _laceio_cardano_core.Serialization.PlutusData.fromCore(data).toCbor();
2609
+ const FUND_REDEEMER_CBOR = cborOf(FUND_REDEEMER$1);
2610
+ const SPEND_WITH_GRANT_REDEEMER_CBOR = cborOf(SPEND_WITH_GRANT_REDEEMER);
2611
+ const deltaOf = (value, fee = 0n) => ({
2612
+ lovelace: value.coins + fee,
2613
+ assets: new Map([...value.assets].map(([assetId, quantity]) => [assetId.toLowerCase(), quantity]))
2614
+ });
2615
+ /**
2616
+ * Metadata is an owner's to attach: the operation type leaves it out, and a
2617
+ * caller that names it anyway is refused rather than built without it.
2618
+ */
2619
+ const refuseMetadata = (operation) => {
2620
+ if (operation.metadata !== void 0) failCustodyBuild({
2621
+ code: "invalid-operation",
2622
+ reason: "invalid-metadata",
2623
+ message: "A grant spend carries no metadata"
2624
+ });
2625
+ };
2626
+ /** The fee bound the operation names, or the default when it names none. */
2627
+ const feeBoundOf = ({ feeBound }) => {
2628
+ if (feeBound === void 0) return require_grant_status.CARDANO_CUSTODY_DEFAULT_GRANT_FEE_BOUND;
2629
+ return typeof feeBound === "bigint" && feeBound > 0n ? feeBound : failCustodyBuild({
2630
+ code: "invalid-operation",
2631
+ reason: "invalid-fee-bound",
2632
+ message: "The fee bound must be a positive number of lovelace"
2633
+ });
2634
+ };
2635
+ /**
2636
+ * The grant UTxO a grant spend spends: the grantee's grant in the named
2637
+ * slot, or its single current, unexpired grant. The grant must be current
2638
+ * against the control state, and no unsettled transaction of this wallet
2639
+ * may already spend its UTxO.
2640
+ */
2641
+ const selectGrant = ({ grantee, slot }, { grants, state, spentInFlight }, { now }) => {
2642
+ const granteeHex = grantee.toLowerCase();
2643
+ const held = grants.filter(({ grant }) => grant.grantee.toLowerCase() === granteeHex);
2644
+ const livenessOf = ({ grant }) => require_grant_status.custodyGrantLiveness(grant, state, now);
2645
+ let selected;
2646
+ if (slot === void 0) {
2647
+ const current = held.filter((found) => livenessOf(found) === "current");
2648
+ if (current.length > 1) return failCustodyBuild({
2649
+ code: "grant-spend",
2650
+ reason: "ambiguous-grant",
2651
+ grantee,
2652
+ slots: current.map(({ grant }) => grant.slot)
2653
+ });
2654
+ selected = current[0] ?? held.at(-1);
2655
+ } else selected = held.find(({ grant }) => grant.slot === slot);
2656
+ if (!selected) return failCustodyBuild({
2657
+ code: "grant-spend",
2658
+ reason: "unknown-grant",
2659
+ grantee,
2660
+ ...slot !== void 0 && { slot }
2661
+ });
2662
+ const liveness = livenessOf(selected);
2663
+ if (liveness !== "current") return failCustodyBuild({
2664
+ code: "grant-spend",
2665
+ reason: "dead-grant",
2666
+ slot: selected.grant.slot,
2667
+ liveness
2668
+ });
2669
+ return spentInFlight.has(require_util.outpointKey(selected.utxo[0])) ? failCustodyBuild({ code: "operation-in-progress" }) : selected;
2670
+ };
2671
+ /** Refuses an output the grant's recipient list does not name, on the account's network. */
2672
+ const checkRecipients = (grant, outputs, networkId) => {
2673
+ const { recipients } = grant.scope;
2674
+ if (recipients.length === 0) return;
2675
+ const allowed = new Set(recipients.flatMap((recipient) => {
2676
+ const address = require_outputs.custodyRecipientToAddress(recipient, networkId);
2677
+ return address.ok ? [address.value.toBech32()] : [];
2678
+ }));
2679
+ for (const [outputIndex, output] of outputs.entries()) {
2680
+ const address = _laceio_cardano_core.Cardano.Address.fromString(output.address).toBech32();
2681
+ if (!allowed.has(address)) failCustodyBuild({
2682
+ code: "grant-spend",
2683
+ reason: "recipient-not-allowed",
2684
+ outputIndex
2685
+ });
2686
+ }
2687
+ };
2688
+ /**
2689
+ * Refuses an outflow the grant's scope does not allow. `fee`, when known,
2690
+ * is the part of the lovelace outflow that pays the transaction fee; each
2691
+ * refusal reports it, so a caller can size a grant for the payment and the
2692
+ * fee.
2693
+ */
2694
+ const refuseScope = (grant, leaving, fee) => {
2695
+ const violations = require_grant_status.findCustodyScopeViolations(grant.scope, leaving, fee);
2696
+ if (violations.length > 0) failCustodyBuild({
2697
+ code: "grant-spend",
2698
+ reason: "scope",
2699
+ violations
2700
+ });
2701
+ };
2702
+ /**
2703
+ * The upper validity bound of a grant spend: the earlier of the funding
2704
+ * window and the slot holding the grant's expiry, whose start the contract
2705
+ * compares with the expiry. A grant that expires within the current slot
2706
+ * leaves no such slot.
2707
+ */
2708
+ const grantSpendValiditySlot = (grant, funding, chain) => {
2709
+ const windowSlot = validitySlotFor(funding.validUntil, chain);
2710
+ const { expiresAt } = grant.scope;
2711
+ if (expiresAt >= BigInt(chain.clock.slotStart(windowSlot))) return windowSlot;
2712
+ const expirySlot = chain.clock.slotAt(Number(expiresAt));
2713
+ return expirySlot > chain.clock.slotAt(chain.now) ? expirySlot : failCustodyBuild({
2714
+ code: "grant-spend",
2715
+ reason: "dead-grant",
2716
+ slot: grant.slot,
2717
+ liveness: "expired"
2718
+ });
2719
+ };
2720
+ /**
2721
+ * Refuses a spend the fund UTxOs cannot pay however they are combined:
2722
+ * they hold less of a requested asset, or less lovelace, with whatever the
2723
+ * logic run draws, than the outputs and the least fee. It runs before the
2724
+ * fund input cap, because consolidation cannot help an account that is
2725
+ * short of funds.
2726
+ */
2727
+ const refuseShortFunds = (candidates, requested, { leastFee, drawn }) => {
2728
+ const available = totalValueOf(candidates.map(([, output]) => output));
2729
+ if (available.coins + drawn < requested.coins + leastFee || [...requested.assets].some(([assetId, quantity]) => (available.assets.get(assetId) ?? 0n) < quantity)) failCustodyBuild({
2730
+ code: "insufficient-funds",
2731
+ message: "The fund UTxOs hold less than the requested outputs and the least fee"
2732
+ });
2733
+ };
2734
+ const refuseFundInputCount = (funds) => {
2735
+ if (funds.length > MAX_GRANT_SPEND_FUND_INPUTS) failCustodyBuild({
2736
+ code: "grant-spend",
2737
+ reason: "fund-input-cap",
2738
+ maxFundInputs: MAX_GRANT_SPEND_FUND_INPUTS,
2739
+ requiredFundInputs: funds.length
2740
+ });
2741
+ };
2742
+ /**
2743
+ * Refuses a spend whose budgets exceed the per-transaction execution limit,
2744
+ * as a fund input count above what one spend fits: the grant's run is
2745
+ * fixed, so only fewer, larger fund UTxOs help. The count that fits follows
2746
+ * from the grant's and the logic's budgets and the dearest fund budget; the
2747
+ * logic's budget already counts every fund it checked, so the count errs
2748
+ * low.
2749
+ */
2750
+ const refuseOverBudget = (redeemers, { maxExecutionUnitsPerTransaction: limit }) => {
2751
+ const total = redeemers.reduce((sum, { executionUnits }) => ({
2752
+ memory: sum.memory + executionUnits.memory,
2753
+ steps: sum.steps + executionUnits.steps
2754
+ }), {
2755
+ memory: 0,
2756
+ steps: 0
2757
+ });
2758
+ if (total.memory <= limit.memory && total.steps <= limit.steps) return;
2759
+ const fixed = redeemers.filter(({ purpose, data }) => purpose === _laceio_cardano_core.Cardano.RedeemerPurpose.withdrawal || cborOf(data) === SPEND_WITH_GRANT_REDEEMER_CBOR).reduce((sum, { executionUnits }) => ({
2760
+ memory: sum.memory + executionUnits.memory,
2761
+ steps: sum.steps + executionUnits.steps
2762
+ }), {
2763
+ memory: 0,
2764
+ steps: 0
2765
+ });
2766
+ const fundBudgets = redeemers.filter(({ data }) => cborOf(data) === FUND_REDEEMER_CBOR).map(({ executionUnits }) => executionUnits);
2767
+ const fundMemory = Math.max(1, ...fundBudgets.map(({ memory }) => memory));
2768
+ const fundSteps = Math.max(1, ...fundBudgets.map(({ steps }) => steps));
2769
+ const fitting = Math.floor(Math.min((limit.memory - fixed.memory) / fundMemory, (limit.steps - fixed.steps) / fundSteps));
2770
+ failCustodyBuild({
2771
+ code: "grant-spend",
2772
+ reason: "fund-input-cap",
2773
+ maxFundInputs: Math.max(0, Math.min(MAX_GRANT_SPEND_FUND_INPUTS, fitting)),
2774
+ requiredFundInputs: fundBudgets.length
2775
+ });
2776
+ };
2777
+ /**
2778
+ * The grant the recreated grant UTxO carries: the caps `grant_after_spend`
2779
+ * allows for the requested outputs and `fee`, and no lower than zero. The
2780
+ * logic accepts any cap from zero to the value for the fee of the
2781
+ * transaction it checks, so a `fee` no lower than that one writes valid
2782
+ * caps.
2783
+ */
2784
+ const grantAfter = (grant, requested, fee) => {
2785
+ const { scope } = require_grant_status.custodyGrantAfterSpend(grant, deltaOf(requested, fee));
2786
+ return {
2787
+ ...grant,
2788
+ scope: {
2789
+ ...scope,
2790
+ cap: scope.cap > 0n ? scope.cap : 0n,
2791
+ lovelaceCap: scope.lovelaceCap > 0n ? scope.lovelaceCap : 0n
2792
+ }
2793
+ };
2794
+ };
2795
+ /**
2796
+ * The most fee the outflow carries before it breaks a lovelace cap of the
2797
+ * scope: the lower of a lovelace grant's caps, or of a token grant's
2798
+ * lovelace caps, less the requested lovelace.
2799
+ */
2800
+ const feeRoomOf = ({ scope }, requested) => {
2801
+ const [perCallCap, cap] = require_outputs.isCustodyLovelaceAsset(scope.asset) ? [scope.perCallCap, scope.cap] : [scope.lovelacePerCallCap, scope.lovelaceCap];
2802
+ return (perCallCap < cap ? perCallCap : cap) - requested.coins;
2803
+ };
2804
+ /**
2805
+ * Builds a grant spend for the grantee to sign. It references the
2806
+ * account's control UTxO, spends the grant UTxO with `SpendWithGrant` and
2807
+ * plain fund UTxOs with `Fund`, largest first, never a reserve, one
2808
+ * carrying a reference script, or one in `excludedInputs`, and runs the
2809
+ * logic the control names through its withdrawal. The grant UTxO is
2810
+ * recreated with the same value and the caps lowered by the outflow, the
2811
+ * requested outputs are paid, and the change, with whatever the logic run
2812
+ * draws, returns to the account as a plain output. The account pays the
2813
+ * fee, which counts against the grant's caps; the sponsor contributes only
2814
+ * its collateral. The proxy and the logic are read from their parked UTxOs
2815
+ * on a network that has them, and attached elsewhere.
2816
+ *
2817
+ * The caps go into the datum before the fee is known, and the logic counts
2818
+ * the fee in the outflow it checks against them, both on the final
2819
+ * transaction and on the balanced draft the provider evaluates. So every
2820
+ * draft is balanced at no more than the fee the caps were lowered by, nor
2821
+ * the fee that keeps the outflow within the caps, and two builds run with
2822
+ * evaluated budgets. The first lowers the caps by the fee bound, and a fee
2823
+ * above the bound or the caps is refused. The second lowers them by the
2824
+ * first fee and a small margin, and stands when its fee fits that; the
2825
+ * first build stands whenever the second cannot be built.
2826
+ */
2827
+ const buildCustodyGrantSpend = async ({ operation, funding }, context, chain) => {
2828
+ const { identity, control } = context;
2829
+ const { protocolParameters: parameters } = chain;
2830
+ refuseMetadata(operation);
2831
+ const feeBound = feeBoundOf(operation);
2832
+ const spent = selectGrant(operation, context, chain);
2833
+ const { grant } = spent;
2834
+ checkRequestedOutputs(operation.outputs, identity, parameters.coinsPerUtxoByte);
2835
+ checkRecipients(grant, operation.outputs, identity.chainId.networkId);
2836
+ const requested = totalValueOf(operation.outputs);
2837
+ refuseScope(grant, deltaOf(requested));
2838
+ const invalidHereafter = grantSpendValiditySlot(grant, funding, chain);
2839
+ const grantee = grant.grantee;
2840
+ const logicRun = await readCustodyLogicRun(accountLogicOf(context), chain);
2841
+ const drawn = logicRun.withdrawal.quantity;
2842
+ const { contractVersion } = identity;
2843
+ const witnesses = custodyScriptWitnesses(contractVersion, chain, [contractVersion.accountValidator, logicRun.logic.script]);
2844
+ const excluded = new Set((operation.excludedInputs ?? []).map(require_util.outpointKey));
2845
+ const candidates = context.funds.filter(([input, output]) => output.scriptReference === void 0 && !excluded.has(require_util.outpointKey(input)));
2846
+ const planOf = (after) => (funds) => ({
2847
+ inputs: [{
2848
+ utxo: spent.utxo,
2849
+ redeemer: SPEND_WITH_GRANT_REDEEMER
2850
+ }, ...funds.map((utxo) => ({
2851
+ utxo,
2852
+ redeemer: FUND_REDEEMER$1
2853
+ }))],
2854
+ referenceInputs: [control, ...witnesses.referenced],
2855
+ outputs: [grantOutputOf(identity, after, spent.utxo[1].value.coins), ...operation.outputs],
2856
+ changeAddress: identity.address,
2857
+ scripts: witnesses.attached,
2858
+ executionEstimate: custodyExecutionEstimate({
2859
+ spendWithGrant: 1,
2860
+ fund: funds.length,
2861
+ logic: "agent"
2862
+ }),
2863
+ requiredSigner: grantee,
2864
+ invalidHereafter,
2865
+ collateral: {
2866
+ utxo: funding.collateralUtxo,
2867
+ returnAddress: funding.sponsorAddress
2868
+ },
2869
+ withdrawals: [logicRun.withdrawal]
2870
+ });
2871
+ const needed = requested.coins + plannedFeeOf(planOf(grant)([]), parameters) + changeReserveAt(identity.address, parameters.coinsPerUtxoByte) - drawn;
2872
+ const selection = selectFunds(candidates, {
2873
+ coins: needed > 0n ? needed : 0n,
2874
+ assets: requested.assets
2875
+ });
2876
+ if (!selection.covers) refuseShortFunds(candidates, requested, {
2877
+ leastFee: leastFeeOf(planOf(grant)([]), parameters),
2878
+ drawn
2879
+ });
2880
+ const feeRoom = feeRoomOf(grant, requested);
2881
+ /** A build whose caps are lowered by `lowering` and whose drafts fit them. */
2882
+ const build = async (lowering, funds) => {
2883
+ const after = grantAfter(grant, requested, lowering);
2884
+ const transaction = await buildWithEvaluatedBudgets({
2885
+ chain,
2886
+ funds,
2887
+ checkFunds: refuseFundInputCount,
2888
+ plan: planOf(after),
2889
+ maxBalancedFee: lowering < feeRoom ? lowering : feeRoom
2890
+ });
2891
+ return {
2892
+ transaction,
2893
+ after,
2894
+ fee: transaction.body().fee()
2895
+ };
2896
+ };
2897
+ /** Refuses a fee above the bound, or one that takes the outflow over a cap. */
2898
+ const refuseFee = (fee) => fee > feeBound ? failCustodyBuild({
2899
+ code: "grant-spend",
2900
+ reason: "fee-above-bound",
2901
+ fee,
2902
+ feeBound
2903
+ }) : failCustodyBuild({
2904
+ code: "grant-spend",
2905
+ reason: "scope",
2906
+ violations: require_grant_status.findCustodyScopeViolations(grant.scope, deltaOf(requested, fee), fee)
2907
+ });
2908
+ const first = await build(feeBound, selection).catch((error) => {
2909
+ if (!(error instanceof MaxBalancedFeeExceeded)) throw error;
2910
+ refuseOverBudget(error.evaluatedRedeemers, parameters);
2911
+ return refuseFee(error.fee);
2912
+ });
2913
+ refuseOverBudget(first.transaction.witnessSet().redeemers()?.toCore() ?? [], parameters);
2914
+ const firstFunds = resolvedInputsOf(first.transaction, candidates);
2915
+ let chosen = first;
2916
+ const tightFee = first.fee + GRANT_CAP_FEE_MARGIN;
2917
+ const { scope: tight } = grantAfter(grant, requested, tightFee);
2918
+ if (tight.cap > first.after.scope.cap || tight.lovelaceCap > first.after.scope.lovelaceCap) {
2919
+ const used = new Set(firstFunds.map(([input]) => require_util.outpointKey(input)));
2920
+ try {
2921
+ chosen = await build(tightFee, {
2922
+ selected: firstFunds,
2923
+ remaining: candidates.filter(([input]) => !used.has(require_util.outpointKey(input)))
2924
+ });
2925
+ } catch (error) {
2926
+ if (!(error instanceof MaxBalancedFeeExceeded) && !(error instanceof CustodyBuildFailure)) throw error;
2927
+ }
2928
+ }
2929
+ const { transaction, after } = chosen;
2930
+ return finalizeCustodyBuild({
2931
+ transaction,
2932
+ resolvedInputs: resolvedInputsOf(transaction, [
2933
+ spent.utxo,
2934
+ control,
2935
+ ...witnesses.referenced,
2936
+ ...candidates,
2937
+ funding.collateralUtxo
2938
+ ])
2939
+ }, chain, {
2940
+ record: context.record,
2941
+ grantSpend: {
2942
+ slot: grant.slot,
2943
+ after
2944
+ },
2945
+ signer: grantee,
2946
+ outflow: {
2947
+ requested,
2948
+ withdrawal: drawn
2949
+ },
2950
+ funding
2951
+ });
2952
+ };
2953
+ //#endregion
2954
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/custody-owner-operations.ts
2955
+ /**
2956
+ * The control output headroom, its lovelace above the state's minimum, that
2957
+ * an issue or a replace tops up to. The headroom is the owner's fee reserve:
2958
+ * an owner operation the plain funds cannot pay takes its fee from it, so an
2959
+ * owner can still revoke a grant after its agent drains the plain funds.
2960
+ * It pays two revokes. Where the proxy and the logic are attached, as on
2961
+ * Mainnet, a revoke carries about 12.7 KB, 11,871 bytes of them the two
2962
+ * scripts, at 44 lovelace per byte. At Preprod's prices and the 1.6M to 1.8M
2963
+ * memory units upstream's devnet measured over the largest state, the
2964
+ * control pays a fee of about 0.84 ADA. Before evaluation balancing prices
2965
+ * a revoke at its execution estimate, about 0.99 ADA, which the draft the
2966
+ * provider evaluates must find in the headroom. The first revoke's fee, the
2967
+ * second's draft, the state's growth and the growth margin come to about
2968
+ * 2.04 ADA. On Preprod, where the scripts are referenced at 15 lovelace per
2969
+ * byte, a revoke costs about 0.5 ADA and its draft about 0.64 ADA.
2970
+ */
2971
+ const CUSTODY_OWNER_FEE_RESERVE_TARGET = 2100000n;
2972
+ /**
2973
+ * The control datum bytes the control output keeps lovelace for beyond its
2974
+ * minimum after paying a fee. The largest growth one owner operation makes
2975
+ * is a device key: 30 bytes, its 28 bytes and their header. A revoked slot
2976
+ * adds at most 6, and a counter crossing a CBOR width at most 2. The next
2977
+ * operation's growth is then covered, whoever pays its fee.
2978
+ */
2979
+ const CUSTODY_CONTROL_GROWTH_MARGIN_BYTES = 32;
2980
+ const DEVICE_REDEEMER = require_grant_status.encodeCustodyAccountRedeemer({ kind: "device" });
2981
+ const FUND_REDEEMER = require_grant_status.encodeCustodyAccountRedeemer({ kind: "fund" });
2982
+ const SWEEP_REDEEMER = require_grant_status.encodeCustodyAccountRedeemer({ kind: "sweepGrant" });
2983
+ const ISSUE_REDEEMER = require_grant_status.encodeCustodyMintRedeemer({ kind: "issueGrants" });
2984
+ const BURN_REDEEMER = require_grant_status.encodeCustodyMintRedeemer({ kind: "burnGrants" });
2985
+ /**
2986
+ * Refuses a grant whose per-call caps cannot pay the smallest spend: one
2987
+ * output at its minimum ADA and the default grant fee bound. Its agent could
2988
+ * never spend it.
2989
+ */
2990
+ const spendableGrant = ({ grant }, { identity }, { protocolParameters }) => require_grant_status.isCardanoCustodyGrantExhausted(grant.scope, {
2991
+ protocolParameters,
2992
+ networkId: identity.chainId.networkId
2993
+ }) ? failCustodyBuild({
2994
+ code: "invalid-operation",
2995
+ reason: "grant-scope",
2996
+ message: "The caps cannot pay the smallest spend: one output at its minimum ADA and the grant fee bound"
2997
+ }) : grant;
2998
+ /** What an issue or a replace writes: the state and the grant it issues. */
2999
+ const issueEffectOf = (result, context, chain) => {
3000
+ const issued = transitioned(result);
3001
+ return {
3002
+ state: issued.state,
3003
+ outputs: [],
3004
+ issued: spendableGrant(issued, context, chain)
3005
+ };
3006
+ };
3007
+ /**
3008
+ * The sweep of the dead grants in `slots`. Each must be dead against the
3009
+ * state before the sweep. The validity interval starts no earlier than the
3010
+ * current slot, so a grant dead by expiry alone must expire before the
3011
+ * current slot starts; the interval then starts in the first slot after
3012
+ * the latest such expiry.
3013
+ */
3014
+ const sweepEffectOf = (slots, { state, grants, spentInFlight }, chain) => {
3015
+ const swept = slots.map((slot) => {
3016
+ const found = grants.find(({ grant }) => grant.slot === slot);
3017
+ if (!found) return failCustodyBuild({
3018
+ code: "invalid-operation",
3019
+ reason: "state-transition",
3020
+ error: {
3021
+ code: "grant-not-found",
3022
+ slot
3023
+ }
3024
+ });
3025
+ return spentInFlight.has(require_util.outpointKey(found.utxo[0])) ? failCustodyBuild({ code: "operation-in-progress" }) : found;
3026
+ });
3027
+ const currentSlotStart = chain.clock.slotStart(chain.clock.slotAt(chain.now));
3028
+ const { state: next, startsAfter } = transitioned(sweepCustodyGrants(state, swept.map(({ grant }) => grant), currentSlotStart));
3029
+ return {
3030
+ state: next,
3031
+ outputs: [],
3032
+ swept: {
3033
+ grants: swept,
3034
+ ...startsAfter !== void 0 && { invalidBefore: firstSlotStartingAfter(startsAfter, chain) }
3035
+ }
3036
+ };
3037
+ };
3038
+ /**
3039
+ * The metadata an operation carries. Only an owner spend carries any: the
3040
+ * other operation types leave it out, and a caller that names it on one
3041
+ * anyway is refused rather than built without it, as a grant spend is.
3042
+ */
3043
+ const metadataOf = (operation) => {
3044
+ const { metadata } = operation;
3045
+ if (metadata !== void 0 && operation.type !== "owner-spend") return failCustodyBuild({
3046
+ code: "invalid-operation",
3047
+ reason: "invalid-metadata",
3048
+ message: `A ${operation.type} carries no metadata`
3049
+ });
3050
+ return checkRequestedMetadata(metadata);
3051
+ };
3052
+ /**
3053
+ * The state an owner operation writes and what else it does. State changes
3054
+ * go through the library's transitions, which refuse what the account could
3055
+ * not use afterwards; withdrawal reads the reward balance now, because the
3056
+ * ledger accepts only the exact balance.
3057
+ */
3058
+ const ownerEffectOf = async (operation, context, chain) => {
3059
+ const { identity, state } = context;
3060
+ const metadata = metadataOf(operation);
3061
+ const grants = context.grants.map(({ grant }) => grant);
3062
+ const requestOf = (scope) => grantRequestOf({
3063
+ scope,
3064
+ slot: state.nextSlot
3065
+ }, identity, chain.now);
3066
+ switch (operation.type) {
3067
+ case "issue-grant": return issueEffectOf(issueCustodyGrant(state, requestOf(operation.scope), grants), context, chain);
3068
+ case "replace-grant": return issueEffectOf(replaceCustodyGrant(state, {
3069
+ ...requestOf(operation.scope),
3070
+ slot: operation.slot
3071
+ }, grants), context, chain);
3072
+ case "revoke-grant": return {
3073
+ state: transitioned(revokeCustodyGrant(state, operation.slot, grants)),
3074
+ outputs: []
3075
+ };
3076
+ case "revoke-all-grants": return {
3077
+ state: revokeAllCustodyGrants(state),
3078
+ outputs: []
3079
+ };
3080
+ case "sweep-grants": return sweepEffectOf(operation.slots, context, chain);
3081
+ case "add-device": return {
3082
+ state: transitioned(addCustodyDevice(state, operation.keyHash, grants)),
3083
+ outputs: []
3084
+ };
3085
+ case "remove-device": return {
3086
+ state: transitioned(removeCustodyDevice(state, operation.keyHash)),
3087
+ outputs: []
3088
+ };
3089
+ case "owner-spend": return {
3090
+ state,
3091
+ outputs: operation.outputs,
3092
+ ...metadata && { metadata }
3093
+ };
3094
+ case "delegate": return {
3095
+ state,
3096
+ outputs: [],
3097
+ poolId: poolIdOf(operation.poolId)
3098
+ };
3099
+ case "withdraw-rewards": {
3100
+ const { withdrawableAmount } = await readFromProvider("Custody reward account", () => chain.provider.getRewardAccountInfo({ rewardAccount: require_util.CardanoRewardAccount(identity.rewardAccount) }, { chainId: chain.chainId }));
3101
+ const withdrawal = require_bignum_vo.BigNumber.valueOf(withdrawableAmount);
3102
+ return withdrawal > 0n ? {
3103
+ state,
3104
+ outputs: [],
3105
+ withdrawal
3106
+ } : failCustodyBuild({
3107
+ code: "invalid-operation",
3108
+ reason: "no-rewards",
3109
+ message: "The custody account has no rewards to withdraw"
3110
+ });
3111
+ }
3112
+ }
3113
+ };
3114
+ const sweptGrantsOf = ({ effect }) => effect.swept?.grants ?? [];
3115
+ /**
3116
+ * The lovelace the withdrawals add to the transaction: the rewards an
3117
+ * operation withdraws and whatever the logic run draws. The change or the
3118
+ * control output keeps it at the account address.
3119
+ */
3120
+ const withdrawnOf = ({ effect, logicRun }) => (effect.withdrawal ?? 0n) + logicRun.withdrawal.quantity;
3121
+ /** The transaction's plan with `funds` paying and the control output at `controlLovelace`. */
3122
+ const planOf = (build, funds, controlLovelace) => {
3123
+ const { context, chain, effect, signer, funding, issued, witnesses } = build;
3124
+ const { identity, control } = context;
3125
+ const swept = sweptGrantsOf(build);
3126
+ const isStakeOperation = effect.poolId !== void 0 || effect.withdrawal !== void 0;
3127
+ const mint = issued ? {
3128
+ assets: new Map([[require_tokens.custodyGrantAssetId(context.record, issued.grant.slot), 1n]]),
3129
+ redeemer: ISSUE_REDEEMER
3130
+ } : swept.length > 0 ? {
3131
+ assets: new Map(swept.map(({ token }) => [token, -1n])),
3132
+ redeemer: BURN_REDEEMER
3133
+ } : void 0;
3134
+ return {
3135
+ inputs: [
3136
+ {
3137
+ utxo: control,
3138
+ redeemer: DEVICE_REDEEMER
3139
+ },
3140
+ ...swept.map(({ utxo }) => ({
3141
+ utxo,
3142
+ redeemer: SWEEP_REDEEMER
3143
+ })),
3144
+ ...funds.map((utxo) => ({
3145
+ utxo,
3146
+ redeemer: FUND_REDEEMER
3147
+ }))
3148
+ ],
3149
+ outputs: [
3150
+ controlOutputOf(identity, effect.state, controlLovelace),
3151
+ ...issued ? [issued.output] : [],
3152
+ ...effect.outputs
3153
+ ],
3154
+ referenceInputs: witnesses.referenced,
3155
+ changeAddress: identity.address,
3156
+ scripts: [...witnesses.attached, ...isStakeOperation ? [identity.stakeScript] : []],
3157
+ executionEstimate: custodyExecutionEstimate({
3158
+ device: 1,
3159
+ sweepGrant: swept.length,
3160
+ fund: funds.length,
3161
+ issueGrants: issued ? 1 : 0,
3162
+ burnGrants: swept.length > 0 ? 1 : 0,
3163
+ operate: isStakeOperation ? 1 : 0,
3164
+ logic: "owner"
3165
+ }),
3166
+ requiredSigner: signer,
3167
+ ...effect.swept?.invalidBefore !== void 0 && { invalidBefore: effect.swept.invalidBefore },
3168
+ invalidHereafter: validitySlotFor(funding.validUntil, chain),
3169
+ collateral: {
3170
+ utxo: funding.collateralUtxo,
3171
+ returnAddress: funding.sponsorAddress
3172
+ },
3173
+ ...mint && { mint },
3174
+ ...effect.poolId && { delegation: {
3175
+ stakeCredential: scriptCredentialOf(identity.stakeScriptHash),
3176
+ poolId: effect.poolId,
3177
+ redeemer: require_grant_status.encodeCustodyStakeRedeemer()
3178
+ } },
3179
+ withdrawals: [build.logicRun.withdrawal, ...effect.withdrawal ? [{
3180
+ rewardAccount: identity.rewardAccount,
3181
+ quantity: effect.withdrawal,
3182
+ redeemer: require_grant_status.encodeCustodyStakeRedeemer()
3183
+ }] : []],
3184
+ ...effect.metadata && { metadata: effect.metadata }
3185
+ };
3186
+ };
3187
+ /**
3188
+ * Moves `amount` lovelace from the change into the control output once the
3189
+ * fee is final. A change left below its minimum ADA joins the control
3190
+ * output whole, when it holds no token.
3191
+ *
3192
+ * @throws `FundShortfall` when the change cannot spare `amount`; once no
3193
+ * fund is left the build fails with `owner-reserve-required`, or with
3194
+ * `insufficient-funds` when there is no change at all
3195
+ */
3196
+ const topUpControl = (transaction, { context, chain, effect, issued }, amount) => {
3197
+ const outputs = [...transaction.body().toCore().outputs];
3198
+ const [control] = outputs;
3199
+ const changeIndex = changeIndexOf(outputs, context.identity.address, 1 + (issued ? 1 : 0) + effect.outputs.length);
3200
+ if (changeIndex < 0) throw new FundShortfall("No change output to top the reserve up from");
3201
+ const change = outputs[changeIndex];
3202
+ const left = change.value.coins - amount;
3203
+ const kept = (0, _laceio_cardano_tx_construction.minAdaRequired)(change, BigInt(chain.protocolParameters.coinsPerUtxoByte));
3204
+ const hasTokens = (change.value.assets?.size ?? 0) > 0;
3205
+ if (left >= kept) {
3206
+ outputs[0] = withCoins(control, control.value.coins + amount);
3207
+ outputs[changeIndex] = withCoins(change, left);
3208
+ } else if (!hasTokens && left >= 0n) {
3209
+ outputs[0] = withCoins(control, control.value.coins + change.value.coins);
3210
+ outputs.splice(changeIndex, 1);
3211
+ } else throw new FundShortfall("The change cannot top the owner fee reserve up", {
3212
+ code: "owner-reserve-required",
3213
+ target: CUSTODY_OWNER_FEE_RESERVE_TARGET,
3214
+ shortfall: amount - (hasTokens ? change.value.coins - kept : change.value.coins)
3215
+ });
3216
+ return {
3217
+ transaction: withOutputs(transaction, outputs),
3218
+ controlLovelace: outputs[0].value.coins
3219
+ };
3220
+ };
3221
+ /**
3222
+ * Plain funds, then reserves, largest first, pay the requested outputs, the
3223
+ * issued grant's lovelace, any growth of the control output and the fee, and
3224
+ * the change, rewards included, returns to the account without a datum. A
3225
+ * reserve's remainder becomes plain change. Swept grant lovelace goes to the
3226
+ * control output, and an issue tops the control headroom up to the owner fee
3227
+ * reserve target from the change.
3228
+ *
3229
+ * A fund carrying a reference script is never spent. Anyone can send one to
3230
+ * the account. Where the proxy and the logic are attached, such a fund
3231
+ * holding one of them would make the ledger refuse every owner operation
3232
+ * for providing the script twice, instead of falling back to the control
3233
+ * headroom; anywhere, its script would add to the fee and, for the fee
3234
+ * sponsor, to the languages of the script data hash.
3235
+ */
3236
+ const buildPaidByFunds = async (build) => {
3237
+ const { context, chain, effect, issued, minimum } = build;
3238
+ const { identity } = context;
3239
+ const spendable = context.funds.filter(([, output]) => output.scriptReference === void 0);
3240
+ const { protocolParameters: parameters } = chain;
3241
+ const swept = sweptGrantsOf(build);
3242
+ const controlLovelace = build.spentAccountLovelace > minimum ? build.spentAccountLovelace : minimum;
3243
+ const reserveTarget = minimum + CUSTODY_OWNER_FEE_RESERVE_TARGET;
3244
+ const topUp = issued && reserveTarget > controlLovelace ? reserveTarget - controlLovelace : 0n;
3245
+ const requested = totalValueOf(effect.outputs);
3246
+ 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);
3247
+ let settledLovelace = controlLovelace;
3248
+ return {
3249
+ transaction: await buildWithEvaluatedBudgets({
3250
+ chain,
3251
+ funds: selectFunds(spendable, {
3252
+ coins: needed > 0n ? needed : 0n,
3253
+ assets: requested.assets
3254
+ }, context.reserves),
3255
+ plan: (funds) => planOf(build, funds, controlLovelace),
3256
+ ...topUp > 0n && { settle: (built) => {
3257
+ const settled = topUpControl(built, build, topUp);
3258
+ settledLovelace = settled.controlLovelace;
3259
+ return settled.transaction;
3260
+ } }
3261
+ }),
3262
+ controlLovelace: settledLovelace,
3263
+ candidates: [
3264
+ ...spendable,
3265
+ ...context.reserves,
3266
+ ...swept.map(({ utxo }) => utxo)
3267
+ ]
3268
+ };
3269
+ };
3270
+ const headroomShortfall = () => failCustodyBuild({
3271
+ code: "insufficient-funds",
3272
+ message: "The plain funds and reserves cannot pay the fee, and the control output headroom cannot either"
3273
+ });
3274
+ /**
3275
+ * The control output pays the fee from its headroom: the transaction spends
3276
+ * only the control UTxO and any swept grant UTxOs, and the control output
3277
+ * takes everything they hold and whatever the withdrawals add, less the
3278
+ * fee. It must keep the new state's minimum and the growth margin, which a
3279
+ * headroom below the least fee never keeps, so such a build is refused
3280
+ * before any evaluation.
3281
+ */
3282
+ const buildPaidByControl = async (build) => {
3283
+ const { chain, minimum, spentAccountLovelace } = build;
3284
+ const held = spentAccountLovelace + withdrawnOf(build);
3285
+ const plan = {
3286
+ ...planOf(build, [], held),
3287
+ isFeePaidByFirstOutput: true
3288
+ };
3289
+ const floor = minimum + BigInt(CUSTODY_CONTROL_GROWTH_MARGIN_BYTES) * BigInt(chain.protocolParameters.coinsPerUtxoByte);
3290
+ if (held - leastFeeOf(plan, chain.protocolParameters) < floor) return headroomShortfall();
3291
+ const transaction = await buildWithEvaluatedBudgets({
3292
+ chain,
3293
+ funds: {
3294
+ selected: [],
3295
+ remaining: []
3296
+ },
3297
+ plan: () => plan
3298
+ });
3299
+ const controlLovelace = held - transaction.body().fee();
3300
+ if (controlLovelace < floor) return headroomShortfall();
3301
+ return {
3302
+ transaction,
3303
+ controlLovelace,
3304
+ candidates: sweptGrantsOf(build).map(({ utxo }) => utxo)
3305
+ };
3306
+ };
3307
+ const isInsufficientFunds = (error) => error instanceof CustodyBuildFailure && error.reason.code === "insufficient-funds";
3308
+ /**
3309
+ * Builds an owner operation in the sponsor's collateral mode: the account
3310
+ * pays and the sponsor contributes only its shared collateral. The control
3311
+ * UTxO is spent with `Device` and recreated from the resulting state, the
3312
+ * logic its state names runs through its withdrawal, and the bound device
3313
+ * key is the only required signer. The proxy and the logic are read from
3314
+ * their parked UTxOs on a network that has them, and attached elsewhere; a
3315
+ * stake operation attaches the stake script.
3316
+ *
3317
+ * Plain funds and reserves pay first. When they cannot, an operation that
3318
+ * pays nothing out and issues no grant takes its fee from the control
3319
+ * output's headroom instead.
3320
+ */
3321
+ const buildCustodyOwnerOperation = async ({ operation, funding }, context, chain) => {
3322
+ const signer = requireListedDevice(context);
3323
+ const { identity, control } = context;
3324
+ const { coinsPerUtxoByte } = chain.protocolParameters;
3325
+ const effect = await ownerEffectOf(operation, context, chain);
3326
+ checkRequestedOutputs(effect.outputs, identity, coinsPerUtxoByte);
3327
+ const logicRun = await readCustodyLogicRun(accountLogicOf(context), chain);
3328
+ const { contractVersion } = identity;
3329
+ const swept = effect.swept?.grants ?? [];
3330
+ const { issued: grant } = effect;
3331
+ const issued = grant && {
3332
+ grant,
3333
+ output: grantOutputOf(identity, grant, outputLovelaceFor((lovelace) => grantOutputOf(identity, grant, lovelace), 0n, coinsPerUtxoByte))
3334
+ };
3335
+ const build = {
3336
+ context,
3337
+ chain,
3338
+ effect,
3339
+ signer,
3340
+ funding,
3341
+ ...issued && { issued },
3342
+ minimum: controlLovelaceFor({
3343
+ identity,
3344
+ state: effect.state,
3345
+ floor: 0n
3346
+ }, coinsPerUtxoByte),
3347
+ spentAccountLovelace: swept.reduce((total, { utxo: [, output] }) => total + output.value.coins, control[1].value.coins),
3348
+ logicRun,
3349
+ witnesses: custodyScriptWitnesses(contractVersion, chain, [contractVersion.accountValidator, logicRun.logic.script])
3350
+ };
3351
+ const isControlPayable = effect.outputs.length === 0 && !issued;
3352
+ let paid;
3353
+ try {
3354
+ paid = await buildPaidByFunds(build);
3355
+ } catch (error) {
3356
+ if (!isControlPayable || !isInsufficientFunds(error)) throw error;
3357
+ paid = await buildPaidByControl(build);
3358
+ }
3359
+ const { transaction } = paid;
3360
+ return finalizeCustodyBuild({
3361
+ transaction,
3362
+ resolvedInputs: resolvedInputsOf(transaction, [
3363
+ control,
3364
+ ...paid.candidates,
3365
+ ...build.witnesses.referenced,
3366
+ funding.collateralUtxo
3367
+ ])
3368
+ }, chain, {
3369
+ record: context.record,
3370
+ control: {
3371
+ state: effect.state,
3372
+ lovelace: paid.controlLovelace
3373
+ },
3374
+ issuedGrants: issued ? [{
3375
+ grant: issued.grant,
3376
+ lovelace: issued.output.value.coins
3377
+ }] : [],
3378
+ sweptSlots: swept.map(({ grant }) => grant.slot),
3379
+ signer,
3380
+ deviceKeyHash: signer,
3381
+ outflow: {
3382
+ requested: totalValueOf(effect.outputs),
3383
+ withdrawal: withdrawnOf(build)
3384
+ },
3385
+ funding
3386
+ });
3387
+ };
3388
+ //#endregion
3389
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/build-custody-transaction.ts
3390
+ const ACCOUNT_OPERATION_TYPES = new Set([
3391
+ "add-device",
3392
+ "delegate",
3393
+ "grant-spend",
3394
+ "issue-grant",
3395
+ "owner-spend",
3396
+ "remove-device",
3397
+ "replace-grant",
3398
+ "revoke-all-grants",
3399
+ "revoke-grant",
3400
+ "sweep-grants",
3401
+ "withdraw-rewards"
3402
+ ]);
3403
+ const refuseOperation = (type) => failCustodyBuild({
3404
+ code: "invalid-operation",
3405
+ reason: "unknown-operation",
3406
+ message: `Unknown custody operation ${type} for this request`
3407
+ });
3408
+ const refuseFundingMode = (mode) => failCustodyBuild({
3409
+ code: "invalid-operation",
3410
+ reason: "funding-mode",
3411
+ message: `Funding mode ${mode} does not fit the operation`
3412
+ });
3413
+ /**
3414
+ * Builds the custody transaction a request asks for. Creation runs in the
3415
+ * sponsor's fee mode on the current network. Every operation on an
3416
+ * existing account runs in collateral mode, with the account paying: the
3417
+ * sponsor's fee mode serves creation only.
3418
+ */
3419
+ const buildCustodyTransaction = async (request, sources) => {
3420
+ const chain = await loadCustodyChain(sources);
3421
+ if (!("accountId" in request)) {
3422
+ const { operation, funding } = request;
3423
+ if (operation.type !== "create") return refuseOperation(operation.type);
3424
+ if (funding.mode !== "fee") return refuseFundingMode(funding.mode);
3425
+ return buildCustodyCreation(operation, funding, chain);
3426
+ }
3427
+ const { accountId, operation, funding } = request;
3428
+ if (!ACCOUNT_OPERATION_TYPES.has(operation.type)) return refuseOperation(operation.type);
3429
+ if (funding.mode !== "collateral") return refuseFundingMode(funding.mode);
3430
+ const context = await resolveCustodyAccount(accountId, await sources.snapshot(accountId), chain);
3431
+ return operation.type === "grant-spend" ? buildCustodyGrantSpend({
3432
+ operation,
3433
+ funding
3434
+ }, context, chain) : buildCustodyOwnerOperation({
3435
+ operation,
3436
+ funding
3437
+ }, context, chain);
3438
+ };
3439
+ //#endregion
3440
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/creator-index-probe-side-effect.ts
3441
+ const isSoftIndex = (index) => Number.isSafeInteger(index) && index >= 0 && index < 2147483648;
3442
+ const probeErrorOf = (reason) => {
3443
+ if (reason.code === "unavailable" || reason.code === "build-failed") return reason;
3444
+ if (reason.code === "invalid-operation" && reason.reason === "invalid-device-key") return {
3445
+ ...reason,
3446
+ reason: reason.reason
3447
+ };
3448
+ return {
3449
+ code: "build-failed",
3450
+ message: `Unexpected creator index probe failure: ${reason.code}`
3451
+ };
3452
+ };
3453
+ const resultOf = ({ accountValidatorHash, creatorIndex }, state) => {
3454
+ const probed = {
3455
+ accountValidatorHash,
3456
+ creatorIndex
3457
+ };
3458
+ return state.status === "account" ? {
3459
+ ...probed,
3460
+ status: "account",
3461
+ record: state.record
3462
+ } : {
3463
+ ...probed,
3464
+ status: state.status
3465
+ };
3466
+ };
3467
+ const probe = async (request, { chainId, provider, logger }) => {
3468
+ const contractVersion = require_hex.findCustodyContractVersion(request.accountValidatorHash);
3469
+ if (!contractVersion.ok) return {
3470
+ ok: false,
3471
+ error: contractVersion.error
3472
+ };
3473
+ if (!isSoftIndex(request.creatorIndex)) return {
3474
+ ok: false,
3475
+ error: {
3476
+ code: "invalid-operation",
3477
+ reason: "invalid-start-index",
3478
+ message: `Invalid creator index ${request.creatorIndex}`
3479
+ }
3480
+ };
3481
+ if (!chainId) return {
3482
+ ok: false,
3483
+ error: {
3484
+ code: "unavailable",
3485
+ message: "Cardano chain id not available"
3486
+ }
3487
+ };
3488
+ try {
3489
+ return {
3490
+ ok: true,
3491
+ result: resultOf(request, await readCustodyCreatorIndex({
3492
+ contractVersion: contractVersion.value,
3493
+ deviceAccountPublicKey: request.deviceAccountPublicKey,
3494
+ creatorIndex: request.creatorIndex
3495
+ }, {
3496
+ chainId,
3497
+ provider
3498
+ }))
3499
+ };
3500
+ } catch (error) {
3501
+ if (error instanceof CustodyBuildFailure) return {
3502
+ ok: false,
3503
+ error: probeErrorOf(error.reason)
3504
+ };
3505
+ logger.error("Custody creator index probe failed unexpectedly", error);
3506
+ return {
3507
+ ok: false,
3508
+ error: {
3509
+ code: "build-failed",
3510
+ message: messageOf(error)
3511
+ }
3512
+ };
3513
+ }
3514
+ };
3515
+ const makeCustodyCreatorIndexProbing = () => ({ cardanoCustody: { probeCustodyCreatorIndex$ } }, { cardanoContext }, { cardanoProvider, actions, logger }) => {
3516
+ const answer = async (request) => probe(request, {
3517
+ chainId: await (0, rxjs.firstValueFrom)(cardanoContext.selectChainId$),
3518
+ provider: cardanoProvider,
3519
+ logger
3520
+ });
3521
+ return probeCustodyCreatorIndex$.pipe((0, rxjs.mergeMap)(({ payload: request }) => (0, rxjs.from)(answer(request)).pipe((0, rxjs.map)((outcome) => outcome.ok ? actions.cardanoCustody.probeCustodyCreatorIndexCompleted({
3522
+ requestId: request.requestId,
3523
+ result: outcome.result
3524
+ }) : actions.cardanoCustody.probeCustodyCreatorIndexFailed({
3525
+ requestId: request.requestId,
3526
+ error: outcome.error
3527
+ })))));
3528
+ };
3529
+ //#endregion
3530
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/build-side-effect.ts
3531
+ const failureOf = (error, logger) => {
3532
+ if (error instanceof CustodyBuildFailure) return error.reason;
3533
+ logger.error("Custody transaction build failed unexpectedly", error);
3534
+ return {
3535
+ code: "build-failed",
3536
+ message: messageOf(error)
3537
+ };
3538
+ };
3539
+ const makeCustodyTransactionBuilding = () => ({ cardanoCustody: { buildCustodyTransaction$ } }, { cardanoContext, cardanoCustody, wallets }, { cardanoProvider, actions, logger }) => {
3540
+ const snapshot = async (accountId) => {
3541
+ const [allWallets, accountStateOf, settledUtxos] = await Promise.all([
3542
+ (0, rxjs.firstValueFrom)(wallets.selectAll$),
3543
+ (0, rxjs.firstValueFrom)(cardanoCustody.selectCardanoCustodyAccountState$),
3544
+ (0, rxjs.firstValueFrom)(cardanoContext.selectAccountUtxos$)
3545
+ ]);
3546
+ return {
3547
+ account: allWallets.flatMap((wallet) => wallet.accounts).filter(require_account.isCardanoCustodyAccount).find((account) => account.accountId === accountId),
3548
+ accountState: accountStateOf(accountId),
3549
+ settledUtxos: settledUtxos[accountId] ?? []
3550
+ };
3551
+ };
3552
+ const build = async (request) => {
3553
+ try {
3554
+ const [chainId, eraSummaries] = await Promise.all([(0, rxjs.firstValueFrom)(cardanoContext.selectChainId$), (0, rxjs.firstValueFrom)(cardanoContext.selectEraSummaries$)]);
3555
+ return {
3556
+ ok: true,
3557
+ result: await buildCustodyTransaction(require_serializable.Serializable.from(request), {
3558
+ chainId,
3559
+ eraSummaries,
3560
+ snapshot,
3561
+ provider: cardanoProvider,
3562
+ now: Date.now()
3563
+ })
3564
+ };
3565
+ } catch (error) {
3566
+ return {
3567
+ ok: false,
3568
+ error: failureOf(error, logger)
3569
+ };
3570
+ }
3571
+ };
3572
+ return buildCustodyTransaction$.pipe((0, rxjs.mergeMap)(({ payload: { requestId, request } }) => (0, rxjs.from)(build(request)).pipe((0, rxjs.map)((outcome) => outcome.ok ? actions.cardanoCustody.buildCustodyTransactionCompleted({
3573
+ requestId,
3574
+ result: outcome.result
3575
+ }) : actions.cardanoCustody.buildCustodyTransactionFailed({
3576
+ requestId,
3577
+ error: outcome.error
3578
+ })))));
3579
+ };
3580
+ const custodyTransactionSideEffects = [makeCustodyTransactionBuilding(), makeCustodyCreatorIndexProbing()];
3581
+ //#endregion
3582
+ //#region ../../packages/module/blockchain-cardano/src/store/custody/init.ts
3583
+ const init = () => ({ sideEffects: custodyTransactionSideEffects });
3584
+ //#endregion
3585
+ exports.default = init;