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