@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,2688 @@
1
+ import { d as fail, f as ok, i as isLowerCaseHex, l as findCustodyContractVersion, o as SUPPORTED_CUSTODY_CONTRACT_VERSIONS, r as isHash28Hex, u as findCustodyLogic } from "./hex-mkXcV3pN.js";
2
+ import { A as isCustodyLovelaceAsset, B as recipientKey, C as policyIdOf, D as findCustodyStateViolations, E as findCustodyGrantViolations, G as unknownAlternative, H as decodeResult, M as decodeCustodyGrant, O as isCustodyGrantCurrent, P as decodeCustodyStateLogic, R as custodyRecipientFromAddress, S as negateValueDelta, U as expectConstr, V as constr, W as expectConstrIndex, _ as stateNftAssetIdOf, b as assetNameOf, c as grantAssetIdOf, d as holdsNoAccountToken, f as holdsOnlyLovelaceAndGrantToken, g as isKnownCustodyLogic, h as isControlOutput, i as accountUtxoKindOf, j as decodeCustodyAccountState, k as isCustodyGrantExpired, l as holdsAccountToken, m as isAtAccount, n as accountPlutusAddressKey, o as decodedInlineGrant, p as holdsOnlyLovelaceAndStateNft, r as accountRewardAccountOf, s as decodedInlineState, t as accountAddressOf, u as holdsMisplacedAccountToken, v as viewOfOutput, w as quantityOfAsset, x as isAssetIdOf, y as addValueDeltas, z as custodyRecipientToAddress } from "./outputs-Bd2yZVjx.js";
3
+ import { r as parseCustodyAccountTokenName } from "./tokens-Diw4MLRM.js";
4
+ import { a as isNetworkMagic, i as identityOfValidatedCreator, n as chainIdFromNetworkMagic } from "./identity-DII6f-h7.js";
5
+ import * as Crypto from "@laceio/cardano/crypto";
6
+ import { Hash28ByteBase16, blake2b } from "@laceio/cardano/crypto";
7
+ import { Cardano, Serialization } from "@laceio/cardano/core";
8
+ import { HexBlob } from "@laceio/cardano/util";
9
+ import { minAdaRequired } from "@laceio/cardano/tx-construction";
10
+ //#region ../../packages/lib/cardano-custody/src/analysis/transaction-facts.ts
11
+ const outpointKey = ({ txId, index }) => `${txId.toLowerCase()}#${index}`;
12
+ const toTxIn = ({ txId, index }) => ({
13
+ txId,
14
+ index
15
+ });
16
+ /** Byte order of lower-case hex, then index: how the ledger orders inputs. */
17
+ const compareOutpoints = (a, b) => {
18
+ const aId = a.txId.toLowerCase();
19
+ const bId = b.txId.toLowerCase();
20
+ if (aId !== bId) return aId < bId ? -1 : 1;
21
+ return a.index - b.index;
22
+ };
23
+ /**
24
+ * Pairs every input the body names with the caller's resolution. Every
25
+ * input, reference input and collateral input must be resolved exactly once,
26
+ * and no resolution may name an outpoint the body does not.
27
+ */
28
+ const resolveBody = (body, resolvedInputs) => {
29
+ const refusals = [];
30
+ const resolutions = /* @__PURE__ */ new Map();
31
+ const sources = [
32
+ [body.inputs, "inputs"],
33
+ [body.referenceInputs ?? [], "referenceInputs"],
34
+ [body.collaterals ?? [], "collaterals"]
35
+ ];
36
+ const named = new Set(sources.flatMap(([inputs]) => inputs.map(outpointKey)));
37
+ for (const [input, txOut] of resolvedInputs) {
38
+ const key = outpointKey(input);
39
+ const view = viewOfOutput(txOut);
40
+ if (!named.has(key)) refusals.push({
41
+ code: "custody-context-mismatch",
42
+ reason: "resolution-not-in-body",
43
+ input: toTxIn(input)
44
+ });
45
+ else if (resolutions.has(key)) refusals.push({
46
+ code: "custody-context-mismatch",
47
+ reason: "duplicate-resolution",
48
+ input: toTxIn(input)
49
+ });
50
+ else if (view.address === void 0) refusals.push({
51
+ code: "custody-context-mismatch",
52
+ reason: "invalid-resolution",
53
+ input: toTxIn(input)
54
+ });
55
+ resolutions.set(key, view);
56
+ }
57
+ const resolve = (inputs, source) => inputs.flatMap((input) => {
58
+ const output = resolutions.get(outpointKey(input));
59
+ if (output) return [{
60
+ input: toTxIn(input),
61
+ output
62
+ }];
63
+ refusals.push({
64
+ code: "custody-context-mismatch",
65
+ reason: "unresolved-input",
66
+ input: toTxIn(input),
67
+ source
68
+ });
69
+ return [];
70
+ });
71
+ const [inputs, referenceInputs, collaterals] = sources.map(([list, source]) => resolve(list, source));
72
+ return refusals.length > 0 ? fail(refusals) : ok({
73
+ inputs: inputs.sort((a, b) => compareOutpoints(a.input, b.input)),
74
+ referenceInputs,
75
+ collaterals
76
+ });
77
+ };
78
+ /** The key of the withdrawal map in a transaction body. */
79
+ const WITHDRAWALS_KEY = 5n;
80
+ /**
81
+ * The reward accounts the withdrawal maps of a body name, read from its
82
+ * bytes, repeats included: a repeated key, and a repeated map, are lost
83
+ * once the body is decoded.
84
+ */
85
+ const withdrawalAccountsOf = (bodyCbor) => {
86
+ const reader = new Serialization.CborReader(bodyCbor);
87
+ const accounts = [];
88
+ reader.readStartMap();
89
+ while (reader.peekState() !== Serialization.CborReaderState.EndMap) {
90
+ if (reader.readInt() !== WITHDRAWALS_KEY) {
91
+ reader.skipValue();
92
+ continue;
93
+ }
94
+ reader.readStartMap();
95
+ while (reader.peekState() !== Serialization.CborReaderState.EndMap) {
96
+ accounts.push(Cardano.Address.fromBytes(HexBlob.fromBytes(reader.readByteString())).toBech32());
97
+ reader.skipValue();
98
+ }
99
+ reader.readEndMap();
100
+ }
101
+ return accounts;
102
+ };
103
+ const readTransaction = ({ body, bodyCbor, witness }, resolved, options) => {
104
+ const mint = new Map([...body.mint ?? /* @__PURE__ */ new Map()].map(([assetId, quantity]) => [assetId.toLowerCase(), quantity]));
105
+ const { invalidBefore, invalidHereafter } = body.validityInterval ?? {};
106
+ return {
107
+ body,
108
+ ...resolved,
109
+ outputs: body.outputs.map(viewOfOutput),
110
+ collateralReturn: body.collateralReturn ? viewOfOutput(body.collateralReturn) : void 0,
111
+ redeemers: witness.redeemers ?? [],
112
+ requiredSigners: new Set((body.requiredExtraSignatures ?? []).map((signer) => signer.toLowerCase())),
113
+ mintPolicies: [...new Set([...mint.keys()].map(policyIdOf))].sort(),
114
+ mint,
115
+ lowerBoundMs: invalidBefore === void 0 ? void 0 : options.slotToPosixMs(invalidBefore),
116
+ upperBoundMs: invalidHereafter === void 0 ? void 0 : options.slotToPosixMs(invalidHereafter),
117
+ witnessScriptHashes: new Set((witness.scripts ?? []).map((script) => Serialization.Script.fromCore(script).hash())),
118
+ withdrawalAccounts: withdrawalAccountsOf(bodyCbor)
119
+ };
120
+ };
121
+ const redeemerData = (facts, purpose, index) => facts.redeemers.find((redeemer) => redeemer.purpose === purpose && redeemer.index === index)?.data;
122
+ const paymentCredentialOf = (view) => (view.address?.asBase() ?? view.address?.asEnterprise() ?? view.address?.asPointer())?.getPaymentCredential();
123
+ const stakeCredentialOf = (certificate) => "stakeCredential" in certificate ? certificate.stakeCredential : void 0;
124
+ const scriptHashOfRewardAccount = (rewardAccount) => {
125
+ const credential = Cardano.Address.fromString(rewardAccount)?.asReward()?.getPaymentCredential();
126
+ return credential?.type === Cardano.CredentialType.ScriptHash ? credential.hash : void 0;
127
+ };
128
+ //#endregion
129
+ //#region ../../packages/lib/cardano-custody/src/analysis/grant-spend.ts
130
+ const valueAt = (views, plutusAddressKey) => addValueDeltas(views.filter((view) => view.plutusAddressKey !== void 0 && view.plutusAddressKey === plutusAddressKey).map((view) => view.value));
131
+ /** `leaving_value` over outputs that are already read. */
132
+ const leavingValueAt = (inputs, outputs, plutusAddressKey) => addValueDeltas([valueAt(inputs, plutusAddressKey), negateValueDelta(valueAt(outputs, plutusAddressKey))]);
133
+ /**
134
+ * Every rule of `stays_within_scope` a net outflow breaks: the scoped asset
135
+ * within its per call and remaining caps; for a token scope, lovelace within
136
+ * both lovelace caps; and no other asset class leaving. A net inflow breaks
137
+ * nothing.
138
+ *
139
+ * @param accountFee - the fee the account pays, which `leaving.lovelace`
140
+ * already includes; each violation whose outflow is lovelace reports it
141
+ * as `fee`
142
+ */
143
+ const findCustodyScopeViolations = (scope, leaving, accountFee) => {
144
+ const violations = [];
145
+ const isLovelaceScope = isCustodyLovelaceAsset(scope.asset);
146
+ const lovelaceFee = accountFee === void 0 ? {} : { fee: accountFee };
147
+ const assetFee = isLovelaceScope ? lovelaceFee : {};
148
+ const assetOutflow = quantityOfAsset(leaving, scope.asset);
149
+ if (assetOutflow > scope.perCallCap) violations.push({
150
+ code: "per-call-cap-exceeded",
151
+ outflow: assetOutflow,
152
+ limit: scope.perCallCap,
153
+ ...assetFee
154
+ });
155
+ if (assetOutflow > scope.cap) violations.push({
156
+ code: "cap-exceeded",
157
+ outflow: assetOutflow,
158
+ limit: scope.cap,
159
+ ...assetFee
160
+ });
161
+ if (!isLovelaceScope) {
162
+ if (leaving.lovelace > scope.lovelacePerCallCap) violations.push({
163
+ code: "lovelace-per-call-cap-exceeded",
164
+ outflow: leaving.lovelace,
165
+ limit: scope.lovelacePerCallCap,
166
+ ...lovelaceFee
167
+ });
168
+ if (leaving.lovelace > scope.lovelaceCap) violations.push({
169
+ code: "lovelace-cap-exceeded",
170
+ outflow: leaving.lovelace,
171
+ limit: scope.lovelaceCap,
172
+ ...lovelaceFee
173
+ });
174
+ }
175
+ for (const [assetId, quantity] of leaving.assets) if (quantity > 0n && !isAssetIdOf(assetId, scope.asset)) violations.push({
176
+ code: "foreign-asset-outflow",
177
+ assetId,
178
+ outflow: quantity
179
+ });
180
+ return violations;
181
+ };
182
+ const clampedOutflow = (quantity) => quantity > 0n ? quantity : 0n;
183
+ /**
184
+ * `grant_after_spend`: the grant with `cap`, and for a token grant
185
+ * `lovelaceCap`, lowered by the net outflow of its asset, never raised by a
186
+ * net deposit. These are the most the recreated grant may keep: the
187
+ * contract accepts any cap from zero to this value, so a builder may write
188
+ * lower caps that already count a fee it has not yet computed.
189
+ */
190
+ const custodyGrantAfterSpend = (grant, leaving) => {
191
+ const { scope } = grant;
192
+ return {
193
+ ...grant,
194
+ scope: {
195
+ ...scope,
196
+ cap: scope.cap - clampedOutflow(quantityOfAsset(leaving, scope.asset)),
197
+ lovelaceCap: isCustodyLovelaceAsset(scope.asset) ? scope.lovelaceCap : scope.lovelaceCap - clampedOutflow(leaving.lovelace)
198
+ }
199
+ };
200
+ };
201
+ /** `ends_before_expiry`: a finite upper bound no later than the expiry. */
202
+ const findExpiryViolation = (upperBoundMs, expiresAt) => {
203
+ if (upperBoundMs === void 0) return { code: "no-upper-bound" };
204
+ return Number.isSafeInteger(upperBoundMs) && BigInt(upperBoundMs) <= expiresAt ? void 0 : {
205
+ code: "expired",
206
+ upperBoundMs,
207
+ expiresAt
208
+ };
209
+ };
210
+ const findMisplacedAccountTokens = (facts) => [["input", facts.inputs], ["output", facts.outputs]].flatMap(([kind, views]) => views.flatMap((view, index) => holdsMisplacedAccountToken(view, [facts.account.accountValidatorHash]) ? [{
211
+ code: "account-token-misplaced",
212
+ location: {
213
+ kind,
214
+ index
215
+ }
216
+ }] : []));
217
+ /** `pays_only_recipients` and `deposits_are_plain`, per output. */
218
+ const findOutputViolations = (facts, grant) => {
219
+ const { recipients } = grant.scope;
220
+ const recipientKeys = new Set(recipients.map(recipientKey));
221
+ const { plutusAddressKey } = facts.grantInput;
222
+ return facts.outputs.flatMap((view, outputIndex) => {
223
+ const violations = [];
224
+ const isBack = view.plutusAddressKey !== void 0 && view.plutusAddressKey === plutusAddressKey;
225
+ if (recipients.length > 0 && !isBack && (view.plutusAddressKey === void 0 || !recipientKeys.has(view.plutusAddressKey))) violations.push({
226
+ code: "recipient-not-allowed",
227
+ outputIndex
228
+ });
229
+ if (isBack && !holdsAccountToken(view, facts.account) && (view.datumKind !== "none" || view.hasReferenceScript)) violations.push({
230
+ code: "deposit-not-plain",
231
+ outputIndex
232
+ });
233
+ return violations;
234
+ });
235
+ };
236
+ /** `referenced_control`: the account's control UTxO among the reference inputs, exactly once. */
237
+ const findReferencedControl = (facts) => {
238
+ const controls = facts.referenceInputs.filter((view) => isControlOutput(view, facts.account));
239
+ if (controls.length !== 1) return { violations: [{
240
+ code: "referenced-control-count",
241
+ count: controls.length
242
+ }] };
243
+ const state = decodedInlineState(controls[0]);
244
+ return state ? {
245
+ state,
246
+ violations: []
247
+ } : { violations: [{ code: "referenced-control-datum" }] };
248
+ };
249
+ /** `is_current`, naming why a grant is not current against the referenced state. */
250
+ const findCurrencyViolation = (grant, state) => {
251
+ if (grant.generation < state.grantGeneration) return {
252
+ code: "grant-not-current",
253
+ liveness: "superseded"
254
+ };
255
+ if (grant.generation > state.grantGeneration) return {
256
+ code: "grant-not-current",
257
+ liveness: "future-generation"
258
+ };
259
+ return state.revoked.includes(grant.slot) ? {
260
+ code: "grant-not-current",
261
+ liveness: "revoked"
262
+ } : void 0;
263
+ };
264
+ const isSameExceptCaps = (actual, expected) => {
265
+ const a = actual.scope;
266
+ const b = expected.scope;
267
+ return actual.slot === expected.slot && actual.grantee === expected.grantee && actual.generation === expected.generation && a.asset.policyId === b.asset.policyId && a.asset.assetName === b.asset.assetName && a.perCallCap === b.perCallCap && a.lovelacePerCallCap === b.lovelacePerCallCap && a.expiresAt === b.expiresAt && a.recipients.length === b.recipients.length && a.recipients.every((recipient, index) => recipientKey(recipient) === recipientKey(b.recipients[index]));
268
+ };
269
+ const isSameValue = (a, b) => a.lovelace === b.lovelace && a.assets.size === b.assets.size && [...a.assets].every(([assetId, quantity]) => b.assets.get(assetId) === quantity);
270
+ /**
271
+ * `find_grant_output` and the checks on it: one output holding the grant
272
+ * token, at the spent UTxO's address with its exact value and no reference
273
+ * script, carrying the grant with every field kept but caps lowered within
274
+ * `carries_grant_within`.
275
+ */
276
+ const findGrantOutputViolations = (facts, grant, expected) => {
277
+ const assetId = grantAssetIdOf(facts.account, grant.slot);
278
+ const holders = facts.outputs.flatMap((view, outputIndex) => assetId !== void 0 && view.value.assets.get(assetId) === 1n ? [{
279
+ view,
280
+ outputIndex
281
+ }] : []);
282
+ if (holders.length !== 1) return [{
283
+ code: "grant-output-count",
284
+ count: holders.length
285
+ }];
286
+ const [{ view, outputIndex }] = holders;
287
+ const violations = [];
288
+ if (view.plutusAddressKey === void 0 || view.plutusAddressKey !== facts.grantInput.plutusAddressKey) violations.push({
289
+ code: "grant-output-address",
290
+ outputIndex
291
+ });
292
+ if (!isSameValue(view.value, facts.grantInput.value)) violations.push({
293
+ code: "grant-output-value",
294
+ outputIndex
295
+ });
296
+ if (view.hasReferenceScript) violations.push({
297
+ code: "grant-output-reference-script",
298
+ outputIndex
299
+ });
300
+ if (view.inlineDatum === void 0) return [...violations, {
301
+ code: "grant-output-datum",
302
+ outputIndex,
303
+ reason: "not-inline"
304
+ }];
305
+ const actual = decodedInlineGrant(view);
306
+ if (!actual) return [...violations, {
307
+ code: "grant-output-datum",
308
+ outputIndex,
309
+ reason: "malformed"
310
+ }];
311
+ if (!isSameExceptCaps(actual, expected)) violations.push({
312
+ code: "grant-output-datum",
313
+ outputIndex,
314
+ reason: "field-changed"
315
+ });
316
+ for (const field of ["cap", "lovelaceCap"]) {
317
+ const capActual = actual.scope[field];
318
+ const capExpected = expected.scope[field];
319
+ if (capActual < 0n || capActual > capExpected) violations.push({
320
+ code: "grant-output-cap",
321
+ outputIndex,
322
+ field,
323
+ actual: capActual,
324
+ expected: capExpected
325
+ });
326
+ }
327
+ return violations;
328
+ };
329
+ /** Every violation of a grant spend whose views are already read. */
330
+ const grantSpendViolationsOf = (facts) => {
331
+ const grant = decodedInlineGrant(facts.grantInput);
332
+ if (!grant) return [{ code: "grant-datum-invalid" }];
333
+ const violations = [];
334
+ if (!holdsOnlyLovelaceAndGrantToken(facts.grantInput.value, facts.account, grant.slot)) violations.push({ code: "grant-input-value" });
335
+ const tokenInputs = facts.inputs.filter((view) => holdsAccountToken(view, facts.account)).length;
336
+ if (tokenInputs !== 1) violations.push({
337
+ code: "account-token-inputs",
338
+ count: tokenInputs
339
+ });
340
+ const control = findReferencedControl(facts);
341
+ violations.push(...control.violations);
342
+ const currency = control.state && findCurrencyViolation(grant, control.state);
343
+ if (currency) violations.push(currency);
344
+ if (!facts.requiredSigners.has(grant.grantee)) violations.push({
345
+ code: "grantee-not-signer",
346
+ grantee: grant.grantee
347
+ });
348
+ const expiry = findExpiryViolation(facts.upperBoundMs, grant.scope.expiresAt);
349
+ if (expiry) violations.push(expiry);
350
+ const leaving = leavingValueAt(facts.inputs, facts.outputs, facts.grantInput.plutusAddressKey);
351
+ return [
352
+ ...violations,
353
+ ...findCustodyScopeViolations(grant.scope, leaving),
354
+ ...findOutputViolations(facts, grant),
355
+ ...findMisplacedAccountTokens(facts),
356
+ ...findGrantOutputViolations(facts, grant, custodyGrantAfterSpend(grant, leaving)),
357
+ ...[...facts.mint].filter(([assetId]) => policyIdOf(assetId) === facts.account.accountValidatorHash).map(([assetId, quantity]) => ({
358
+ code: "mint-under-policy",
359
+ assetId,
360
+ quantity
361
+ }))
362
+ ];
363
+ };
364
+ //#endregion
365
+ //#region ../../packages/lib/cardano-custody/src/record.ts
366
+ const CUSTODY_ACCOUNT_RECORD_TYPE = "cardano-custody-account";
367
+ const createCustodyAccountRecord = (identity) => canonicalCustodyAccountRecord({
368
+ type: CUSTODY_ACCOUNT_RECORD_TYPE,
369
+ version: 1,
370
+ networkMagic: identity.chainId.networkMagic,
371
+ accountValidatorHash: identity.contractVersion.accountValidatorHash,
372
+ creatorKeyHash: identity.creatorKeyHash,
373
+ stakeScriptHash: identity.stakeScriptHash,
374
+ address: identity.address
375
+ });
376
+ /** A copy holding exactly the record fields, in their canonical order. */
377
+ const canonicalCustodyAccountRecord = (record) => ({
378
+ type: record.type,
379
+ version: record.version,
380
+ networkMagic: record.networkMagic,
381
+ accountValidatorHash: record.accountValidatorHash,
382
+ creatorKeyHash: record.creatorKeyHash,
383
+ stakeScriptHash: record.stakeScriptHash,
384
+ address: record.address
385
+ });
386
+ const RECORD_FIELDS = Object.keys(canonicalCustodyAccountRecord({}));
387
+ const UPSTREAM_RECORD_FIELDS = [
388
+ "owner",
389
+ "stakeScriptHash",
390
+ "address"
391
+ ];
392
+ const malformed$1 = (reason, field) => ({
393
+ step: 1,
394
+ code: "malformed-record",
395
+ reason,
396
+ ...field === void 0 ? {} : { field }
397
+ });
398
+ const parseJson = (input) => {
399
+ if (typeof input !== "string") return input;
400
+ try {
401
+ return JSON.parse(input);
402
+ } catch {
403
+ return;
404
+ }
405
+ };
406
+ const isPlainObject$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
407
+ const hasExactFields$1 = (value, fields) => {
408
+ const keys = Object.keys(value);
409
+ return keys.length === fields.length && fields.every((field) => keys.includes(field));
410
+ };
411
+ const parseAddress = (value) => typeof value === "string" && Cardano.Address.isValidBech32(value) ? Cardano.Address.fromBech32(value) : null;
412
+ const HASH_FIELDS = [
413
+ "accountValidatorHash",
414
+ "creatorKeyHash",
415
+ "stakeScriptHash"
416
+ ];
417
+ const readRecordClaims = (value) => {
418
+ if (!hasExactFields$1(value, RECORD_FIELDS)) return fail(malformed$1(`expected exactly ${RECORD_FIELDS.join(", ")}`));
419
+ if (value.type !== "cardano-custody-account") return fail(malformed$1("unknown record type", "type"));
420
+ if (value.version !== 1) return fail(malformed$1("unsupported record version", "version"));
421
+ if (!isNetworkMagic(value.networkMagic)) return fail(malformed$1("expected a network magic", "networkMagic"));
422
+ const invalidHash = HASH_FIELDS.find((field) => !isHash28Hex(value[field]));
423
+ if (invalidHash) return fail(malformed$1("expected 28 bytes of lower-case hex", invalidHash));
424
+ const address = parseAddress(value.address);
425
+ if (!address) return fail(malformed$1("expected an address", "address"));
426
+ return ok({
427
+ networkMagic: value.networkMagic,
428
+ accountValidatorHash: value.accountValidatorHash,
429
+ creatorKeyHash: value.creatorKeyHash,
430
+ stakeScriptHash: value.stakeScriptHash,
431
+ address,
432
+ addressText: value.address
433
+ });
434
+ };
435
+ /**
436
+ * Claims of the upstream `{ owner, stakeScriptHash, address }` record. It
437
+ * names no network or contract version: the network is the selected one, and
438
+ * the account validator is the address's payment script.
439
+ */
440
+ const readUpstreamRecordClaims = (value, selectedNetworkMagic) => {
441
+ if (!hasExactFields$1(value, UPSTREAM_RECORD_FIELDS)) return fail(malformed$1(`expected exactly ${UPSTREAM_RECORD_FIELDS.join(", ")}`));
442
+ const invalidHash = ["owner", "stakeScriptHash"].find((field) => !isHash28Hex(value[field]));
443
+ if (invalidHash) return fail(malformed$1("expected 28 bytes of lower-case hex", invalidHash));
444
+ const address = parseAddress(value.address);
445
+ const payment = address?.asBase()?.getPaymentCredential();
446
+ if (!address || payment?.type !== Cardano.CredentialType.ScriptHash) return fail(malformed$1("expected a script base address", "address"));
447
+ return ok({
448
+ networkMagic: selectedNetworkMagic,
449
+ accountValidatorHash: payment.hash,
450
+ creatorKeyHash: value.owner,
451
+ stakeScriptHash: value.stakeScriptHash,
452
+ address,
453
+ addressText: value.address
454
+ });
455
+ };
456
+ const readClaims = (input, selectedNetworkMagic) => {
457
+ const value = parseJson(input);
458
+ if (!isPlainObject$1(value)) return fail(malformed$1("expected a JSON object"));
459
+ return "owner" in value ? readUpstreamRecordClaims(value, selectedNetworkMagic) : readRecordClaims(value);
460
+ };
461
+ /**
462
+ * Validates an imported record, in order: (1) known type, version and field
463
+ * shapes; (2) a supported contract version; (3) the stake script hash
464
+ * re-derived from the creator key hash; (4) the address re-derived for the
465
+ * record's network, byte for byte; (5) the record's network is the selected
466
+ * one. Step 3 is what proves the record names a genuine custody account: the
467
+ * mint accepts any stake script.
468
+ *
469
+ * Also accepts the upstream `{ owner, stakeScriptHash, address }` record,
470
+ * taking the network from `selectedNetworkMagic`.
471
+ *
472
+ * @param input - the record as JSON text or as an already parsed value
473
+ */
474
+ const parseCustodyAccountRecord = (input, selectedNetworkMagic) => {
475
+ const claims = readClaims(input, selectedNetworkMagic);
476
+ if (!claims.ok) return claims;
477
+ const { networkMagic, accountValidatorHash, creatorKeyHash, stakeScriptHash, address, addressText } = claims.value;
478
+ const contractVersion = findCustodyContractVersion(accountValidatorHash);
479
+ if (!contractVersion.ok) return fail({
480
+ step: 2,
481
+ ...contractVersion.error
482
+ });
483
+ const identity = identityOfValidatedCreator({
484
+ contractVersion: contractVersion.value,
485
+ creatorKeyHash,
486
+ chainId: chainIdFromNetworkMagic(networkMagic)
487
+ });
488
+ if (identity.stakeScriptHash !== stakeScriptHash) return fail({
489
+ step: 3,
490
+ code: "stake-script-hash-mismatch",
491
+ expected: identity.stakeScriptHash,
492
+ actual: stakeScriptHash
493
+ });
494
+ const derivedAddress = identity.address;
495
+ if (Cardano.Address.fromBech32(derivedAddress).toBytes() !== address.toBytes()) return fail({
496
+ step: 4,
497
+ code: "address-mismatch",
498
+ expected: derivedAddress,
499
+ actual: addressText
500
+ });
501
+ if (networkMagic !== selectedNetworkMagic) return fail({
502
+ step: 5,
503
+ code: "network-mismatch",
504
+ expected: selectedNetworkMagic,
505
+ actual: networkMagic
506
+ });
507
+ return ok({
508
+ record: createCustodyAccountRecord(identity),
509
+ identity
510
+ });
511
+ };
512
+ //#endregion
513
+ //#region ../../packages/lib/cardano-custody/src/codecs/redeemers.ts
514
+ /** Constructor order of `AccountRedeemer`; no constructor has fields. */
515
+ const ACCOUNT_REDEEMER_KINDS = [
516
+ "device",
517
+ "spendWithGrant",
518
+ "sweepGrant",
519
+ "fund"
520
+ ];
521
+ /** Constructor order of `MintRedeemer`; no constructor has fields. */
522
+ const MINT_REDEEMER_KINDS = [
523
+ "createAccount",
524
+ "issueGrants",
525
+ "burnGrants"
526
+ ];
527
+ /** Decodes a union whose constructors all have no fields. */
528
+ const decodeFieldless = (data, kinds, what) => decodeResult(() => {
529
+ const index = expectConstrIndex(data, "", what);
530
+ const kind = kinds[Number(index)];
531
+ if (kind === void 0) return unknownAlternative("", `${what} of ${kinds.join(", ")}`, index);
532
+ expectConstr(data, {
533
+ index: Number(index),
534
+ arity: 0
535
+ }, {
536
+ path: "",
537
+ what
538
+ });
539
+ return { kind };
540
+ });
541
+ /** `Device` is constructor 0, `SpendWithGrant` 1, `SweepGrant` 2 and `Fund` 3. */
542
+ const encodeCustodyAccountRedeemer = ({ kind }) => constr(ACCOUNT_REDEEMER_KINDS.indexOf(kind));
543
+ const decodeCustodyAccountRedeemer = (data) => decodeFieldless(data, ACCOUNT_REDEEMER_KINDS, "an account redeemer");
544
+ /** `CreateAccount` is constructor 0, `IssueGrants` 1 and `BurnGrants` 2. */
545
+ const encodeCustodyMintRedeemer = ({ kind }) => constr(MINT_REDEEMER_KINDS.indexOf(kind));
546
+ const decodeCustodyMintRedeemer = (data) => decodeFieldless(data, MINT_REDEEMER_KINDS, "a mint redeemer");
547
+ /** `Operate`: constructor 0 with no fields. */
548
+ const encodeCustodyStakeRedeemer = () => constr(0);
549
+ /**
550
+ * `Run`, the redeemer of a logic script's withdraw and publish handlers:
551
+ * constructor 0 with no fields.
552
+ */
553
+ const encodeCustodyLogicRedeemer = () => constr(0);
554
+ //#endregion
555
+ //#region ../../packages/lib/cardano-custody/src/codecs/reserve.ts
556
+ /** The datum the upstream library writes on a reserve: constructor 0 with no fields. */
557
+ const RESERVE_DATUM_CBOR = "d87980";
558
+ /**
559
+ * Whether an inline datum is, byte for byte, the reserve datum `d87980`.
560
+ * Any datum makes a deposit an owner-only reserve; only this one has a known
561
+ * size, so a builder can spend such a reserve without a surprise in fee or
562
+ * minimum lovelace.
563
+ */
564
+ const isCustodyReserveDatum = (datum) => Serialization.PlutusData.fromCore(datum).toCbor() === RESERVE_DATUM_CBOR;
565
+ //#endregion
566
+ //#region ../../packages/lib/cardano-custody/src/analysis/fee-shares.ts
567
+ const lovelaceOf = (items) => items.reduce((total, { value }) => total + value.coins, 0n);
568
+ const positivePart = (quantity) => quantity > 0n ? quantity : 0n;
569
+ /** How the fee splits between the external inputs and the operated accounts. */
570
+ const feeSharesOf = ({ externalInputs, externalOutputs, fee }) => {
571
+ const payers = new Set(externalInputs.map(({ address }) => address));
572
+ const beyondChange = lovelaceOf(externalInputs) - lovelaceOf(externalOutputs.filter(({ address }) => payers.has(address)));
573
+ const externalShare = beyondChange < fee ? positivePart(beyondChange) : fee;
574
+ return {
575
+ payers: [...payers],
576
+ externalShare,
577
+ accountShare: fee - externalShare
578
+ };
579
+ };
580
+ /**
581
+ * The part of the accounts' fee share each operated account pays, from the
582
+ * lovelace each gives up. Each pays only what the others cannot cover, so an
583
+ * account is named as paying someone whenever some split of the fee has it
584
+ * do so. A single account pays the whole share.
585
+ */
586
+ const accountFeeSharesOf = (givenUp, accountShare) => {
587
+ const total = givenUp.reduce((sum, lovelace) => sum + positivePart(lovelace), 0n);
588
+ return givenUp.map((lovelace) => positivePart(accountShare - (total - positivePart(lovelace))));
589
+ };
590
+ //#endregion
591
+ //#region ../../packages/lib/cardano-custody/src/analysis/state-change.ts
592
+ const isSameOrder = (before, after) => {
593
+ const kept = before.filter((item) => after.includes(item));
594
+ const keptAfter = after.filter((item) => before.includes(item));
595
+ return kept.every((item, index) => item === keptAfter[index]);
596
+ };
597
+ const counterChange = (before, after) => before === after ? void 0 : {
598
+ before,
599
+ after
600
+ };
601
+ const diffCustodyStates = (before, after) => {
602
+ const generation = counterChange(before.grantGeneration, after.grantGeneration);
603
+ const nextSlot = counterChange(before.nextSlot, after.nextSlot);
604
+ const outstanding = counterChange(before.outstanding, after.outstanding);
605
+ return {
606
+ before,
607
+ after,
608
+ ...before.logic === after.logic ? {} : { logic: {
609
+ before: before.logic,
610
+ after: after.logic
611
+ } },
612
+ devicesAdded: after.devices.filter((device) => !before.devices.includes(device)),
613
+ devicesRemoved: before.devices.filter((device) => !after.devices.includes(device)),
614
+ revokedAdded: after.revoked.filter((slot) => !before.revoked.includes(slot)),
615
+ revokedRemoved: before.revoked.filter((slot) => !after.revoked.includes(slot)),
616
+ ...generation ? { grantGeneration: generation } : {},
617
+ ...nextSlot ? { nextSlot } : {},
618
+ ...outstanding ? { outstanding } : {},
619
+ isReordered: !isSameOrder(before.devices, after.devices) || !isSameOrder(before.revoked, after.revoked)
620
+ };
621
+ };
622
+ //#endregion
623
+ //#region ../../packages/lib/cardano-custody/src/analysis/account-inspection.ts
624
+ const accountKey = (account) => `${account.accountValidatorHash}${account.stakeScriptHash}`;
625
+ const hashesOf = (account) => ({
626
+ accountValidatorHash: account.accountValidatorHash,
627
+ stakeScriptHash: account.stakeScriptHash
628
+ });
629
+ const versionOfPolicy = (policyId) => SUPPORTED_CUSTODY_CONTRACT_VERSIONS.find((version) => version.accountValidatorHash === policyId);
630
+ /**
631
+ * Every custody account the transaction names: by an input, reference input
632
+ * or output at its address, by a mint of one of its account tokens, or by
633
+ * the record.
634
+ */
635
+ const findCandidates = (facts, record) => {
636
+ const candidates = /* @__PURE__ */ new Map();
637
+ const add = (candidate) => candidates.set(accountKey(candidate), candidate);
638
+ const views = [
639
+ ...facts.inputs.map(({ output }) => output),
640
+ ...facts.referenceInputs.map(({ output }) => output),
641
+ ...facts.outputs
642
+ ];
643
+ for (const { addressClass } of views) if (addressClass.kind === "account") add({
644
+ contractVersion: addressClass.contractVersion,
645
+ accountValidatorHash: addressClass.contractVersion.accountValidatorHash,
646
+ stakeScriptHash: addressClass.stakeScriptHash
647
+ });
648
+ for (const assetId of facts.mint.keys()) {
649
+ const contractVersion = versionOfPolicy(policyIdOf(assetId));
650
+ const name = parseCustodyAccountTokenName(assetNameOf(assetId));
651
+ if (contractVersion && name) add({
652
+ contractVersion,
653
+ accountValidatorHash: contractVersion.accountValidatorHash,
654
+ stakeScriptHash: name.stakeScriptHash
655
+ });
656
+ }
657
+ if (record) add({
658
+ contractVersion: record.contractVersion,
659
+ accountValidatorHash: record.contractVersion.accountValidatorHash,
660
+ stakeScriptHash: record.stakeScriptHash
661
+ });
662
+ return [...candidates.values()];
663
+ };
664
+ const VOTE_DELEGATIONS = new Set([
665
+ Cardano.CertificateType.StakeRegistrationDelegation,
666
+ Cardano.CertificateType.StakeVoteDelegation,
667
+ Cardano.CertificateType.StakeVoteRegistrationDelegation,
668
+ Cardano.CertificateType.VoteDelegation,
669
+ Cardano.CertificateType.VoteRegistrationDelegation
670
+ ]);
671
+ /**
672
+ * The certificates on the account's stake credential and the withdrawal
673
+ * from its reward account, apart from the logic run, which withdraws from
674
+ * the logic's credential. Registration with a deposit and pool delegation
675
+ * are what Lace builds; vote and combined delegations are valid on chain
676
+ * but unsupported, and every other certificate is refused.
677
+ */
678
+ const readStakeEffects = (account, facts) => {
679
+ const refusals = [];
680
+ const certificates = [];
681
+ let isStaked = false;
682
+ for (const [certificateIndex, certificate] of (facts.body.certificates ?? []).entries()) {
683
+ const credential = stakeCredentialOf(certificate);
684
+ if (credential?.type !== Cardano.CredentialType.ScriptHash || credential.hash !== account.stakeScriptHash) continue;
685
+ isStaked = true;
686
+ if (certificate.__typename === Cardano.CertificateType.Registration) certificates.push({
687
+ certificateIndex,
688
+ kind: "registration",
689
+ deposit: certificate.deposit
690
+ });
691
+ else if (certificate.__typename === Cardano.CertificateType.StakeDelegation) certificates.push({
692
+ certificateIndex,
693
+ kind: "pool-delegation",
694
+ poolId: certificate.poolId
695
+ });
696
+ else refusals.push({
697
+ code: "custody-unsupported-operation",
698
+ reason: VOTE_DELEGATIONS.has(certificate.__typename) ? "unsupported-certificate" : "refused-certificate",
699
+ certificateIndex,
700
+ certificateType: certificate.__typename
701
+ });
702
+ }
703
+ const withdrawal = (facts.body.withdrawals ?? []).find(({ stakeAddress }) => scriptHashOfRewardAccount(stakeAddress) === account.stakeScriptHash);
704
+ return {
705
+ certificates,
706
+ withdrawal: withdrawal?.quantity,
707
+ refusals,
708
+ isStaked: isStaked || withdrawal !== void 0
709
+ };
710
+ };
711
+ /**
712
+ * The run of the logic the account operates under: the logic its spent
713
+ * control UTxO names, the referenced one for a grant spend, or the control
714
+ * output's at creation. The proxy requires a withdrawal from that logic's
715
+ * stake credential, which makes the ledger run it. The decoded body keeps
716
+ * one withdrawal per reward account; a repeat in the bytes is refused.
717
+ */
718
+ const readLogicRun = (account, facts, logic) => {
719
+ if (logic === void 0) return { refusals: [] };
720
+ const withdrawal = (facts.body.withdrawals ?? []).find(({ stakeAddress }) => scriptHashOfRewardAccount(stakeAddress) === logic);
721
+ return withdrawal ? {
722
+ run: {
723
+ logic,
724
+ draw: withdrawal.quantity
725
+ },
726
+ refusals: []
727
+ } : { refusals: [{
728
+ code: "custody-unsupported-operation",
729
+ reason: "logic-withdrawal-missing",
730
+ account,
731
+ logic
732
+ }] };
733
+ };
734
+ /**
735
+ * A device whose key hash is not 28 bytes can never sign, and the contract
736
+ * does not check key lengths, so an on-chain state may keep one. Lace never
737
+ * adds one, but a device can still remove it in a rewrite that keeps it
738
+ * meanwhile.
739
+ */
740
+ const isCarriedOver = (violation, before) => before !== void 0 && violation.code === "invalid-device-key-hash" && before.devices.includes(violation.device);
741
+ /** The authoring checks a state written by a device or a creation must pass. */
742
+ const checkWrittenState = ({ account, before, after }) => findCustodyStateViolations(after).filter((violation) => !isCarriedOver(violation, before)).map((violation) => ({
743
+ code: "custody-malformed-datum",
744
+ reason: "state-violation",
745
+ account,
746
+ violation
747
+ }));
748
+ const isSameAccount = (account, record) => record !== void 0 && record.contractVersion.accountValidatorHash === account.accountValidatorHash && record.stakeScriptHash === account.stakeScriptHash;
749
+ /**
750
+ * Lace creates only the creator-only state: the current logic of the
751
+ * account's contract version, the creator as the single device, generation
752
+ * zero and no grant issued, revoked or outstanding. The logic also accepts
753
+ * any generation at creation.
754
+ */
755
+ const isCreatorOnly = (state, creator, logic) => state.logic === logic && state.devices.length === 1 && state.devices[0] === creator && state.grantGeneration === 0n && state.nextSlot === 0n && state.revoked.length === 0 && state.outstanding === 0n;
756
+ /**
757
+ * At creation the stake validator needs the creator's signature, and Lace
758
+ * creates only the creator-only state; otherwise a device listed in the
759
+ * spent state must sign.
760
+ */
761
+ const checkSigner = ({ account, before, after }, facts, scope) => {
762
+ const { deviceKeyHash } = scope;
763
+ const refusals = [];
764
+ const notSigning = (keyHash) => facts.requiredSigners.has(keyHash) ? [] : [{
765
+ code: "custody-device-not-listed",
766
+ reason: "not-required-signer",
767
+ account,
768
+ keyHash
769
+ }];
770
+ if (!before) {
771
+ if (!isSameAccount(account, scope.record)) return scope.record ? [] : [{
772
+ code: "custody-context-mismatch",
773
+ reason: "creation-without-record",
774
+ account
775
+ }];
776
+ const creator = scope.record.creatorKeyHash;
777
+ if (deviceKeyHash !== void 0 && deviceKeyHash !== creator) refusals.push({
778
+ code: "custody-device-not-listed",
779
+ reason: "not-creator",
780
+ account,
781
+ keyHash: deviceKeyHash
782
+ });
783
+ refusals.push(...notSigning(creator));
784
+ if (!isCreatorOnly(after, creator, scope.record.contractVersion.currentLogicHash)) refusals.push({
785
+ code: "custody-context-mismatch",
786
+ reason: "initial-state",
787
+ account
788
+ });
789
+ return refusals;
790
+ }
791
+ if (deviceKeyHash === void 0) return before.devices.some((device) => facts.requiredSigners.has(device)) ? [] : [{
792
+ code: "custody-device-not-listed",
793
+ reason: "no-device-signer",
794
+ account
795
+ }];
796
+ if (!before.devices.includes(deviceKeyHash)) refusals.push({
797
+ code: "custody-device-not-listed",
798
+ reason: "not-listed",
799
+ account,
800
+ keyHash: deviceKeyHash
801
+ });
802
+ return [...refusals, ...notSigning(deviceKeyHash)];
803
+ };
804
+ /** The redeemers the contract lets spend each kind of account UTxO. */
805
+ const REDEEMERS_OF = {
806
+ control: ["device"],
807
+ grant: ["spendWithGrant", "sweepGrant"],
808
+ reserve: ["fund"],
809
+ fund: ["fund"]
810
+ };
811
+ /**
812
+ * The account inputs by kind, each with its spend redeemer, which must fit
813
+ * the UTxO it spends. A plain deposit rides on any account token spent
814
+ * beside it; a reserve needs the control UTxO, so only a device spends it.
815
+ */
816
+ const readAccountInputs = (account, facts) => {
817
+ const refusals = [];
818
+ const spent = facts.inputs.flatMap((view, redeemerIndex) => {
819
+ if (!isAtAccount(view.output, account)) return [];
820
+ const kind = accountUtxoKindOf(view.output, account);
821
+ const data = redeemerData(facts, Cardano.RedeemerPurpose.spend, redeemerIndex);
822
+ const decoded = data === void 0 ? void 0 : decodeCustodyAccountRedeemer(data);
823
+ if (!decoded?.ok) {
824
+ refusals.push({
825
+ code: "custody-unsupported-operation",
826
+ reason: decoded ? "unknown-redeemer" : "missing-redeemer",
827
+ purpose: "spend",
828
+ index: redeemerIndex
829
+ });
830
+ return [{
831
+ ...view,
832
+ kind,
833
+ redeemer: void 0
834
+ }];
835
+ }
836
+ if (!REDEEMERS_OF[kind].includes(decoded.value.kind)) refusals.push({
837
+ code: "custody-context-mismatch",
838
+ reason: "redeemer-mismatch",
839
+ input: view.input,
840
+ redeemer: decoded.value.kind
841
+ });
842
+ return [{
843
+ ...view,
844
+ kind,
845
+ redeemer: decoded.value
846
+ }];
847
+ });
848
+ const ofKind = (kind) => spent.filter((input) => input.kind === kind);
849
+ const controls = ofKind("control");
850
+ const grants = ofKind("grant");
851
+ const reserves = ofKind("reserve");
852
+ const funds = ofKind("fund");
853
+ if (controls.length === 0) refusals.push(...reserves.map(({ input }) => ({
854
+ code: "custody-unsupported-operation",
855
+ reason: "reserve-without-control",
856
+ input
857
+ })));
858
+ if (funds.length > 0 && controls.length + grants.length === 0) refusals.push({
859
+ code: "custody-unsupported-operation",
860
+ reason: "fund-without-account-token",
861
+ account
862
+ });
863
+ return {
864
+ controls,
865
+ spends: grants.filter(({ redeemer }) => redeemer?.kind === "spendWithGrant"),
866
+ sweeps: grants.filter(({ redeemer }) => redeemer?.kind === "sweepGrant"),
867
+ reserves,
868
+ funds,
869
+ isSpent: spent.length > 0,
870
+ refusals
871
+ };
872
+ };
873
+ const bySlot$1 = (a, b) => a.slot < b.slot ? -1 : a.slot > b.slot ? 1 : 0;
874
+ const mintShapeOf = (entries) => {
875
+ const names = entries.map(([assetId, quantity]) => ({
876
+ name: parseCustodyAccountTokenName(assetNameOf(assetId)),
877
+ quantity
878
+ }));
879
+ const [first] = names;
880
+ if (names.length === 1 && first.name?.kind === "state-nft") return {
881
+ kind: "creation",
882
+ stakeScriptHash: first.name.stakeScriptHash
883
+ };
884
+ const stakeScriptHash = first?.name?.stakeScriptHash;
885
+ if (!(stakeScriptHash !== void 0 && names.every(({ name, quantity }) => name?.kind === "grant" && name.stakeScriptHash === stakeScriptHash && (quantity === 1n || quantity === -1n)))) return { kind: "invalid" };
886
+ const issues = names.filter(({ quantity }) => quantity === 1n).length;
887
+ return {
888
+ kind: issues === names.length ? "issue" : issues === 0 ? "burn" : "mixed",
889
+ stakeScriptHash
890
+ };
891
+ };
892
+ /**
893
+ * The account's mint: its state NFT at creation, grant tokens issued at +1
894
+ * or burned at -1, under the one mint redeemer of the policy. A device
895
+ * spend of the control UTxO counts only the account's own grant tokens, so
896
+ * any other name under the policy beside it is refused. A mint of no valid
897
+ * shape is the transaction checks' to refuse.
898
+ */
899
+ const readMint = (account, facts, hasControlInput) => {
900
+ const refusals = [];
901
+ const policy = account.accountValidatorHash;
902
+ const nftAssetId = stateNftAssetIdOf(account);
903
+ const entries = [...facts.mint].filter(([assetId]) => policyIdOf(assetId) === policy);
904
+ const shape = mintShapeOf(entries);
905
+ const isOwnGrantMint = shape.kind !== "invalid" && shape.kind !== "creation" && shape.stakeScriptHash === account.stakeScriptHash;
906
+ const issued = [];
907
+ const burned = [];
908
+ for (const [assetId, quantity] of entries) {
909
+ const name = parseCustodyAccountTokenName(assetNameOf(assetId));
910
+ if (isOwnGrantMint && name?.kind === "grant") (quantity === 1n ? issued : burned).push({
911
+ slot: name.slot,
912
+ assetId
913
+ });
914
+ else if (shape.kind !== "invalid" && shape.kind !== "mixed" && (hasControlInput || assetId === nftAssetId && quantity !== 1n)) refusals.push({
915
+ code: "custody-unsupported-operation",
916
+ reason: "unsupported-mint",
917
+ assetId,
918
+ quantity
919
+ });
920
+ }
921
+ issued.sort(bySlot$1);
922
+ burned.sort(bySlot$1);
923
+ const isCreation = facts.mint.has(nftAssetId);
924
+ if (isCreation || isOwnGrantMint) {
925
+ const index = facts.mintPolicies.indexOf(policy);
926
+ const data = redeemerData(facts, Cardano.RedeemerPurpose.mint, index);
927
+ const decoded = data === void 0 ? void 0 : decodeCustodyMintRedeemer(data);
928
+ const expected = {
929
+ creation: "createAccount",
930
+ issue: "issueGrants",
931
+ burn: "burnGrants"
932
+ };
933
+ if (!decoded?.ok) refusals.push({
934
+ code: "custody-unsupported-operation",
935
+ reason: decoded ? "unknown-redeemer" : "missing-redeemer",
936
+ purpose: "mint",
937
+ index
938
+ });
939
+ else if (expected[shape.kind] !== void 0 && decoded.value.kind !== expected[shape.kind]) refusals.push({
940
+ code: "custody-unsupported-operation",
941
+ reason: "mint-redeemer-mismatch",
942
+ account,
943
+ redeemer: decoded.value.kind
944
+ });
945
+ }
946
+ if (isOwnGrantMint && !hasControlInput) refusals.push({
947
+ code: "custody-unsupported-operation",
948
+ reason: "grant-mint-without-control",
949
+ account
950
+ });
951
+ for (const count of [issued.length, burned.length]) if (count > 8) refusals.push({
952
+ code: "custody-unsupported-operation",
953
+ reason: "grant-batch-limit",
954
+ account,
955
+ count,
956
+ limit: 8
957
+ });
958
+ return {
959
+ isCreation,
960
+ issued,
961
+ burned,
962
+ refusals
963
+ };
964
+ };
965
+ /** The control input's checks: one control UTxO whose resolution holds a state. */
966
+ const readControlInput = (account, inputs) => {
967
+ const refusals = [];
968
+ const [control] = inputs.controls;
969
+ if (inputs.controls.length > 1) refusals.push({
970
+ code: "custody-context-mismatch",
971
+ reason: "multiple-control-inputs",
972
+ account
973
+ });
974
+ const before = control && decodedInlineState(control.output);
975
+ if (control) {
976
+ if (!before) refusals.push({
977
+ code: "custody-malformed-datum",
978
+ reason: "control-input-datum",
979
+ input: control.input
980
+ });
981
+ if (!holdsOnlyLovelaceAndStateNft(control.output.value, account)) refusals.push({
982
+ code: "custody-malformed-datum",
983
+ reason: "control-input-value",
984
+ input: control.input
985
+ });
986
+ }
987
+ return {
988
+ control,
989
+ before,
990
+ refusals
991
+ };
992
+ };
993
+ /**
994
+ * The account's control UTxO among the reference inputs. Only a grant spend
995
+ * reads the control UTxO without spending it: Lace spends it for every
996
+ * owner operation, delegation and withdrawal included.
997
+ */
998
+ const readReferencedControls = (account, facts, isGrantSpend) => {
999
+ const references = facts.referenceInputs.filter(({ output }) => isControlOutput(output, account));
1000
+ return {
1001
+ references,
1002
+ refusals: isGrantSpend ? [] : references.map(({ input }) => ({
1003
+ code: "custody-unsupported-operation",
1004
+ reason: "reference-control",
1005
+ input
1006
+ }))
1007
+ };
1008
+ };
1009
+ /**
1010
+ * Registration belongs to creation and creation needs it; delegation and
1011
+ * withdrawal need the control UTxO spent, since Lace never references it
1012
+ * for them.
1013
+ */
1014
+ const checkStakeOperations = (account, { certificates, withdrawal }, { isCreation, hasControlInput }) => {
1015
+ const refusals = [];
1016
+ const isRegistered = certificates.some(({ kind }) => kind === "registration");
1017
+ if (isRegistered && !isCreation) refusals.push({
1018
+ code: "custody-unsupported-operation",
1019
+ reason: "registration-without-creation",
1020
+ account
1021
+ });
1022
+ if (isCreation && !isRegistered) refusals.push({
1023
+ code: "custody-unsupported-operation",
1024
+ reason: "creation-without-registration",
1025
+ account
1026
+ });
1027
+ if ((withdrawal !== void 0 || certificates.some(({ kind }) => kind === "pool-delegation")) && !hasControlInput) refusals.push({
1028
+ code: "custody-unsupported-operation",
1029
+ reason: "stake-operation-without-control",
1030
+ account
1031
+ });
1032
+ return refusals;
1033
+ };
1034
+ /**
1035
+ * The control output's checks. With a control input or a creation there is
1036
+ * exactly one, holding only lovelace and the NFT and an account state, with
1037
+ * no reference script, which the proxy refuses there; without either there
1038
+ * is none.
1039
+ */
1040
+ const readControlOutput = (account, facts, hasStateSource) => {
1041
+ const refusals = [];
1042
+ const controls = facts.outputs.flatMap((view, outputIndex) => isControlOutput(view, account) ? [{
1043
+ view,
1044
+ outputIndex
1045
+ }] : []);
1046
+ if (!hasStateSource && controls.length > 0) refusals.push({
1047
+ code: "custody-context-mismatch",
1048
+ reason: "control-output-without-control-input",
1049
+ account
1050
+ });
1051
+ if (hasStateSource && controls.length !== 1) refusals.push({
1052
+ code: "custody-malformed-datum",
1053
+ reason: "control-output-count",
1054
+ account,
1055
+ count: controls.length
1056
+ });
1057
+ const control = controls.length === 1 ? controls[0] : void 0;
1058
+ const after = control && decodedInlineState(control.view);
1059
+ if (hasStateSource && control) {
1060
+ if (!holdsOnlyLovelaceAndStateNft(control.view.value, account)) refusals.push({
1061
+ code: "custody-malformed-datum",
1062
+ reason: "control-output-value",
1063
+ outputIndex: control.outputIndex
1064
+ });
1065
+ if (!after) refusals.push({
1066
+ code: "custody-malformed-datum",
1067
+ reason: "control-output-datum",
1068
+ outputIndex: control.outputIndex
1069
+ });
1070
+ if (control.view.hasReferenceScript) refusals.push({
1071
+ code: "custody-unsupported-operation",
1072
+ reason: "control-output-reference-script",
1073
+ outputIndex: control.outputIndex
1074
+ });
1075
+ }
1076
+ return {
1077
+ count: controls.length,
1078
+ control,
1079
+ after,
1080
+ refusals
1081
+ };
1082
+ };
1083
+ /** What one issued grant breaks of `issues_grant`, or its output and grant. */
1084
+ const readIssuedGrant = (account, facts, { assetId }) => {
1085
+ const holders = facts.outputs.flatMap((view, outputIndex) => view.value.assets.get(assetId) === 1n ? [{
1086
+ view,
1087
+ outputIndex
1088
+ }] : []);
1089
+ if (holders.length !== 1) return { problem: {
1090
+ code: "output-count",
1091
+ count: holders.length
1092
+ } };
1093
+ const [{ view, outputIndex }] = holders;
1094
+ if (!isAtAccount(view, account)) return { problem: {
1095
+ code: "address",
1096
+ outputIndex
1097
+ } };
1098
+ if (view.value.assets.size !== 1) return { problem: {
1099
+ code: "value",
1100
+ outputIndex
1101
+ } };
1102
+ if (view.hasReferenceScript) return { problem: {
1103
+ code: "reference-script",
1104
+ outputIndex
1105
+ } };
1106
+ if (view.inlineDatum === void 0) return { problem: {
1107
+ code: "datum-not-inline",
1108
+ outputIndex
1109
+ } };
1110
+ const decoded = decodeCustodyGrant(view.inlineDatum);
1111
+ return decoded.ok ? {
1112
+ view,
1113
+ outputIndex,
1114
+ grant: decoded.value
1115
+ } : { problem: {
1116
+ code: "datum-malformed",
1117
+ outputIndex
1118
+ } };
1119
+ };
1120
+ /**
1121
+ * The grants a device rewrite issues: `issues_grant` for each minted token,
1122
+ * in slot order from the spent state's next slot, each under the generation
1123
+ * of the control output, plus the rules Lace applies to every grant it
1124
+ * authors and an expiry after `now`.
1125
+ */
1126
+ const readIssuedGrants = (account, facts, { issued, before, after, now }) => {
1127
+ const refusals = [];
1128
+ const reports = [];
1129
+ const refuse = (slot, problem) => refusals.push({
1130
+ code: "custody-malformed-datum",
1131
+ reason: "grant-output-invalid",
1132
+ account,
1133
+ slot,
1134
+ problem
1135
+ });
1136
+ for (const [index, mint] of issued.entries()) {
1137
+ const { slot } = mint;
1138
+ if (before) {
1139
+ const expected = before.nextSlot + BigInt(index);
1140
+ if (slot !== expected) refuse(slot, {
1141
+ code: "not-next-slot",
1142
+ expected
1143
+ });
1144
+ }
1145
+ const read = readIssuedGrant(account, facts, mint);
1146
+ if ("problem" in read) {
1147
+ refuse(slot, read.problem);
1148
+ continue;
1149
+ }
1150
+ const { view, outputIndex, grant } = read;
1151
+ if (grant.slot !== slot) refuse(slot, {
1152
+ code: "slot-mismatch",
1153
+ outputIndex,
1154
+ datumSlot: grant.slot
1155
+ });
1156
+ if (after && grant.generation !== after.grantGeneration) refuse(slot, {
1157
+ code: "generation-mismatch",
1158
+ outputIndex,
1159
+ generation: grant.generation,
1160
+ expected: after.grantGeneration
1161
+ });
1162
+ refusals.push(...findCustodyGrantViolations(grant).map((violation) => ({
1163
+ code: "custody-malformed-datum",
1164
+ reason: "grant-violation",
1165
+ account,
1166
+ slot,
1167
+ violation
1168
+ })));
1169
+ if (isCustodyGrantExpired(grant, now)) refusals.push({
1170
+ code: "custody-malformed-datum",
1171
+ reason: "expired-grant",
1172
+ account,
1173
+ slot,
1174
+ expiresAt: grant.scope.expiresAt
1175
+ });
1176
+ reports.push({
1177
+ outputIndex,
1178
+ assetId: mint.assetId,
1179
+ grant,
1180
+ lovelace: view.value.lovelace
1181
+ });
1182
+ }
1183
+ return {
1184
+ reports,
1185
+ refusals
1186
+ };
1187
+ };
1188
+ /**
1189
+ * `is_dead` against the spent state: issued under an older generation, its
1190
+ * slot revoked, or the validity interval starting strictly after its expiry.
1191
+ */
1192
+ const deathOf = (grant, state, lowerBoundMs) => {
1193
+ if (grant.generation < state.grantGeneration) return "superseded";
1194
+ if (state.revoked.includes(grant.slot)) return "revoked";
1195
+ return lowerBoundMs !== void 0 && Number.isSafeInteger(lowerBoundMs) && BigInt(lowerBoundMs) > grant.scope.expiresAt ? "expired" : void 0;
1196
+ };
1197
+ /**
1198
+ * The grants a device rewrite sweeps: each grant UTxO spent with
1199
+ * `SweepGrant` carries its grant inline beside only its token, is dead
1200
+ * against the spent state, and has its token burned; every burned token is
1201
+ * a swept grant's.
1202
+ */
1203
+ const readSweptGrants = (account, facts, { sweeps, burned, before, hasControlInput }) => {
1204
+ const refusals = [];
1205
+ const reports = [];
1206
+ if (sweeps.length > 0 && !hasControlInput) refusals.push({
1207
+ code: "custody-unsupported-operation",
1208
+ reason: "sweep-without-control",
1209
+ account
1210
+ });
1211
+ for (const { input, output } of sweeps) {
1212
+ const grant = decodedInlineGrant(output);
1213
+ if (!grant) {
1214
+ refusals.push({
1215
+ code: "custody-malformed-datum",
1216
+ reason: "grant-input-datum",
1217
+ input
1218
+ });
1219
+ continue;
1220
+ }
1221
+ const assetId = grantAssetIdOf(account, grant.slot);
1222
+ if (!assetId || !holdsOnlyLovelaceAndGrantToken(output.value, account, grant.slot)) {
1223
+ refusals.push({
1224
+ code: "custody-malformed-datum",
1225
+ reason: "grant-input-value",
1226
+ input
1227
+ });
1228
+ continue;
1229
+ }
1230
+ if (facts.mint.get(assetId) !== -1n) refusals.push({
1231
+ code: "custody-unsupported-operation",
1232
+ reason: "sweep-burn-mismatch",
1233
+ account,
1234
+ slot: grant.slot
1235
+ });
1236
+ if (!before) continue;
1237
+ const death = deathOf(grant, before, facts.lowerBoundMs);
1238
+ if (!death) {
1239
+ refusals.push({
1240
+ code: "custody-unsupported-operation",
1241
+ reason: "sweep-not-dead",
1242
+ account,
1243
+ input,
1244
+ slot: grant.slot
1245
+ });
1246
+ continue;
1247
+ }
1248
+ reports.push({
1249
+ input,
1250
+ assetId,
1251
+ grant,
1252
+ lovelace: output.value.lovelace,
1253
+ death
1254
+ });
1255
+ }
1256
+ const sweptSlots = sweeps.flatMap(({ output }) => {
1257
+ const grant = decodedInlineGrant(output);
1258
+ return grant ? [grant.slot] : [];
1259
+ });
1260
+ for (const { slot } of burned) if (!sweptSlots.includes(slot)) refusals.push({
1261
+ code: "custody-unsupported-operation",
1262
+ reason: "sweep-burn-mismatch",
1263
+ account,
1264
+ slot
1265
+ });
1266
+ return {
1267
+ reports,
1268
+ refusals
1269
+ };
1270
+ };
1271
+ /**
1272
+ * Every control state the transaction spends, references or writes for the
1273
+ * account names a logic of its contract version's catalogue: the logic is
1274
+ * the account's rule set, and any registered script may be named there, so
1275
+ * an unknown one runs rules Lace has not reviewed. Each unknown logic is
1276
+ * refused once.
1277
+ */
1278
+ const checkKnownLogics = (account, version, states) => {
1279
+ const unknown = /* @__PURE__ */ new Set();
1280
+ for (const state of states) if (state && !findCustodyLogic(version, state.logic).ok) unknown.add(state.logic);
1281
+ return [...unknown].map((logic) => ({
1282
+ code: "custody-unsupported-version",
1283
+ reason: "unknown-logic",
1284
+ account,
1285
+ logic
1286
+ }));
1287
+ };
1288
+ /**
1289
+ * The counter rules of `recreates_control_output`, and the rules Lace adds:
1290
+ * the logic stays, since Lace does not sign an upgrade; a slot leaves the
1291
+ * revoked list only with its grant token burned in the same rewrite, or
1292
+ * with a generation step that supersedes every grant; a slot joins it only
1293
+ * once, and only after it was issued.
1294
+ */
1295
+ const checkStateChange = (account, change, { issued, burned }) => {
1296
+ const { before, after } = change;
1297
+ const refusals = [];
1298
+ if (change.logic) refusals.push({
1299
+ code: "custody-unsupported-operation",
1300
+ reason: "logic-changed",
1301
+ account,
1302
+ before: change.logic.before,
1303
+ after: change.logic.after
1304
+ });
1305
+ if (after.grantGeneration < before.grantGeneration) refusals.push({
1306
+ code: "custody-malformed-datum",
1307
+ reason: "generation-decreased",
1308
+ account,
1309
+ before: before.grantGeneration,
1310
+ after: after.grantGeneration
1311
+ });
1312
+ const minted = BigInt(issued.length);
1313
+ const counters = [[
1314
+ "nextSlot",
1315
+ before.nextSlot + minted,
1316
+ after.nextSlot
1317
+ ], [
1318
+ "outstanding",
1319
+ before.outstanding + minted - BigInt(burned.length),
1320
+ after.outstanding
1321
+ ]];
1322
+ for (const [counter, expected, actual] of counters) if (actual !== expected) refusals.push({
1323
+ code: "custody-malformed-datum",
1324
+ reason: "counter-mismatch",
1325
+ account,
1326
+ counter,
1327
+ expected,
1328
+ actual
1329
+ });
1330
+ if (after.grantGeneration === before.grantGeneration) {
1331
+ for (const slot of change.revokedRemoved) if (!burned.some((mint) => mint.slot === slot)) refusals.push({
1332
+ code: "custody-unsupported-operation",
1333
+ reason: "revoked-slot-restored",
1334
+ account,
1335
+ slot
1336
+ });
1337
+ }
1338
+ const invalid = change.revokedAdded.filter((slot, index) => slot < 0n || slot >= before.nextSlot || change.revokedAdded.indexOf(slot) !== index);
1339
+ for (const slot of new Set(after.revoked)) {
1340
+ const previousCount = before.revoked.filter((value) => value === slot).length;
1341
+ if (after.revoked.filter((value) => value === slot).length > Math.max(1, previousCount)) invalid.push(slot);
1342
+ }
1343
+ for (const slot of new Set(invalid)) refusals.push({
1344
+ code: "custody-unsupported-operation",
1345
+ reason: "revoked-slot-invalid",
1346
+ account,
1347
+ slot
1348
+ });
1349
+ return refusals;
1350
+ };
1351
+ /**
1352
+ * Grant spends are refused unless the caller allows them. An allowed one is
1353
+ * refused for every rule of the logic's grant spend rule it breaks, so an
1354
+ * accepted grant spend is one the contract accepts, and for any input
1355
+ * outside the account address: an agent spends from the account alone.
1356
+ */
1357
+ const checkGrantSpends = ({ account, spends }, facts, scope) => [...spends.flatMap(({ input, output }) => scope.options.allowGrantSpend ? grantSpendViolationsOf({
1358
+ grantInput: output,
1359
+ inputs: facts.inputs.map((view) => view.output),
1360
+ referenceInputs: facts.referenceInputs.map((view) => view.output),
1361
+ outputs: facts.outputs,
1362
+ mint: facts.mint,
1363
+ requiredSigners: facts.requiredSigners,
1364
+ upperBoundMs: facts.upperBoundMs,
1365
+ account
1366
+ }).map((violation) => ({
1367
+ code: "custody-unsupported-operation",
1368
+ reason: "grant-spend-violation",
1369
+ account,
1370
+ input,
1371
+ violation
1372
+ })) : [{
1373
+ code: "custody-unsupported-operation",
1374
+ reason: "grant-spend",
1375
+ account,
1376
+ input
1377
+ }]), ...scope.options.allowGrantSpend && spends.length > 0 ? facts.inputs.filter(({ output }) => !isAtAccount(output, account)).map(({ input }) => ({
1378
+ code: "custody-unsupported-operation",
1379
+ reason: "grant-spend-foreign-input",
1380
+ account,
1381
+ input
1382
+ })) : []];
1383
+ /** The grant a single grant spend uses and the output that recreates it. */
1384
+ const grantSpendReportOf = (account, spends, facts) => {
1385
+ if (spends.length !== 1) return void 0;
1386
+ const [{ input, output }] = spends;
1387
+ const grant = decodedInlineGrant(output);
1388
+ const assetId = grant && grantAssetIdOf(account, grant.slot);
1389
+ const outputIndex = facts.outputs.findIndex((view) => assetId !== void 0 && view.value.assets.get(assetId) === 1n);
1390
+ const after = outputIndex === -1 ? void 0 : decodedInlineGrant(facts.outputs[outputIndex]);
1391
+ return grant && after ? {
1392
+ input,
1393
+ grant,
1394
+ outputIndex,
1395
+ after
1396
+ } : void 0;
1397
+ };
1398
+ /**
1399
+ * The operations a device rewrite performs, from its state change and the
1400
+ * grants it issues and sweeps. One revoked slot beside one issued grant is a
1401
+ * replacement; a generation step of one that empties the revoked list
1402
+ * under the same logic revokes every grant, where an upgrade has the same
1403
+ * counters.
1404
+ */
1405
+ const stateOperationsOf = (change, issuedSlots, sweptSlots) => {
1406
+ const { before, after, revokedAdded } = change;
1407
+ const isRevokeAll = !change.logic && after.grantGeneration === before.grantGeneration + 1n && after.revoked.length === 0;
1408
+ const isReplace = !change.grantGeneration && revokedAdded.length === 1 && issuedSlots.length === 1;
1409
+ return [
1410
+ ...isReplace ? [{
1411
+ kind: "replace-grant",
1412
+ slot: revokedAdded[0],
1413
+ issuedSlot: issuedSlots[0]
1414
+ }] : issuedSlots.map((slot) => ({
1415
+ kind: "issue-grant",
1416
+ slot
1417
+ })),
1418
+ ...isRevokeAll ? [{ kind: "revoke-all-grants" }] : [...isReplace ? [] : revokedAdded.map((slot) => ({
1419
+ kind: "revoke-grant",
1420
+ slot
1421
+ })), ...change.grantGeneration ? [{ kind: "change-grant-generation" }] : []],
1422
+ ...sweptSlots.length > 0 ? [{
1423
+ kind: "sweep-grants",
1424
+ slots: sweptSlots
1425
+ }] : [],
1426
+ ...change.devicesAdded.map((device) => ({
1427
+ kind: "add-device",
1428
+ device
1429
+ })),
1430
+ ...change.devicesRemoved.map((device) => ({
1431
+ kind: "remove-device",
1432
+ device
1433
+ })),
1434
+ ...change.isReordered ? [{ kind: "reorder-state" }] : []
1435
+ ];
1436
+ };
1437
+ /**
1438
+ * Lovelace leaving the account's address, its reward account and its logic
1439
+ * run's draw; negative is a net inflow. Lovelace is fungible, so what the
1440
+ * logic run draws is credited to the account: any of it that leaves counts
1441
+ * as the account's.
1442
+ */
1443
+ const lovelaceGivenUp = ({ netOutflow, stake, logicRun }) => netOutflow.lovelace + (stake.withdrawal ?? 0n) + (logicRun?.draw ?? 0n);
1444
+ /**
1445
+ * Whether an owner operation pays value out of the account: any asset that
1446
+ * leaves it, or lovelace given up beyond its share of the fee. An account
1447
+ * that pays only its own fee spends nothing. Account tokens are minted and
1448
+ * burned, never paid out: placement keeps them at their accounts.
1449
+ */
1450
+ const isOwnerSpend = (facts, feeShare) => [...facts.netOutflow.assets].some(([assetId, quantity]) => quantity > 0n && policyIdOf(assetId) !== facts.account.accountValidatorHash) || lovelaceGivenUp(facts) > feeShare;
1451
+ const operationsOf = (facts, feeShare) => {
1452
+ const { isCreation, grantSpend, stateChange, stake } = facts;
1453
+ return [
1454
+ ...isCreation ? [{ kind: "create" }] : [],
1455
+ ...grantSpend ? [{
1456
+ kind: "grant-spend",
1457
+ slot: grantSpend.grant.slot
1458
+ }] : [],
1459
+ ...stateChange ? stateOperationsOf(stateChange, facts.issued.map(({ grant }) => grant.slot), facts.swept.map(({ grant }) => grant.slot)) : [],
1460
+ ...!facts.isGrantSpend && isOwnerSpend(facts, feeShare) ? [{ kind: "owner-spend" }] : [],
1461
+ ...stake.certificates.flatMap((certificate) => certificate.kind === "pool-delegation" ? [{
1462
+ kind: "delegate",
1463
+ poolId: certificate.poolId
1464
+ }] : []),
1465
+ ...stake.withdrawal === void 0 ? [] : [{
1466
+ kind: "withdraw-rewards",
1467
+ quantity: stake.withdrawal
1468
+ }]
1469
+ ];
1470
+ };
1471
+ /** Each operated account's part of the accounts' fee share, in order. */
1472
+ const feeSharesOfAccounts = (operated, { accountShare }) => accountFeeSharesOf(operated.map(({ operationFacts }) => lovelaceGivenUp(operationFacts)), accountShare);
1473
+ /** The reports of the operated accounts, each with its operations named. */
1474
+ const withOperations = (operated, feeShares) => {
1475
+ const shares = feeSharesOfAccounts(operated, feeShares);
1476
+ return operated.map(({ report, operationFacts }, index) => ({
1477
+ ...report,
1478
+ operations: operationsOf(operationFacts, shares[index])
1479
+ }));
1480
+ };
1481
+ /**
1482
+ * A logic run that draws lovelace must leave it at the account. Lovelace
1483
+ * is fungible, so the draw is credited to the account, and it stays there
1484
+ * when the account gives up no more lovelace than its part of the fee. A
1485
+ * fee payer that takes change back could have paid the whole fee, so the
1486
+ * accounts' part is then nothing: otherwise the payer's change could hold
1487
+ * the draw. A grant spend pays out of the account by definition, so it may
1488
+ * draw nothing.
1489
+ */
1490
+ const checkLogicDraws = (operated, feeShares, isChangeToPayers) => {
1491
+ const shares = isChangeToPayers ? operated.map(() => 0n) : feeSharesOfAccounts(operated, feeShares);
1492
+ return operated.flatMap(({ operationFacts }, index) => {
1493
+ const { account, logicRun, isGrantSpend } = operationFacts;
1494
+ if (!logicRun || logicRun.draw === 0n) return [];
1495
+ return isGrantSpend || lovelaceGivenUp(operationFacts) > shares[index] ? [{
1496
+ code: "custody-unsupported-operation",
1497
+ reason: "logic-draw-leaves-account",
1498
+ account,
1499
+ logic: logicRun.logic,
1500
+ quantity: logicRun.draw
1501
+ }] : [];
1502
+ });
1503
+ };
1504
+ const isOwnReserve = (view) => view.inlineDatum !== void 0 && !view.hasReferenceScript && isCustodyReserveDatum(view.inlineDatum);
1505
+ const inspectAccount = (candidate, facts, scope) => {
1506
+ const account = hashesOf(candidate);
1507
+ const { networkId } = scope.options.chainId;
1508
+ const inputs = readAccountInputs(account, facts);
1509
+ const { control, before, ...controlInput } = readControlInput(account, inputs);
1510
+ const hasControlInput = control !== void 0;
1511
+ const isGrantSpend = inputs.spends.length > 0;
1512
+ const mint = readMint(account, facts, hasControlInput);
1513
+ const stake = readStakeEffects(candidate, facts);
1514
+ const controlOutput = readControlOutput(account, facts, hasControlInput || mint.isCreation);
1515
+ const { after } = controlOutput;
1516
+ const referenced = readReferencedControls(account, facts, isGrantSpend);
1517
+ const referencedStates = referenced.references.map(({ output }) => decodedInlineState(output));
1518
+ const states = [
1519
+ before,
1520
+ after,
1521
+ ...referencedStates
1522
+ ];
1523
+ const logicRun = readLogicRun(account, facts, before?.logic ?? (isGrantSpend ? referencedStates[0]?.logic : void 0) ?? (mint.isCreation ? after?.logic : void 0));
1524
+ const issued = readIssuedGrants(account, facts, {
1525
+ issued: mint.issued,
1526
+ before,
1527
+ after,
1528
+ now: scope.options.now
1529
+ });
1530
+ const swept = readSweptGrants(account, facts, {
1531
+ sweeps: inputs.sweeps,
1532
+ burned: mint.burned,
1533
+ before,
1534
+ hasControlInput
1535
+ });
1536
+ const refusals = [
1537
+ ...inputs.refusals,
1538
+ ...controlInput.refusals,
1539
+ ...mint.refusals,
1540
+ ...referenced.refusals,
1541
+ ...checkKnownLogics(account, candidate.contractVersion, states),
1542
+ ...logicRun.refusals,
1543
+ ...stake.refusals,
1544
+ ...checkStakeOperations(account, stake, {
1545
+ isCreation: mint.isCreation,
1546
+ hasControlInput
1547
+ }),
1548
+ ...controlOutput.refusals,
1549
+ ...issued.refusals,
1550
+ ...swept.refusals,
1551
+ ...checkGrantSpends({
1552
+ account,
1553
+ spends: inputs.spends
1554
+ }, facts, scope)
1555
+ ];
1556
+ const netOutflow = leavingValueAt(facts.inputs.map(({ output }) => output), facts.outputs, accountPlutusAddressKey(account));
1557
+ let stateChange;
1558
+ if (after && (before || mint.isCreation)) {
1559
+ const written = {
1560
+ account,
1561
+ before,
1562
+ after
1563
+ };
1564
+ refusals.push(...checkWrittenState(written), ...checkSigner(written, facts, scope));
1565
+ if (before) {
1566
+ stateChange = diffCustodyStates(before, after);
1567
+ refusals.push(...checkStateChange(account, stateChange, mint));
1568
+ }
1569
+ }
1570
+ const [reference] = referenced.references;
1571
+ const [referencedState] = referencedStates;
1572
+ const grantSpend = grantSpendReportOf(account, inputs.spends, facts);
1573
+ return {
1574
+ isOperated: inputs.isSpent || mint.isCreation || mint.issued.length + mint.burned.length > 0 || stake.isStaked || controlOutput.count > 0,
1575
+ refusals,
1576
+ logics: states.flatMap((state) => state ? [state.logic] : []),
1577
+ report: {
1578
+ contractVersion: candidate.contractVersion,
1579
+ ...account,
1580
+ address: accountAddressOf(account, networkId),
1581
+ rewardAccount: accountRewardAccountOf(account, networkId),
1582
+ ...control && before ? { controlInput: {
1583
+ input: control.input,
1584
+ state: before,
1585
+ lovelace: control.output.value.lovelace
1586
+ } } : {},
1587
+ ...controlOutput.control && after ? { controlOutput: {
1588
+ outputIndex: controlOutput.control.outputIndex,
1589
+ state: after,
1590
+ lovelace: controlOutput.control.view.value.lovelace
1591
+ } } : {},
1592
+ ...isGrantSpend && reference && referencedState ? { referencedControl: {
1593
+ input: reference.input,
1594
+ state: referencedState,
1595
+ lovelace: reference.output.value.lovelace
1596
+ } } : {},
1597
+ fundInputs: inputs.funds.map(({ input, output }) => ({
1598
+ input,
1599
+ value: output.txOut.value,
1600
+ hasReferenceScript: output.hasReferenceScript
1601
+ })),
1602
+ reserveInputs: inputs.reserves.map(({ input, output }) => ({
1603
+ input,
1604
+ value: output.txOut.value,
1605
+ datum: output.datumKind === "hash" ? "hash" : "inline",
1606
+ isOwnFormat: isOwnReserve(output),
1607
+ hasReferenceScript: output.hasReferenceScript
1608
+ })),
1609
+ accountOutputs: facts.outputs.flatMap((view, outputIndex) => isAtAccount(view, account) ? [{
1610
+ outputIndex,
1611
+ value: view.txOut.value,
1612
+ datum: view.datumKind,
1613
+ kind: accountUtxoKindOf(view, account)
1614
+ }] : []),
1615
+ issuedGrants: issued.reports,
1616
+ sweptGrants: swept.reports,
1617
+ ...grantSpend ? { grantSpend } : {},
1618
+ netOutflow,
1619
+ certificates: stake.certificates,
1620
+ ...stake.withdrawal === void 0 ? {} : { withdrawal: stake.withdrawal },
1621
+ ...logicRun.run && logicRun.run.draw > 0n ? { logicDraw: logicRun.run.draw } : {},
1622
+ ...stateChange ? { stateChange } : {}
1623
+ },
1624
+ operationFacts: {
1625
+ account,
1626
+ isCreation: mint.isCreation,
1627
+ isGrantSpend,
1628
+ grantSpend,
1629
+ stateChange,
1630
+ issued: issued.reports,
1631
+ swept: swept.reports,
1632
+ netOutflow,
1633
+ stake,
1634
+ logicRun: logicRun.run
1635
+ }
1636
+ };
1637
+ };
1638
+ //#endregion
1639
+ //#region ../../packages/lib/cardano-custody/src/analysis/auxiliary-data.ts
1640
+ /**
1641
+ * The metadata a transaction carries, read from its auxiliary data and
1642
+ * bound to the signed body by the auxiliary data hash. Undefined without
1643
+ * any. A body that commits to auxiliary data the transaction does not
1644
+ * carry, or auxiliary data the body does not commit to by that hash, is
1645
+ * refused: a signature over the body would then authorise metadata consent
1646
+ * never saw. So is auxiliary data holding anything but metadata.
1647
+ */
1648
+ const readCustodyMetadata = (transaction, body) => {
1649
+ let auxiliaryData;
1650
+ let carried;
1651
+ try {
1652
+ const serialized = transaction.auxiliaryData();
1653
+ auxiliaryData = serialized?.toCore();
1654
+ carried = serialized ? blake2b.hash(serialized.toCbor(), 32) : void 0;
1655
+ } catch {
1656
+ return fail({
1657
+ code: "custody-unsupported-operation",
1658
+ reason: "auxiliary-data-unreadable"
1659
+ });
1660
+ }
1661
+ if (body.auxiliaryDataHash?.toLowerCase() !== carried) return fail({
1662
+ code: "custody-unsupported-operation",
1663
+ reason: "auxiliary-data-hash-mismatch"
1664
+ });
1665
+ if ((auxiliaryData?.scripts?.length ?? 0) > 0) return fail({
1666
+ code: "custody-unsupported-operation",
1667
+ reason: "auxiliary-data-scripts"
1668
+ });
1669
+ const metadata = auxiliaryData?.blob;
1670
+ return ok(metadata && metadata.size > 0 ? metadata : void 0);
1671
+ };
1672
+ /**
1673
+ * A grant spend carrying metadata. An agent spends only what its grant
1674
+ * allows, and attaching data to the account's transactions is not among it.
1675
+ */
1676
+ const checkGrantSpendMetadata = (operated, metadata) => metadata === void 0 ? [] : operated.flatMap(({ report }) => report.grantSpend ? [{
1677
+ code: "custody-unsupported-operation",
1678
+ reason: "grant-spend-metadata",
1679
+ account: hashesOf(report),
1680
+ input: report.grantSpend.input
1681
+ }] : []);
1682
+ //#endregion
1683
+ //#region ../../packages/lib/cardano-custody/src/analysis/refusals.ts
1684
+ /**
1685
+ * The order checks run in: the record, the caller's context, the account
1686
+ * UTxOs it resolves, a grant spend, unsupported shapes, the written datums,
1687
+ * the signing device, the initial state at creation and locked outputs.
1688
+ */
1689
+ const STAGE = {
1690
+ "unsupported-contract-version": 0,
1691
+ "invalid-record": 0,
1692
+ "unresolved-input": 1,
1693
+ "resolution-not-in-body": 1,
1694
+ "duplicate-resolution": 1,
1695
+ "invalid-resolution": 1,
1696
+ "network-mismatch": 1,
1697
+ "redeemer-mismatch": 1,
1698
+ "redeemer-on-key-input": 1,
1699
+ "reference-script-mismatch": 1,
1700
+ "unused-account-validator": 1,
1701
+ "unused-logic": 1,
1702
+ "multiple-control-inputs": 1,
1703
+ "foreign-account": 1,
1704
+ "account-not-operated": 1,
1705
+ "creation-without-record": 1,
1706
+ "control-output-without-control-input": 1,
1707
+ "control-input-datum": 2,
1708
+ "unknown-logic": 2,
1709
+ "control-input-value": 2,
1710
+ "grant-input-datum": 2,
1711
+ "grant-input-value": 2,
1712
+ "grant-spend": 3,
1713
+ "grant-spend-violation": 3,
1714
+ "grant-spend-foreign-input": 3,
1715
+ "grant-spend-metadata": 3,
1716
+ "unsupported-body-field": 4,
1717
+ "auxiliary-data-hash-mismatch": 4,
1718
+ "auxiliary-data-scripts": 4,
1719
+ "auxiliary-data-unreadable": 4,
1720
+ "missing-redeemer": 4,
1721
+ "unknown-redeemer": 4,
1722
+ "duplicate-redeemer": 4,
1723
+ "mint-redeemer-mismatch": 4,
1724
+ "foreign-certificate": 4,
1725
+ "refused-certificate": 4,
1726
+ "unsupported-certificate": 4,
1727
+ "foreign-withdrawal": 4,
1728
+ "duplicate-withdrawal": 4,
1729
+ "logic-draw-leaves-account": 4,
1730
+ "logic-withdrawal-missing": 4,
1731
+ "foreign-mint": 4,
1732
+ "unsupported-mint": 4,
1733
+ "issue-sweep-mixed": 4,
1734
+ "grant-batch-limit": 4,
1735
+ "grant-mint-without-control": 4,
1736
+ "reference-control": 4,
1737
+ "foreign-reference-script": 4,
1738
+ "creation-without-registration": 4,
1739
+ "fund-without-account-token": 4,
1740
+ "reserve-without-control": 4,
1741
+ "sweep-without-control": 4,
1742
+ "sweep-burn-mismatch": 4,
1743
+ "registration-without-creation": 4,
1744
+ "stake-operation-without-control": 4,
1745
+ "account-token-misplaced": 4,
1746
+ "foreign-reserve": 4,
1747
+ "deposit-reference-script": 4,
1748
+ "control-output-count": 5,
1749
+ "control-output-datum": 5,
1750
+ "control-output-value": 5,
1751
+ "control-output-reference-script": 5,
1752
+ "state-violation": 5,
1753
+ "counter-mismatch": 5,
1754
+ "generation-decreased": 5,
1755
+ "logic-changed": 5,
1756
+ "revoked-slot-restored": 5,
1757
+ "revoked-slot-invalid": 5,
1758
+ "grant-output-invalid": 5,
1759
+ "grant-violation": 5,
1760
+ "expired-grant": 5,
1761
+ "sweep-not-dead": 5,
1762
+ "no-device-signer": 6,
1763
+ "not-creator": 6,
1764
+ "not-listed": 6,
1765
+ "not-required-signer": 6,
1766
+ "initial-state": 7,
1767
+ "datum-hash": 8,
1768
+ "unspendable-address": 8
1769
+ };
1770
+ /** Refusals in check order; refusals of one stage keep the order found. */
1771
+ const inCheckOrder = (refusals) => refusals.map((refusal, position) => ({
1772
+ refusal,
1773
+ position
1774
+ })).sort((a, b) => STAGE[a.refusal.reason] - STAGE[b.refusal.reason] || a.position - b.position).map(({ refusal }) => refusal);
1775
+ //#endregion
1776
+ //#region ../../packages/lib/cardano-custody/src/analysis/transaction-checks.ts
1777
+ const UNSUPPORTED_BODY_FIELDS = [
1778
+ "accountBalanceIntervals",
1779
+ "directDeposits",
1780
+ "guards",
1781
+ "proposalProcedures",
1782
+ "subTransactions",
1783
+ "update",
1784
+ "votingProcedures"
1785
+ ];
1786
+ /**
1787
+ * A redeemer pointer named twice has no single reading, so the redeemer a
1788
+ * check reads might not be the one a script runs with.
1789
+ */
1790
+ const checkRedeemerPointers = (facts) => {
1791
+ const seen = /* @__PURE__ */ new Set();
1792
+ const refusals = [];
1793
+ for (const { purpose, index } of facts.redeemers) {
1794
+ const pointer = `${purpose}:${index}`;
1795
+ if (seen.has(pointer)) refusals.push({
1796
+ code: "custody-unsupported-operation",
1797
+ reason: "duplicate-redeemer",
1798
+ purpose,
1799
+ index
1800
+ });
1801
+ seen.add(pointer);
1802
+ }
1803
+ return refusals;
1804
+ };
1805
+ /**
1806
+ * Consistency checks that catch resolutions hiding a custody spend. The
1807
+ * ledger refuses a spend redeemer for an input no script locks, and an
1808
+ * account validator witness that no input or mint needs, so either means a
1809
+ * resolution misstates what the transaction spends.
1810
+ */
1811
+ const checkSpendWitnesses = (facts) => {
1812
+ const refusals = [];
1813
+ for (const [index, { input, output }] of facts.inputs.entries()) if (redeemerData(facts, Cardano.RedeemerPurpose.spend, index) !== void 0 && paymentCredentialOf(output)?.type !== Cardano.CredentialType.ScriptHash) refusals.push({
1814
+ code: "custody-context-mismatch",
1815
+ reason: "redeemer-on-key-input",
1816
+ input
1817
+ });
1818
+ for (const version of SUPPORTED_CUSTODY_CONTRACT_VERSIONS) {
1819
+ const { accountValidatorHash } = version;
1820
+ const isNeeded = facts.inputs.some(({ output: { addressClass } }) => addressClass.kind === "account" && addressClass.contractVersion === version) || facts.mintPolicies.includes(accountValidatorHash);
1821
+ if (facts.witnessScriptHashes.has(accountValidatorHash) && !isNeeded) refusals.push({
1822
+ code: "custody-context-mismatch",
1823
+ reason: "unused-account-validator",
1824
+ accountValidatorHash
1825
+ });
1826
+ }
1827
+ return refusals;
1828
+ };
1829
+ /**
1830
+ * Consistency checks on the known logics a transaction attaches or runs: a
1831
+ * logic runs through a withdrawal from its stake credential, and only over
1832
+ * a control UTxO that names it, spent, referenced or written. The ledger
1833
+ * refuses a witness nothing needs, and a run with no such control UTxO
1834
+ * fails, so a logic attached without a run, or run with no control UTxO
1835
+ * naming it among `logics`, means a resolution hides that control UTxO or
1836
+ * the transaction cannot succeed.
1837
+ */
1838
+ const checkLogicUse = (facts, logics) => {
1839
+ const named = new Set(logics);
1840
+ const withdrawn = new Set((facts.body.withdrawals ?? []).flatMap(({ stakeAddress }) => {
1841
+ const scriptHash = scriptHashOfRewardAccount(stakeAddress);
1842
+ return scriptHash === void 0 ? [] : [scriptHash];
1843
+ }));
1844
+ return [...new Set([...withdrawn, ...facts.witnessScriptHashes].filter(isKnownCustodyLogic))].filter((logic) => !withdrawn.has(logic) || !named.has(logic)).map((logic) => ({
1845
+ code: "custody-context-mismatch",
1846
+ reason: "unused-logic",
1847
+ logic
1848
+ }));
1849
+ };
1850
+ /**
1851
+ * A reward account the body's withdrawal map names more than once. The
1852
+ * decoded body keeps one of its withdrawals, so what inspection reads might
1853
+ * not be what the bytes say.
1854
+ */
1855
+ const checkWithdrawalAccounts = (facts) => {
1856
+ const seen = /* @__PURE__ */ new Set();
1857
+ const repeated = /* @__PURE__ */ new Set();
1858
+ for (const rewardAccount of facts.withdrawalAccounts) {
1859
+ if (seen.has(rewardAccount)) repeated.add(rewardAccount);
1860
+ seen.add(rewardAccount);
1861
+ }
1862
+ return [...repeated].map((rewardAccount) => ({
1863
+ code: "custody-unsupported-operation",
1864
+ reason: "duplicate-withdrawal",
1865
+ rewardAccount
1866
+ }));
1867
+ };
1868
+ const isSameOutpoint = (a, b) => a.txId.toLowerCase() === b.txId.toLowerCase() && a.index === b.index;
1869
+ /**
1870
+ * Reference inputs that carry a reference script must be the contract's
1871
+ * parked scripts on the inspected network: the output reference a supported
1872
+ * version pins, resolved with exactly the script it pins there. Any other
1873
+ * reference script is code Lace has not pinned.
1874
+ */
1875
+ const checkReferenceScripts = (facts, networkMagic) => {
1876
+ const parked = SUPPORTED_CUSTODY_CONTRACT_VERSIONS.flatMap((version) => version.referenceScripts[networkMagic] ?? []);
1877
+ return facts.referenceInputs.flatMap(({ input, output }) => {
1878
+ const pinned = parked.find(({ txIn }) => isSameOutpoint(txIn, input));
1879
+ if (pinned) return output.referenceScriptHash === pinned.scriptHash ? [] : [{
1880
+ code: "custody-context-mismatch",
1881
+ reason: "reference-script-mismatch",
1882
+ input
1883
+ }];
1884
+ return output.hasReferenceScript ? [{
1885
+ code: "custody-unsupported-operation",
1886
+ reason: "foreign-reference-script",
1887
+ input
1888
+ }] : [];
1889
+ });
1890
+ };
1891
+ /**
1892
+ * Certificates and withdrawals on credentials of no custody account the
1893
+ * transaction names. A withdrawal may also run a known logic, which
1894
+ * `checkLogicUse` ties to the control UTxOs naming it.
1895
+ */
1896
+ const checkForeignStakeEffects = (facts, candidates, networkId) => {
1897
+ const refusals = [];
1898
+ const stakeScriptHashes = new Set(candidates.map(({ stakeScriptHash }) => stakeScriptHash));
1899
+ for (const [certificateIndex, certificate] of (facts.body.certificates ?? []).entries()) {
1900
+ const credential = stakeCredentialOf(certificate);
1901
+ if (credential?.type !== Cardano.CredentialType.ScriptHash || !stakeScriptHashes.has(credential.hash)) refusals.push({
1902
+ code: "custody-unsupported-operation",
1903
+ reason: "foreign-certificate",
1904
+ certificateIndex,
1905
+ certificateType: certificate.__typename
1906
+ });
1907
+ }
1908
+ for (const { stakeAddress } of facts.body.withdrawals ?? []) {
1909
+ const scriptHash = scriptHashOfRewardAccount(stakeAddress);
1910
+ if (scriptHash === void 0 || !(stakeScriptHashes.has(scriptHash) || isKnownCustodyLogic(scriptHash))) refusals.push({
1911
+ code: "custody-unsupported-operation",
1912
+ reason: "foreign-withdrawal",
1913
+ rewardAccount: stakeAddress
1914
+ });
1915
+ else if (Cardano.Address.fromString(stakeAddress)?.getNetworkId() !== networkId) refusals.push({
1916
+ code: "custody-context-mismatch",
1917
+ reason: "network-mismatch",
1918
+ address: stakeAddress
1919
+ });
1920
+ }
1921
+ return refusals;
1922
+ };
1923
+ /**
1924
+ * Mints of other policies, and custody mints of a shape no mint redeemer
1925
+ * accepts: one state NFT name, or grant names of one account that all issue
1926
+ * or all burn.
1927
+ */
1928
+ const checkMint = (facts) => {
1929
+ const byPolicy = /* @__PURE__ */ new Map();
1930
+ for (const entry of facts.mint) {
1931
+ const policyId = policyIdOf(entry[0]);
1932
+ byPolicy.set(policyId, [...byPolicy.get(policyId) ?? [], entry]);
1933
+ }
1934
+ return [...byPolicy].flatMap(([policyId, entries]) => {
1935
+ const refuseEach = (reason) => entries.map(([assetId, quantity]) => ({
1936
+ code: "custody-unsupported-operation",
1937
+ reason,
1938
+ assetId,
1939
+ quantity
1940
+ }));
1941
+ if (!versionOfPolicy(policyId)) return refuseEach("foreign-mint");
1942
+ const shape = mintShapeOf(entries);
1943
+ if (shape.kind === "invalid") return refuseEach("unsupported-mint");
1944
+ return shape.kind === "mixed" ? [{
1945
+ code: "custody-unsupported-operation",
1946
+ reason: "issue-sweep-mixed",
1947
+ account: {
1948
+ accountValidatorHash: policyId,
1949
+ stakeScriptHash: shape.stakeScriptHash
1950
+ }
1951
+ }] : [];
1952
+ });
1953
+ };
1954
+ /**
1955
+ * What an output at a custody account that holds no account token must be:
1956
+ * a plain deposit, or a reserve in the format Lace writes, `d87980` with no
1957
+ * reference script. A datum hash locks value behind a preimage no account
1958
+ * builder holds; another datum or a reference script raises the fee and
1959
+ * minimum of every later spend.
1960
+ */
1961
+ const findAccountOutputRefusal = (view, location) => {
1962
+ if (view.datumKind === "hash") return {
1963
+ code: "custody-locked-output",
1964
+ reason: "datum-hash",
1965
+ location
1966
+ };
1967
+ if (view.inlineDatum !== void 0) return view.hasReferenceScript || !isCustodyReserveDatum(view.inlineDatum) ? {
1968
+ code: "custody-unsupported-operation",
1969
+ reason: "foreign-reserve",
1970
+ location
1971
+ } : void 0;
1972
+ return view.hasReferenceScript ? {
1973
+ code: "custody-unsupported-operation",
1974
+ reason: "deposit-reference-script",
1975
+ location
1976
+ } : void 0;
1977
+ };
1978
+ /**
1979
+ * Rules over every output and resolution: custody addresses on the named
1980
+ * network, account tokens at their own account addresses, no value locked
1981
+ * where no redeemer can reach it, and account outputs without an account
1982
+ * token in a shape Lace writes. Outputs holding an account token are the
1983
+ * control and grant outputs, which their account's checks read.
1984
+ */
1985
+ const checkOutputs = (facts, networkId) => {
1986
+ const refusals = [];
1987
+ const custodyViews = [
1988
+ ...[
1989
+ ...facts.inputs,
1990
+ ...facts.referenceInputs,
1991
+ ...facts.collaterals
1992
+ ].map(({ output }) => output),
1993
+ ...facts.outputs,
1994
+ ...facts.collateralReturn ? [facts.collateralReturn] : []
1995
+ ].filter(({ addressClass }) => addressClass.kind !== "other");
1996
+ for (const view of custodyViews) if (view.address?.getNetworkId() !== networkId) refusals.push({
1997
+ code: "custody-context-mismatch",
1998
+ reason: "network-mismatch",
1999
+ address: view.txOut.address
2000
+ });
2001
+ const accountValidatorHashes = SUPPORTED_CUSTODY_CONTRACT_VERSIONS.map((version) => version.accountValidatorHash);
2002
+ for (const { input, output } of facts.inputs) if (holdsMisplacedAccountToken(output, accountValidatorHashes)) refusals.push({
2003
+ code: "custody-unsupported-operation",
2004
+ reason: "account-token-misplaced",
2005
+ location: {
2006
+ kind: "input",
2007
+ input
2008
+ }
2009
+ });
2010
+ const locatedOutputs = [...facts.outputs.map((view, index) => ({
2011
+ view,
2012
+ location: {
2013
+ kind: "output",
2014
+ index
2015
+ }
2016
+ })), ...facts.collateralReturn ? [{
2017
+ view: facts.collateralReturn,
2018
+ location: { kind: "collateral-return" }
2019
+ }] : []];
2020
+ for (const { view, location } of locatedOutputs) {
2021
+ if (location.kind === "output" && holdsMisplacedAccountToken(view, accountValidatorHashes)) refusals.push({
2022
+ code: "custody-unsupported-operation",
2023
+ reason: "account-token-misplaced",
2024
+ location
2025
+ });
2026
+ const { addressClass } = view;
2027
+ if (addressClass.kind === "unspendable") refusals.push({
2028
+ code: "custody-locked-output",
2029
+ reason: "unspendable-address",
2030
+ location
2031
+ });
2032
+ else if (addressClass.kind === "account" && holdsNoAccountToken(view.value, addressClass.contractVersion.accountValidatorHash)) {
2033
+ const refusal = findAccountOutputRefusal(view, location);
2034
+ if (refusal) refusals.push(refusal);
2035
+ }
2036
+ }
2037
+ return refusals;
2038
+ };
2039
+ const checkBodyFields = (facts) => {
2040
+ const body = facts.body;
2041
+ return UNSUPPORTED_BODY_FIELDS.filter((field) => body[field] !== void 0).map((field) => ({
2042
+ code: "custody-unsupported-operation",
2043
+ reason: "unsupported-body-field",
2044
+ field
2045
+ }));
2046
+ };
2047
+ /** With a record, the transaction operates that account and no other. */
2048
+ const checkRecordAccount = (operated, record) => {
2049
+ if (!record) return [];
2050
+ const refusals = operated.filter((account) => !isSameAccount(account, record)).map((account) => ({
2051
+ code: "custody-context-mismatch",
2052
+ reason: "foreign-account",
2053
+ account: hashesOf(account)
2054
+ }));
2055
+ if (!operated.some((account) => isSameAccount(account, record))) refusals.push({
2056
+ code: "custody-context-mismatch",
2057
+ reason: "account-not-operated",
2058
+ account: {
2059
+ accountValidatorHash: record.contractVersion.accountValidatorHash,
2060
+ stakeScriptHash: record.stakeScriptHash
2061
+ }
2062
+ });
2063
+ return refusals;
2064
+ };
2065
+ //#endregion
2066
+ //#region ../../packages/lib/cardano-custody/src/analysis/inspect.ts
2067
+ const readRecord = (options) => {
2068
+ if (options.accountRecord === void 0) return ok(void 0);
2069
+ const parsed = parseCustodyAccountRecord(options.accountRecord, options.chainId.networkMagic);
2070
+ if (parsed.ok) return ok(parsed.value.identity);
2071
+ return fail(parsed.error.code === "unsupported-contract-version" ? {
2072
+ code: "custody-unsupported-version",
2073
+ reason: "unsupported-contract-version",
2074
+ accountValidatorHash: parsed.error.accountValidatorHash
2075
+ } : {
2076
+ code: "custody-context-mismatch",
2077
+ reason: "invalid-record",
2078
+ recordError: parsed.error
2079
+ });
2080
+ };
2081
+ const paymentKeyHashOf = (view) => {
2082
+ const credential = paymentCredentialOf(view);
2083
+ return credential?.type === Cardano.CredentialType.KeyHash ? credential.hash : void 0;
2084
+ };
2085
+ const externalInputOf = ({ input, output }, deviceKeyHash) => ({
2086
+ input,
2087
+ address: output.txOut.address,
2088
+ value: output.txOut.value,
2089
+ isDeviceKey: deviceKeyHash !== void 0 && paymentKeyHashOf(output) === deviceKeyHash
2090
+ });
2091
+ const validityOf = ({ validityInterval }, slotToPosixMs) => {
2092
+ const bounds = {};
2093
+ if (validityInterval?.invalidBefore !== void 0) {
2094
+ const slot = validityInterval.invalidBefore;
2095
+ bounds.invalidBefore = {
2096
+ slot,
2097
+ posixMs: slotToPosixMs(slot)
2098
+ };
2099
+ }
2100
+ if (validityInterval?.invalidHereafter !== void 0) {
2101
+ const slot = validityInterval.invalidHereafter;
2102
+ bounds.invalidHereafter = {
2103
+ slot,
2104
+ posixMs: slotToPosixMs(slot)
2105
+ };
2106
+ }
2107
+ return bounds;
2108
+ };
2109
+ /**
2110
+ * Reads every custody effect of a transaction from its bytes and the
2111
+ * caller's resolutions of its inputs, and refuses what Lace never signs.
2112
+ * The SDK checks its own builds with it before asking for a signature, and
2113
+ * the hosted signer derives its consent and its refusals from it, so both
2114
+ * apply one set of rules.
2115
+ *
2116
+ * It takes the `Serialization.Transaction` whose body hash is signed, so
2117
+ * what it reads is what a signature commits to. Resolutions come from the
2118
+ * caller and are not checked against the chain; an inline datum in a
2119
+ * resolution wins over a datum hash reported beside it. A custody account
2120
+ * is any address whose payment credential is a supported account validator
2121
+ * and whose stake credential is an inline script.
2122
+ *
2123
+ * Every operation on an account runs the logic its control datum names,
2124
+ * through a withdrawal from the logic's stake credential, and that logic
2125
+ * must be one of the contract version's catalogue. A reference input may
2126
+ * carry a reference script only as a contract script parked at the output
2127
+ * reference the version pins on the inspected network.
2128
+ *
2129
+ * Witness-set redeemers are bound to the signed body only through its
2130
+ * script data hash, which inspection does not recompute. A spend redeemer
2131
+ * must fit the UTxO it spends: `Device` the control UTxO, `SpendWithGrant`
2132
+ * or `SweepGrant` a grant UTxO, `Fund` a deposit. `SpendWithGrant` is
2133
+ * refused unless `allowGrantSpend` is set, and the mint redeemer must fit
2134
+ * the mint: `CreateAccount` a state NFT, `IssueGrants` grant tokens minted,
2135
+ * `BurnGrants` grant tokens burned. Another redeemer set under the same body
2136
+ * cannot change what the body does: it can only run a contract branch that
2137
+ * the device's signature does not authorise.
2138
+ */
2139
+ const inspectCustodyTransaction = (transaction, resolvedInputs, options) => {
2140
+ const record = readRecord(options);
2141
+ if (!record.ok) return fail({ refusals: [record.error] });
2142
+ const serializedBody = transaction.body();
2143
+ const body = serializedBody.toCore();
2144
+ const resolved = resolveBody(body, resolvedInputs);
2145
+ if (!resolved.ok) return fail({ refusals: resolved.error });
2146
+ const metadata = readCustodyMetadata(transaction, body);
2147
+ if (!metadata.ok) return fail({ refusals: [metadata.error] });
2148
+ const facts = readTransaction({
2149
+ body,
2150
+ bodyCbor: serializedBody.toCbor(),
2151
+ witness: transaction.witnessSet().toCore()
2152
+ }, resolved.value, options);
2153
+ const { networkId } = options.chainId;
2154
+ const scope = {
2155
+ options,
2156
+ record: record.value,
2157
+ deviceKeyHash: options.deviceKeyHash?.toLowerCase()
2158
+ };
2159
+ const candidates = findCandidates(facts, record.value);
2160
+ const outcomes = candidates.map((candidate) => inspectAccount(candidate, facts, scope));
2161
+ const operated = outcomes.filter(({ isOperated }) => isOperated);
2162
+ const isOperatedAccount = (view) => operated.some(({ report }) => isAtAccount(view, report));
2163
+ const externalInputs = facts.inputs.filter(({ output }) => !isOperatedAccount(output)).map((view) => externalInputOf(view, scope.deviceKeyHash));
2164
+ const externalOutputs = facts.outputs.flatMap((view, outputIndex) => view.addressClass.kind === "other" ? [{
2165
+ outputIndex,
2166
+ address: view.txOut.address,
2167
+ value: view.txOut.value
2168
+ }] : []);
2169
+ const feeShares = feeSharesOf({
2170
+ externalInputs,
2171
+ externalOutputs,
2172
+ fee: body.fee
2173
+ });
2174
+ const refusals = inCheckOrder([
2175
+ ...checkBodyFields(facts),
2176
+ ...checkWithdrawalAccounts(facts),
2177
+ ...checkForeignStakeEffects(facts, candidates, networkId),
2178
+ ...checkMint(facts),
2179
+ ...checkRedeemerPointers(facts),
2180
+ ...checkSpendWitnesses(facts),
2181
+ ...checkLogicUse(facts, outcomes.flatMap(({ logics }) => logics)),
2182
+ ...checkReferenceScripts(facts, options.chainId.networkMagic),
2183
+ ...checkOutputs(facts, networkId),
2184
+ ...checkRecordAccount(operated.map(({ report }) => report), record.value),
2185
+ ...outcomes.flatMap((outcome) => outcome.refusals),
2186
+ ...checkGrantSpendMetadata(operated, metadata.value),
2187
+ ...checkLogicDraws(operated, feeShares, externalOutputs.some(({ address }) => feeShares.payers.includes(address)))
2188
+ ]);
2189
+ if (refusals.length > 0) return fail({ refusals });
2190
+ return ok({
2191
+ accounts: withOperations(operated, feeShares),
2192
+ deposits: facts.outputs.flatMap((view, outputIndex) => view.addressClass.kind === "account" && view.datumKind !== "hash" && !isOperatedAccount(view) ? [{
2193
+ outputIndex,
2194
+ address: view.txOut.address,
2195
+ value: view.txOut.value,
2196
+ accountValidatorHash: view.addressClass.contractVersion.accountValidatorHash,
2197
+ stakeScriptHash: view.addressClass.stakeScriptHash,
2198
+ datum: view.datumKind
2199
+ }] : []),
2200
+ externalOutputs,
2201
+ externalInputs,
2202
+ collateralInputs: facts.collaterals.map((view) => externalInputOf(view, scope.deviceKeyHash)),
2203
+ ...body.collateralReturn ? { collateralReturn: {
2204
+ address: body.collateralReturn.address,
2205
+ value: body.collateralReturn.value
2206
+ } } : {},
2207
+ requiredSigners: [...facts.requiredSigners],
2208
+ validity: validityOf(body, options.slotToPosixMs),
2209
+ fee: body.fee,
2210
+ feeShares,
2211
+ ...body.donation === void 0 ? {} : { donation: body.donation },
2212
+ ...metadata.value === void 0 ? {} : { metadata: metadata.value }
2213
+ });
2214
+ };
2215
+ //#endregion
2216
+ //#region ../../packages/lib/cardano-custody/src/analysis/requested-scope.ts
2217
+ /**
2218
+ * The recipient a bech32 Shelley payment address names, when the text is
2219
+ * spelled exactly as `toBech32` writes it: lower case, with the prefix of the
2220
+ * network its header names. Bech32 also decodes an upper-case spelling and
2221
+ * ignores the prefix, so a mainnet-looking address could otherwise stand for
2222
+ * a testnet recipient.
2223
+ */
2224
+ const canonicalRecipientOf = (value) => {
2225
+ if (typeof value !== "string" || !Cardano.Address.isValidBech32(value)) return;
2226
+ const address = Cardano.Address.fromBech32(value);
2227
+ const recipient = custodyRecipientFromAddress(address);
2228
+ return recipient.ok && address.toBech32() === value ? {
2229
+ address,
2230
+ recipient: recipient.value
2231
+ } : void 0;
2232
+ };
2233
+ const recipientKeysOn = (recipients, networkId) => {
2234
+ const keys = [];
2235
+ for (const text of recipients) {
2236
+ const canonical = canonicalRecipientOf(text);
2237
+ if (canonical?.address.getNetworkId() !== networkId) return void 0;
2238
+ keys.push(recipientKey(canonical.recipient));
2239
+ }
2240
+ return keys;
2241
+ };
2242
+ const hasSameRecipients = (requested, actual, networkId) => {
2243
+ const requestedKeys = recipientKeysOn(requested, networkId);
2244
+ const { recipients } = actual.scope;
2245
+ return requestedKeys !== void 0 && requestedKeys.length === recipients.length && requestedKeys.every((key, index) => key === recipientKey(recipients[index]));
2246
+ };
2247
+ /**
2248
+ * The fields where a requested scope differs from a grant a transaction
2249
+ * issues, in declaration order; empty when they match. Every field must be
2250
+ * equal exactly. Recipients must be the same Plutus addresses in the same
2251
+ * order, and every requested recipient must be on the account network,
2252
+ * because the datum carries no network and the grant would otherwise send
2253
+ * to another network's address with the same credentials.
2254
+ */
2255
+ const compareRequestedGrantScope = (requested, actual, chainId) => {
2256
+ const { scope } = actual;
2257
+ const isEqual = {
2258
+ slot: requested.slot === actual.slot,
2259
+ grantee: requested.grantee === actual.grantee,
2260
+ asset: requested.asset.policyId === scope.asset.policyId && requested.asset.assetName === scope.asset.assetName,
2261
+ perCallCap: requested.perCallCap === scope.perCallCap,
2262
+ cap: requested.cap === scope.cap,
2263
+ lovelacePerCallCap: requested.lovelacePerCallCap === scope.lovelacePerCallCap,
2264
+ lovelaceCap: requested.lovelaceCap === scope.lovelaceCap,
2265
+ expiresAt: requested.expiresAt === scope.expiresAt,
2266
+ recipients: hasSameRecipients(requested.recipients, actual, chainId.networkId)
2267
+ };
2268
+ return Object.keys(isEqual).filter((field) => !isEqual[field]);
2269
+ };
2270
+ const serializeCustodyRequestedGrantScope = (scope) => ({
2271
+ slot: scope.slot.toString(),
2272
+ grantee: scope.grantee,
2273
+ asset: {
2274
+ policyId: scope.asset.policyId,
2275
+ assetName: scope.asset.assetName
2276
+ },
2277
+ perCallCap: scope.perCallCap.toString(),
2278
+ cap: scope.cap.toString(),
2279
+ lovelacePerCallCap: scope.lovelacePerCallCap.toString(),
2280
+ lovelaceCap: scope.lovelaceCap.toString(),
2281
+ expiresAt: scope.expiresAt.toString(),
2282
+ recipients: [...scope.recipients]
2283
+ });
2284
+ const SCOPE_FIELDS = [
2285
+ "slot",
2286
+ "grantee",
2287
+ "asset",
2288
+ "perCallCap",
2289
+ "cap",
2290
+ "lovelacePerCallCap",
2291
+ "lovelaceCap",
2292
+ "expiresAt",
2293
+ "recipients"
2294
+ ];
2295
+ const ASSET_FIELDS = ["policyId", "assetName"];
2296
+ const INTEGER_FIELDS = [
2297
+ "slot",
2298
+ "perCallCap",
2299
+ "cap",
2300
+ "lovelacePerCallCap",
2301
+ "lovelaceCap",
2302
+ "expiresAt"
2303
+ ];
2304
+ const MAX_ASSET_NAME_BYTES = 32;
2305
+ /** Canonical decimal: no sign, no leading zero, no exponent. */
2306
+ const NON_NEGATIVE_DECIMAL = /^(?:0|[1-9]\d*)$/;
2307
+ const isNonNegativeDecimal = (value) => typeof value === "string" && NON_NEGATIVE_DECIMAL.test(value);
2308
+ const malformed = (reason, field) => fail({
2309
+ code: "malformed-requested-scope",
2310
+ reason,
2311
+ ...field === void 0 ? {} : { field }
2312
+ });
2313
+ const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2314
+ const hasExactFields = (value, fields) => {
2315
+ const keys = Object.keys(value);
2316
+ return keys.length === fields.length && fields.every((field) => keys.includes(field));
2317
+ };
2318
+ const isShelleyPaymentAddress = (value) => canonicalRecipientOf(value) !== void 0;
2319
+ const isWellFormedAsset = (asset) => {
2320
+ const { policyId, assetName } = asset;
2321
+ if (policyId === "") return assetName === "";
2322
+ return isHash28Hex(policyId) && isLowerCaseHex(assetName) && assetName.length <= MAX_ASSET_NAME_BYTES * 2;
2323
+ };
2324
+ /**
2325
+ * Reads a requested scope from protocol JSON, refusing anything but the
2326
+ * exact shape `serializeCustodyRequestedGrantScope` writes: no missing or
2327
+ * extra field, canonical non-negative decimal integers, a positive expiry,
2328
+ * a 28-byte grantee, a lovelace or 28-byte policy asset with a name of at
2329
+ * most 32 bytes, and at most 8 Shelley payment addresses in canonical
2330
+ * bech32. Network membership of the recipients is checked when comparing.
2331
+ *
2332
+ * @param input - the scope as JSON text or as an already parsed value
2333
+ */
2334
+ const parseCustodyRequestedGrantScope = (input) => {
2335
+ let value = input;
2336
+ if (typeof input === "string") try {
2337
+ value = JSON.parse(input);
2338
+ } catch {
2339
+ return malformed("expected JSON");
2340
+ }
2341
+ if (!isPlainObject(value) || !hasExactFields(value, SCOPE_FIELDS)) return malformed(`expected exactly ${SCOPE_FIELDS.join(", ")}`);
2342
+ const integerField = INTEGER_FIELDS.find((field) => !isNonNegativeDecimal(value[field]));
2343
+ if (integerField) return malformed("expected a non-negative decimal string", integerField);
2344
+ if (value.expiresAt === "0") return malformed("expected a positive expiry", "expiresAt");
2345
+ if (!isHash28Hex(value.grantee)) return malformed("expected 28 bytes of lower-case hex", "grantee");
2346
+ const { asset, recipients } = value;
2347
+ if (!isPlainObject(asset) || !hasExactFields(asset, ASSET_FIELDS) || !isWellFormedAsset(asset)) return malformed("expected lovelace or a policy id and asset name", "asset");
2348
+ if (!Array.isArray(recipients) || recipients.length > 8 || !recipients.every(isShelleyPaymentAddress)) return malformed(`expected at most 8 bech32 payment addresses`, "recipients");
2349
+ const integers = value;
2350
+ return ok({
2351
+ slot: BigInt(integers.slot),
2352
+ grantee: value.grantee,
2353
+ asset: {
2354
+ policyId: asset.policyId,
2355
+ assetName: asset.assetName
2356
+ },
2357
+ perCallCap: BigInt(integers.perCallCap),
2358
+ cap: BigInt(integers.cap),
2359
+ lovelacePerCallCap: BigInt(integers.lovelacePerCallCap),
2360
+ lovelaceCap: BigInt(integers.lovelaceCap),
2361
+ expiresAt: BigInt(integers.expiresAt),
2362
+ recipients
2363
+ });
2364
+ };
2365
+ //#endregion
2366
+ //#region ../../packages/lib/cardano-custody/src/classify.ts
2367
+ /** The tokens of a value under a policy. */
2368
+ const tokensUnder = (value, policyId) => [...value.assets?.entries() ?? []].filter(([assetId, quantity]) => quantity !== 0n && Cardano.AssetId.getPolicyId(assetId) === policyId).map(([assetId]) => assetId);
2369
+ /** Reads a UTxO that holds exactly one grant token of the account, in quantity one. */
2370
+ const classifyGrant = (utxo, token, slot) => {
2371
+ const [, output] = utxo;
2372
+ if (output.value.assets?.size !== 1) return { anomaly: {
2373
+ code: "grant-value-not-strict",
2374
+ utxo,
2375
+ slot
2376
+ } };
2377
+ if (output.datum === void 0) return { anomaly: {
2378
+ code: "grant-datum-not-inline",
2379
+ utxo,
2380
+ slot
2381
+ } };
2382
+ const decoded = decodeCustodyGrant(output.datum);
2383
+ if (!decoded.ok) return { anomaly: {
2384
+ code: "grant-datum-malformed",
2385
+ utxo,
2386
+ slot,
2387
+ error: decoded.error
2388
+ } };
2389
+ if (decoded.value.slot !== slot) return { anomaly: {
2390
+ code: "grant-slot-mismatch",
2391
+ utxo,
2392
+ slot,
2393
+ datumSlot: decoded.value.slot
2394
+ } };
2395
+ return { grant: {
2396
+ utxo,
2397
+ grant: decoded.value,
2398
+ token
2399
+ } };
2400
+ };
2401
+ /** The grant token and its slot, when the tokens are exactly one own grant token held once. */
2402
+ const ownGrantToken = (account, value, tokens) => {
2403
+ const [token] = tokens;
2404
+ if (tokens.length !== 1 || value.assets?.get(token) !== 1n) return void 0;
2405
+ const name = parseCustodyAccountTokenName(Cardano.AssetId.getAssetName(token));
2406
+ return name?.kind === "grant" && name.stakeScriptHash === account.stakeScriptHash ? {
2407
+ token,
2408
+ slot: name.slot
2409
+ } : void 0;
2410
+ };
2411
+ /**
2412
+ * Sorts an account's UTxOs by kind. UTxOs not at the exact account address
2413
+ * are ignored. It never throws: a UTxO that holds an account token in a
2414
+ * shape the contract rules out is listed in `anomalies`.
2415
+ */
2416
+ const classifyCustodyAccountUtxos = (account, utxos) => {
2417
+ const policyId = Cardano.PolicyId(account.accountValidatorHash);
2418
+ const stateNft = Cardano.AssetId.fromParts(policyId, Cardano.AssetName(account.stakeScriptHash));
2419
+ const controls = [];
2420
+ const grants = [];
2421
+ const own = [];
2422
+ const foreign = [];
2423
+ const funds = [];
2424
+ const stranded = [];
2425
+ const anomalies = [];
2426
+ for (const utxo of utxos) {
2427
+ const [, output] = utxo;
2428
+ if (output.address !== account.address) continue;
2429
+ const tokens = tokensUnder(output.value, policyId);
2430
+ if (tokens.includes(stateNft)) controls.push(utxo);
2431
+ else if (tokens.length > 0) {
2432
+ const grantToken = ownGrantToken(account, output.value, tokens);
2433
+ if (!grantToken) {
2434
+ anomalies.push({
2435
+ code: "unexpected-account-tokens",
2436
+ utxo,
2437
+ assetIds: tokens
2438
+ });
2439
+ continue;
2440
+ }
2441
+ const classified = classifyGrant(utxo, grantToken.token, grantToken.slot);
2442
+ if ("grant" in classified) grants.push(classified.grant);
2443
+ else anomalies.push(classified.anomaly);
2444
+ } else if (output.datum !== void 0) (output.scriptReference === void 0 && isCustodyReserveDatum(output.datum) ? own : foreign).push(utxo);
2445
+ else if (output.datumHash === void 0) funds.push(utxo);
2446
+ else stranded.push(utxo);
2447
+ }
2448
+ grants.sort((a, b) => a.grant.slot < b.grant.slot ? -1 : a.grant.slot > b.grant.slot ? 1 : 0);
2449
+ return {
2450
+ controls,
2451
+ grants,
2452
+ reserves: {
2453
+ own,
2454
+ foreign
2455
+ },
2456
+ funds,
2457
+ stranded,
2458
+ anomalies
2459
+ };
2460
+ };
2461
+ /**
2462
+ * The liveness of a grant against a control state at `now`. A grant dead for
2463
+ * several reasons takes the first of superseded, revoked and expired, so
2464
+ * `expired` alone means a sweep needs a validity interval that starts after
2465
+ * the expiry.
2466
+ *
2467
+ * @param now - POSIX milliseconds; a non-finite value counts as past every expiry
2468
+ */
2469
+ const custodyGrantLiveness = (grant, state, now) => {
2470
+ if (grant.generation < state.grantGeneration) return "superseded";
2471
+ if (grant.generation > state.grantGeneration) return "future-generation";
2472
+ if (!isCustodyGrantCurrent(grant, state)) return "revoked";
2473
+ return isCustodyGrantExpired(grant, now) ? "expired" : "current";
2474
+ };
2475
+ //#endregion
2476
+ //#region ../../packages/lib/cardano-custody/src/device-key.ts
2477
+ /**
2478
+ * The CIP-1854 path `m/1854'/1815'/a'/0/i` of custody device and creator
2479
+ * keys. The account index `a` belongs to the contract version and network:
2480
+ * `custodyDeviceAccountIndex` gives it.
2481
+ */
2482
+ const CUSTODY_DEVICE_KEY_PATH = {
2483
+ purpose: 1854,
2484
+ coinType: 1815,
2485
+ role: 0
2486
+ };
2487
+ /** First index that is a hardened derivation; device keys use soft ones only. */
2488
+ const HARDENED_INDEX = 2147483648;
2489
+ /**
2490
+ * The device key at `m/1854'/1815'/a'/0/index`, derived from the CIP-1854
2491
+ * account extended public key of account `a` by soft derivation, so no
2492
+ * secret is needed.
2493
+ */
2494
+ const deriveCustodyDeviceKey = async (accountPublicKey, index) => {
2495
+ if (!Number.isInteger(index) || index < 0 || index >= HARDENED_INDEX) return fail({
2496
+ code: "invalid-device-index",
2497
+ index
2498
+ });
2499
+ await Crypto.ready();
2500
+ try {
2501
+ const publicKey = Crypto.Bip32PublicKey.fromHex(accountPublicKey).derive([CUSTODY_DEVICE_KEY_PATH.role, index]).toRawKey();
2502
+ return ok({
2503
+ index,
2504
+ publicKey: publicKey.hex(),
2505
+ keyHash: publicKey.hash().hex()
2506
+ });
2507
+ } catch {
2508
+ return fail({ code: "invalid-account-public-key" });
2509
+ }
2510
+ };
2511
+ /**
2512
+ * A short form of a device key hash for people to compare across devices:
2513
+ * its first and last 8 hex characters.
2514
+ */
2515
+ const custodyDeviceFingerprint = (keyHash) => `${keyHash.slice(0, 8)}…${keyHash.slice(-8)}`;
2516
+ //#endregion
2517
+ //#region ../../packages/contract/cardano-context/src/custody/account-state.ts
2518
+ /** Whether the account's contract version lists the logic in its catalogue. */
2519
+ const isKnownLogic = (accountValidatorHash, logic) => {
2520
+ const version = findCustodyContractVersion(accountValidatorHash);
2521
+ return version.ok && findCustodyLogic(version.value, logic).ok;
2522
+ };
2523
+ /**
2524
+ * Classifies a custody account's UTxOs by the rules of the custody contract
2525
+ * and reads the control state.
2526
+ */
2527
+ const classifyCardanoCustodyAccount = ({ account, utxos, withdrawableRewards, protocolParameters }) => {
2528
+ const { controls, grants, reserves, funds, stranded, anomalies } = classifyCustodyAccountUtxos(account, utxos);
2529
+ const holdings = {
2530
+ funds,
2531
+ reserves,
2532
+ grants,
2533
+ grantLockedLovelace: grants.reduce((total, { utxo: [, output] }) => total + output.value.coins, 0n),
2534
+ stranded,
2535
+ anomalies,
2536
+ withdrawableRewards
2537
+ };
2538
+ if (controls.length === 0) return {
2539
+ ...holdings,
2540
+ status: "notCreated"
2541
+ };
2542
+ const invalid = (reason) => ({
2543
+ ...holdings,
2544
+ status: "invalid",
2545
+ reason,
2546
+ controlUtxos: controls
2547
+ });
2548
+ if (controls.length > 1) return invalid({ code: "multiple-control-utxos" });
2549
+ const [controlUtxo] = controls;
2550
+ const [, controlOutput] = controlUtxo;
2551
+ if (controlOutput.datum === void 0) return invalid({ code: "missing-inline-datum" });
2552
+ const logic = decodeCustodyStateLogic(controlOutput.datum);
2553
+ if (!logic.ok) return invalid({
2554
+ code: "undecodable-state",
2555
+ error: logic.error
2556
+ });
2557
+ if (!isKnownLogic(account.accountValidatorHash, logic.value)) return invalid({
2558
+ code: "unknown-logic",
2559
+ logic: logic.value
2560
+ });
2561
+ const decoded = decodeCustodyAccountState(controlOutput.datum);
2562
+ if (!decoded.ok) return invalid({
2563
+ code: "undecodable-state",
2564
+ error: decoded.error
2565
+ });
2566
+ const controlMinimumLovelace = minAdaRequired(controlOutput, BigInt(protocolParameters.coinsPerUtxoByte));
2567
+ const controlLovelace = controlOutput.value.coins;
2568
+ const deviceKeyHash = account.device?.keyHash;
2569
+ return {
2570
+ ...holdings,
2571
+ status: "live",
2572
+ controlUtxo,
2573
+ state: decoded.value,
2574
+ controlMinimumLovelace,
2575
+ controlHeadroomLovelace: controlLovelace > controlMinimumLovelace ? controlLovelace - controlMinimumLovelace : 0n,
2576
+ ...deviceKeyHash !== void 0 && { currentDeviceListed: decoded.value.devices.some((device) => device.toLowerCase() === deviceKeyHash.toLowerCase()) }
2577
+ };
2578
+ };
2579
+ //#endregion
2580
+ //#region ../../packages/contract/cardano-context/src/custody/build-transaction.ts
2581
+ /**
2582
+ * The most lovelace a grant spend's fee may take when the operation names
2583
+ * no `feeBound`: 1,500,000 lovelace. The spend lowers the grant's remaining caps by its outputs
2584
+ * and fee before the fee is final, so the bound is what the caps must leave
2585
+ * room for.
2586
+ */
2587
+ const CARDANO_CUSTODY_DEFAULT_GRANT_FEE_BOUND = 1500000n;
2588
+ //#endregion
2589
+ //#region ../../packages/contract/cardano-context/src/custody/grant-status.ts
2590
+ /**
2591
+ * Any key hash: an unrestricted grant pays out to any address, and an
2592
+ * enterprise key address makes the smallest output.
2593
+ */
2594
+ const ANY_KEY_HASH = Hash28ByteBase16("00".repeat(28));
2595
+ /** The addresses a grant may pay out to, or one standing for any address. */
2596
+ const payoutAddressesOf = ({ recipients }, networkId) => {
2597
+ if (recipients.length === 0) return [Cardano.EnterpriseAddress.fromCredentials(networkId, {
2598
+ type: Cardano.CredentialType.KeyHash,
2599
+ hash: ANY_KEY_HASH
2600
+ }).toAddress().toBech32()];
2601
+ return recipients.flatMap((recipient) => {
2602
+ const address = custodyRecipientToAddress(recipient, networkId);
2603
+ return address.ok ? [address.value.toBech32()] : [];
2604
+ });
2605
+ };
2606
+ /** The scope's token, or undefined when its policy id or name is malformed. */
2607
+ const tokenOf = ({ asset }) => {
2608
+ try {
2609
+ return Cardano.AssetId.fromParts(Cardano.PolicyId(asset.policyId), Cardano.AssetName(asset.assetName));
2610
+ } catch {
2611
+ return;
2612
+ }
2613
+ };
2614
+ const smallerOf = (a, b) => a < b ? a : b;
2615
+ /**
2616
+ * Whether a grant scope can no longer pay the smallest spend. That spend
2617
+ * is one output to the cheapest address the grant allows, holding the
2618
+ * ledger minimum lovelace, plus the fee bound. A lovelace grant draws both
2619
+ * from its lovelace caps (`cap`, `perCallCap`). A token grant draws one
2620
+ * token from its token caps and the lovelace from `lovelaceCap` and
2621
+ * `lovelacePerCallCap`. A grant with no address it can pay is exhausted.
2622
+ */
2623
+ const isCardanoCustodyGrantExhausted = (scope, { protocolParameters, networkId, feeBound = CARDANO_CUSTODY_DEFAULT_GRANT_FEE_BOUND }) => {
2624
+ const isLovelace = isCustodyLovelaceAsset(scope.asset);
2625
+ const token = isLovelace ? void 0 : tokenOf(scope);
2626
+ if (!isLovelace && !token) return true;
2627
+ const addresses = payoutAddressesOf(scope, networkId);
2628
+ if (addresses.length === 0) return true;
2629
+ const coinsPerUtxoByte = BigInt(protocolParameters.coinsPerUtxoByte);
2630
+ const smallestLovelaceSpend = addresses.map((address) => minAdaRequired({
2631
+ address,
2632
+ value: {
2633
+ coins: 0n,
2634
+ ...token && { assets: new Map([[token, 1n]]) }
2635
+ }
2636
+ }, coinsPerUtxoByte)).reduce(smallerOf) + feeBound;
2637
+ if (isLovelace) return smallerOf(scope.cap, scope.perCallCap) < smallestLovelaceSpend;
2638
+ return smallerOf(scope.cap, scope.perCallCap) < 1n || smallerOf(scope.lovelaceCap, scope.lovelacePerCallCap) < smallestLovelaceSpend;
2639
+ };
2640
+ const bySlot = ({ grant: a }, { grant: b }) => a.slot < b.slot ? -1 : a.slot > b.slot ? 1 : 0;
2641
+ /**
2642
+ * The status of a grant the confirmed view holds and the in-flight view
2643
+ * keeps. `grant` is its in-flight version: an unsettled grant spend lowers
2644
+ * its caps and changes nothing else.
2645
+ */
2646
+ const confirmedGrantStatus = (grant, { confirmed, inFlight, now, ...pricing }) => {
2647
+ const liveness = custodyGrantLiveness(grant, confirmed, now);
2648
+ if (liveness === "revoked" || liveness === "superseded") return liveness;
2649
+ if (liveness === "future-generation") return "pending";
2650
+ const next = custodyGrantLiveness(grant, inFlight, now);
2651
+ if (next === "revoked" || next === "superseded") return "revoking";
2652
+ if (liveness === "expired") return "expired";
2653
+ return isCardanoCustodyGrantExhausted(grant.scope, pricing) ? "exhausted" : "live";
2654
+ };
2655
+ /**
2656
+ * The status of every grant UTxO of a custody account, in slot order.
2657
+ * `inFlight` is the account after the submitted operations that have not
2658
+ * settled; `confirmed` is the account on chain, absent while it has no
2659
+ * confirmed control state. A grant is known by its slot, which no grant
2660
+ * ever reuses. `now` is POSIX milliseconds: the time of the chain tip.
2661
+ *
2662
+ * A grant only the in-flight view holds is `pending`, and one only the
2663
+ * confirmed view holds is `sweeping`. A grant both hold is judged against
2664
+ * the confirmed state, and is `revoking` when the in-flight state kills it.
2665
+ * Its listed version is the in-flight one, so an unsettled grant spend
2666
+ * shows its lowered caps at once.
2667
+ */
2668
+ const classifyCardanoCustodyGrants = ({ confirmed, inFlight, now, protocolParameters, networkId }) => {
2669
+ const confirmedSlots = new Set(confirmed?.grants.map(({ grant }) => grant.slot));
2670
+ const inFlightSlots = new Set(inFlight.grants.map(({ grant }) => grant.slot));
2671
+ const kept = inFlight.grants.map((grantUtxo) => ({
2672
+ ...grantUtxo,
2673
+ status: confirmed && confirmedSlots.has(grantUtxo.grant.slot) ? confirmedGrantStatus(grantUtxo.grant, {
2674
+ confirmed: confirmed.state,
2675
+ inFlight: inFlight.state,
2676
+ now,
2677
+ protocolParameters,
2678
+ networkId
2679
+ }) : "pending"
2680
+ }));
2681
+ const swept = (confirmed?.grants ?? []).filter(({ grant }) => !inFlightSlots.has(grant.slot)).map((grantUtxo) => ({
2682
+ ...grantUtxo,
2683
+ status: "sweeping"
2684
+ }));
2685
+ return [...kept, ...swept].sort(bySlot);
2686
+ };
2687
+ //#endregion
2688
+ export { findCustodyScopeViolations as S, CUSTODY_ACCOUNT_RECORD_TYPE as _, custodyDeviceFingerprint as a, parseCustodyAccountRecord as b, custodyGrantLiveness as c, serializeCustodyRequestedGrantScope as d, inspectCustodyTransaction as f, encodeCustodyStakeRedeemer as g, encodeCustodyMintRedeemer as h, classifyCardanoCustodyAccount as i, compareRequestedGrantScope as l, encodeCustodyLogicRedeemer as m, isCardanoCustodyGrantExhausted as n, deriveCustodyDeviceKey as o, encodeCustodyAccountRedeemer as p, CARDANO_CUSTODY_DEFAULT_GRANT_FEE_BOUND as r, classifyCustodyAccountUtxos as s, classifyCardanoCustodyGrants as t, parseCustodyRequestedGrantScope as u, canonicalCustodyAccountRecord as v, custodyGrantAfterSpend as x, createCustodyAccountRecord as y };