@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,2797 @@
1
+ require("./rolldown-runtime-Br2OG8yv.cjs");
2
+ require("./_polyfill-buffer-patch-CM8X7JiY.cjs");
3
+ require("./codecs-Cswkg7Tm.cjs");
4
+ const require_bytes_vo = require("./bytes.vo-BNRquqaO.cjs");
5
+ const require_errors = require("./errors-BIftM8ZN.cjs");
6
+ const require_providers = require("./providers-FjhM47BH.cjs");
7
+ const require_gated_call = require("./gated-call-DgJm1Mi_.cjs");
8
+ const require_artefact_loader = require("./artefact-loader-BWWYAD7E.cjs");
9
+ const require_grant_rules = require("./grant-rules-BzVTWXS7.cjs");
10
+ const require_types = require("./types-Cc3mC8u0.cjs");
11
+ const require_envelopes = require("./envelopes-CH25R-DN.cjs");
12
+ const require_grant_circuits = require("./grant-circuits-DswmW3nI.cjs");
13
+ const require_submit = require("./submit-Bs77dPn_.cjs");
14
+ const require_enc_key_circuits = require("./enc-key-circuits-BLrZrBVI.cjs");
15
+ const require_acc_module = require("./acc-module-4iIUv6My.cjs");
16
+ const require_device_readers = require("./device-readers-CdxS2btd.cjs");
17
+ const require_grants = require("./grants-BoI5uwSF.cjs");
18
+ const require_account_call = require("./account-call-Bd4Rpbvt.cjs");
19
+ let rxjs = require("rxjs");
20
+ //#region ../../packages/module/passport-account/src/acc/device-circuits.ts
21
+ /**
22
+ * The gated circuits the device flows authorise, named once so the flow
23
+ * that submits a call and the signer that scopes a key session to it
24
+ * agree on the circuit.
25
+ */
26
+ const ADD_DEVICE_CIRCUIT = "add_device_with_jubjub";
27
+ const REMOVE_DEVICE_CIRCUIT = "remove_device_with_jubjub";
28
+ //#endregion
29
+ //#region ../../packages/module/passport-account/src/acc/pending-deployment.ts
30
+ /** Whether a record's pending creation carries the whole deployment, not only its marker. */
31
+ const isPendingDeployment = (pending) => "authorityKey" in pending;
32
+ /**
33
+ * The refusal of a creation that resumes an unfinished one under another
34
+ * lock choice than `recorded`, the one it was started with, which the
35
+ * message names so a retry can repeat it.
36
+ */
37
+ const lockChoiceMismatch = (recorded) => new require_errors.CreationLockMismatchError(`The unfinished Passport account creation was started with lockAccount ${recorded}; resume it with the same lock choice.`);
38
+ //#endregion
39
+ //#region ../../packages/module/passport-account/src/store/envelope.ts
40
+ /** AES-GCM is specified for 96-bit IVs; each seal draws a fresh one. */
41
+ const IV_LENGTH = 12;
42
+ const utf8 = (value) => new TextEncoder().encode(value);
43
+ /** Whether a stored value has the sealed envelope layout. */
44
+ const isSealedRecord = (value) => {
45
+ if (typeof value !== "object" || value === null) return false;
46
+ const candidate = value;
47
+ return candidate.v === 1 && typeof candidate.iv === "string" && typeof candidate.ciphertext === "string";
48
+ };
49
+ /**
50
+ * Encrypts a record into a {@link SealedRecord} under the given key,
51
+ * marking the envelope pending when asked.
52
+ */
53
+ const sealRecord = async (key, record, { pending = false } = {}) => {
54
+ const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
55
+ const ciphertext = await crypto.subtle.encrypt({
56
+ name: "AES-GCM",
57
+ iv
58
+ }, key, utf8(JSON.stringify(record)));
59
+ return {
60
+ v: 1,
61
+ iv: require_bytes_vo.HexBytes.fromByteArray(iv),
62
+ ciphertext: require_bytes_vo.HexBytes.fromByteArray(new Uint8Array(ciphertext)),
63
+ ...pending ? { pending: true } : {}
64
+ };
65
+ };
66
+ /**
67
+ * Decrypts a {@link SealedRecord} back into the record it sealed. Any
68
+ * failure to open - a tampered ciphertext, a mismatched key, or a
69
+ * malformed envelope - throws RecordCorruptedError.
70
+ */
71
+ const openRecord = async (key, sealed) => {
72
+ try {
73
+ const plaintext = await crypto.subtle.decrypt({
74
+ name: "AES-GCM",
75
+ iv: require_bytes_vo.ByteArray.fromHex(require_bytes_vo.HexBytes(sealed.iv))
76
+ }, key, require_bytes_vo.ByteArray.fromHex(require_bytes_vo.HexBytes(sealed.ciphertext)));
77
+ return JSON.parse(new TextDecoder().decode(plaintext));
78
+ } catch {
79
+ throw new require_errors.RecordCorruptedError();
80
+ }
81
+ };
82
+ //#endregion
83
+ //#region ../../packages/module/passport-account/src/store/account-record.ts
84
+ const COLLECTION_ID = "passport-account";
85
+ const ACCOUNT_RECORD_KEY = "account";
86
+ /**
87
+ * An {@link AccountRecordStore} over the platform key-value storage,
88
+ * namespaced under its own collection. With a `keyProvider` the record is
89
+ * sealed at rest and only sealed envelopes are readable. What a sealed
90
+ * record reads as without a `keyProvider` is the caller's choice:
91
+ * 'skip' (the default) reads it as undefined, for the ceremony-free
92
+ * restore peek that cannot open it by design; 'error' raises
93
+ * RecordUnreadableError, for the flows where a missing storage key is a
94
+ * persistence misconfiguration that must not look like an absent
95
+ * account. Without a `keyProvider` (the dev authoriser) the record
96
+ * persists in plaintext, which holds no secret once the account is
97
+ * complete; while a creation is unfinished it holds the contract's
98
+ * maintenance authority key (see {@link PassportAccountRecord}), so the
99
+ * dev authoriser is for development and tests only.
100
+ */
101
+ const createAccountRecordStore = (createKeyValueStorage, keyProvider, { onSealedWithoutKey = "skip" } = {}) => {
102
+ const storage = createKeyValueStorage({ collectionId: COLLECTION_ID });
103
+ const open = async (keyOf, value) => isSealedRecord(value) ? openRecord(await keyOf(), value) : void 0;
104
+ const stored = () => storage.getValues([ACCOUNT_RECORD_KEY]).pipe((0, rxjs.map)(([value]) => value), (0, rxjs.defaultIfEmpty)(void 0));
105
+ const isPending = (record) => record.deployment !== void 0;
106
+ const presenceOf = (value) => {
107
+ if (value === void 0) return "absent";
108
+ return (isSealedRecord(value) ? value.pending === true : isPending(value)) ? "pending" : "complete";
109
+ };
110
+ return {
111
+ read: () => keyProvider ? stored().pipe((0, rxjs.concatMap)(async (value) => open(keyProvider, value))) : stored().pipe((0, rxjs.map)((value) => {
112
+ if (!isSealedRecord(value)) return value;
113
+ if (onSealedWithoutKey === "error") throw new require_errors.RecordUnreadableError();
114
+ })),
115
+ write: (record) => keyProvider ? (0, rxjs.defer)(async () => sealRecord(await keyProvider(), record, { pending: isPending(record) })).pipe((0, rxjs.mergeMap)((sealed) => storage.setValue(ACCOUNT_RECORD_KEY, sealed))) : storage.setValue(ACCOUNT_RECORD_KEY, record),
116
+ presence: () => stored().pipe((0, rxjs.map)(presenceOf))
117
+ };
118
+ };
119
+ /**
120
+ * The promise view of an {@link AccountRecordStore}, for the flows. A
121
+ * storage that completes without emitting (destroyed mid-flow) reads as
122
+ * absent and writes as done, mirroring the stream contract so a flow
123
+ * never hangs on it.
124
+ */
125
+ const toAccountRecords = (store) => ({
126
+ read: async () => (0, rxjs.firstValueFrom)(store.read(), { defaultValue: void 0 }),
127
+ write: async (record) => (0, rxjs.firstValueFrom)(store.write(record), { defaultValue: void 0 }),
128
+ presence: async () => (0, rxjs.firstValueFrom)(store.presence(), { defaultValue: "absent" })
129
+ });
130
+ //#endregion
131
+ //#region ../../packages/module/passport-account/src/acc/inbox-walk.ts
132
+ const SCRATCH_BLOCK_TIME = /* @__PURE__ */ new Date(0);
133
+ const defaultLoadLedger = async () => import("@midnightntwrk/ledger-v9");
134
+ /** The entries the contract holds, in inbox order, the Lace records included. */
135
+ const inboxEntries = (ledgerState) => {
136
+ const entries = [];
137
+ for (let ordinal = 0n; ordinal < ledgerState.inbox_count; ordinal += 1n) if (ledgerState.inbox.member(ordinal)) entries.push(ledgerState.inbox.lookup(ordinal));
138
+ return entries;
139
+ };
140
+ /**
141
+ * The commitment the chain carries for a coin the account received,
142
+ * recomputed with the ledger's own output construction: a contract-owned
143
+ * output of this coin towards the account address, which is exactly what
144
+ * a depositor builds and the contract claims. A description the ledger
145
+ * refuses to commit to is no candidate.
146
+ */
147
+ const toCandidate = ({ ZswapOutput }, address, { coin, entry }) => {
148
+ const info = {
149
+ type: require_bytes_vo.HexBytes.fromByteArray(coin.color),
150
+ nonce: require_bytes_vo.HexBytes.fromByteArray(coin.nonce),
151
+ value: coin.value
152
+ };
153
+ try {
154
+ return {
155
+ coin,
156
+ info,
157
+ commitment: ZswapOutput.newContractOwned(info, void 0, address).commitment.toLowerCase(),
158
+ entry
159
+ };
160
+ } catch {
161
+ return null;
162
+ }
163
+ };
164
+ /**
165
+ * The nullifier each held coin's spend reveals, computed with the ledger's
166
+ * own input construction: a contract-owned input of the coin from the
167
+ * account, exactly what the contract builds when it spends one. A
168
+ * contract-owned nullifier binds the coin and the account address alone,
169
+ * never the coin's position, so the inputs are built over a scratch
170
+ * commitment tree holding just these coins rather than the chain's tree.
171
+ */
172
+ const coinNullifiers = ({ ZswapChainState, ZswapInput, ZswapOffer, ZswapOutput }, address, held) => {
173
+ const tree = held.reduce((state, { info }) => state.tryApply(ZswapOffer.fromOutput(ZswapOutput.newContractOwned(info, void 0, address), info.type, info.value))[0], new ZswapChainState()).postBlockUpdate(SCRATCH_BLOCK_TIME, 0n);
174
+ return held.map(({ info }, position) => ZswapInput.newContractOwned({
175
+ ...info,
176
+ mt_index: BigInt(position)
177
+ }, void 0, address, tree).nullifier.toLowerCase());
178
+ };
179
+ /**
180
+ * The shielded outputs the chain created for the account and the
181
+ * nullifiers it recorded, read from the ledger events of the transactions
182
+ * that acted on the contract. A deposit is one such action, so its
183
+ * transaction is among them; so is every spend of a coin the account
184
+ * holds, because only the contract's own call can spend it.
185
+ */
186
+ const chainHistory = async (address, indexer) => {
187
+ const actions = await indexer.enumerateContractActions(address);
188
+ const hashes = [...new Set(actions.map((action) => action.txHash))];
189
+ const events = await Promise.all(hashes.map(async (hash) => indexer.queryShieldedEvents(hash)));
190
+ const recipient = address.toLowerCase();
191
+ return {
192
+ outputs: new Map(events.flatMap(({ outputs }) => outputs).filter((output) => output.contractAddress === recipient).map((output) => [output.commitment, output.index])),
193
+ nullifiers: new Set(events.flatMap(({ nullifiers }) => nullifiers))
194
+ };
195
+ };
196
+ /**
197
+ * Walks the account's inbox the way the custody standard prescribes:
198
+ * every entry other than the Lace records, which hold the account keys
199
+ * and the grant bookkeeping rather than coins, is opened with the
200
+ * encryption secrets, each coin that opens is verified by recomputing
201
+ * its commitment and finding it among the outputs the chain created for
202
+ * the account, and the index that output took in the commitment tree is
203
+ * captured with it. A coin whose nullifier the same history recorded has
204
+ * been spent and is left out; nullifiers of other parties' inputs never
205
+ * match, since a contract-owned nullifier binds the account address.
206
+ * Anyone can append any bytes to the inbox, so an entry that does not open, or opens to a coin
207
+ * the chain never created for this account, is skipped as noise and
208
+ * counted, never raised. Two entries describing the same coin, as a
209
+ * re-encryption after a key rotation leaves behind, yield one holding,
210
+ * read from the first entry that described it. An empty inbox, or one
211
+ * with nothing that opens, is answered without loading the ledger or
212
+ * reading the chain's history. A ledger binding that fails to load is a
213
+ * failure of the walk itself, not of any entry, so it is raised rather
214
+ * than reported as an inbox of noise.
215
+ */
216
+ const walkInbox = async ({ address, ledgerState, indexer, openEntries, loadLedger = defaultLoadLedger }) => {
217
+ const entries = inboxEntries(ledgerState).filter((entry) => !require_envelopes.isLaceRecord(entry));
218
+ const nothing = (skippedEntries) => ({
219
+ holdings: [],
220
+ holdingEntries: [],
221
+ skippedEntries
222
+ });
223
+ if (entries.length === 0) return nothing(0);
224
+ const coins = await openEntries(entries);
225
+ if (coins.every((coin) => coin === null)) return nothing(entries.length);
226
+ const ledger = await loadLedger();
227
+ const candidates = coins.map((coin, ordinal) => coin && toCandidate(ledger, address, {
228
+ coin,
229
+ entry: entries[ordinal]
230
+ }));
231
+ if (candidates.every((candidate) => candidate === null)) return nothing(entries.length);
232
+ const { outputs, nullifiers } = await chainHistory(address, indexer);
233
+ const held = /* @__PURE__ */ new Map();
234
+ let skippedEntries = 0;
235
+ for (const candidate of candidates) {
236
+ const index = candidate ? outputs.get(candidate.commitment) : void 0;
237
+ if (!candidate || index === void 0) {
238
+ skippedEntries += 1;
239
+ continue;
240
+ }
241
+ if (!held.has(candidate.commitment)) held.set(candidate.commitment, {
242
+ candidate,
243
+ index
244
+ });
245
+ }
246
+ const confirmed = [...held.values()];
247
+ const spent = nullifiers.size === 0 ? confirmed.map(() => false) : coinNullifiers(ledger, address, confirmed.map(({ candidate }) => candidate)).map((nullifier) => nullifiers.has(nullifier));
248
+ const unspent = confirmed.filter((_, position) => !spent[position]);
249
+ return {
250
+ holdings: unspent.map(({ candidate, index }) => ({
251
+ color: require_types.TokenColor(require_bytes_vo.HexBytes.fromByteArray(candidate.coin.color)),
252
+ value: candidate.coin.value.toString(),
253
+ commitment: candidate.commitment,
254
+ index: Number(index)
255
+ })),
256
+ holdingEntries: unspent.map(({ candidate }) => candidate.entry),
257
+ skippedEntries
258
+ };
259
+ };
260
+ //#endregion
261
+ //#region ../../packages/module/passport-account/src/flows/key-session.ts
262
+ /**
263
+ * Runs an operation under a single key ceremony, so a flow that reaches
264
+ * for the device key several times costs one user prompt instead of one
265
+ * per authoriser call. `flow` names the flow for an authoriser that
266
+ * presents it before the ceremony. An authoriser with no interactive
267
+ * ceremony exposes no session, and the operation runs directly. The
268
+ * session function is used detached, so implementations must not rely on
269
+ * `this`.
270
+ */
271
+ const inKeySession = async (authoriser, flow, operation) => {
272
+ const { withKeySession } = authoriser;
273
+ return withKeySession ? withKeySession(operation, flow) : operation();
274
+ };
275
+ //#endregion
276
+ //#region ../../packages/module/passport-account/src/acc/enc-key-rotation.ts
277
+ /** Prepares `rotate_enc_key_with_jubjub` over the raw 32-byte X25519 key `enc_key` is replaced with. */
278
+ const prepareRotateEncKey = (newKey) => ({
279
+ circuit: require_enc_key_circuits.ROTATE_ENC_KEY_CIRCUIT,
280
+ args: [newKey]
281
+ });
282
+ /** Prepares `append_inbox_with_jubjub` over one inbox entry: a re-encrypted coin or a Lace record. */
283
+ const prepareAppendInbox = (entry) => ({
284
+ circuit: require_enc_key_circuits.APPEND_INBOX_CIRCUIT,
285
+ args: [entry]
286
+ });
287
+ //#endregion
288
+ //#region ../../packages/module/passport-account/src/flows/rotation.ts
289
+ /**
290
+ * How long an accepted call is given to show in the indexer's view of the
291
+ * ledger, which can lag the block the call landed in.
292
+ */
293
+ const LEDGER_POLL_ATTEMPTS = 30;
294
+ const LEDGER_POLL_DELAY_MS = 1e3;
295
+ const sleep$1 = async (ms) => new Promise((resolve) => {
296
+ setTimeout(resolve, ms);
297
+ });
298
+ /**
299
+ * The encryption key's view members, which a rotation and a read grant's
300
+ * issue need: the re-encryption a rotation moves the holdings through
301
+ * and the sealing a grantee receives the secret by.
302
+ *
303
+ * @throws {@link ReadUnavailableError} when the key offers either not,
304
+ * as a key that only opens entries does.
305
+ */
306
+ const viewCapability = (encryptionKey) => {
307
+ const { reencryptInbox, sealView } = encryptionKey;
308
+ if (!reencryptInbox || !sealView) throw new require_errors.ReadUnavailableError();
309
+ return {
310
+ reencryptInbox,
311
+ sealView
312
+ };
313
+ };
314
+ /** Resolves this device's reader and device keys, inside the caller's key session. */
315
+ const readLocalDevice = async ({ passportAuthoriser, passportEncryptionKey, passportNetwork }) => ({
316
+ readerPublicKey: await passportEncryptionKey.readerPublicKey(passportNetwork.networkId),
317
+ devicePublicKey: require_envelopes.encodeDevicePublicKey(await passportAuthoriser.devicePublicKey())
318
+ });
319
+ /** Reads the inbox and this device's keyring of the account from its ledger. */
320
+ const readAccountInbox = (account, ledgerState, readerPublicKey) => {
321
+ const entries = inboxEntries(ledgerState);
322
+ return {
323
+ entries,
324
+ keyring: {
325
+ account: require_providers.AccAddress(account),
326
+ encKey: ledgerState.enc_key,
327
+ envelopes: require_envelopes.deviceEnvelopes(entries, readerPublicKey)
328
+ }
329
+ };
330
+ };
331
+ /**
332
+ * The device entries the ledger holds, as lowercase hex, less `except`:
333
+ * the devices a rotation may seal the new secret to, which a key session
334
+ * names to the user before it opens.
335
+ *
336
+ * @throws {@link DeviceRosterInvalidError} when there are more than
337
+ * {@link MAX_DEVICE_ENTRIES}, which no signer would accept.
338
+ */
339
+ const liveDeviceEntries = (ledgerState, except) => {
340
+ const entries = [...ledgerState.devices].map((entry) => require_bytes_vo.HexBytes.fromByteArray(entry)).filter((entry) => entry !== except?.toLowerCase());
341
+ if (entries.length > 64) throw new require_errors.DeviceRosterInvalidError(`The account holds ${entries.length} device entries, more than the 64 a rotation names.`);
342
+ return entries;
343
+ };
344
+ /**
345
+ * The ledger once the indexer reflects a call that advanced the auth
346
+ * nonce past `authNonce`. A chain of gated calls must have each bind the
347
+ * nonce the previous one left behind, which the indexer can serve a
348
+ * moment late.
349
+ *
350
+ * @throws {@link GrantRosterUnsettledError} when the indexer never
351
+ * reflects the call within the polling window.
352
+ */
353
+ const awaitAdvancedLedger = async (indexer, address, authNonce) => {
354
+ for (let attempt = 0; attempt < LEDGER_POLL_ATTEMPTS; attempt++) {
355
+ const ledgerState = await require_account_call.queryAccountLedger(indexer, address);
356
+ if (ledgerState.auth_nonce > authNonce) return ledgerState;
357
+ await sleep$1(LEDGER_POLL_DELAY_MS);
358
+ }
359
+ throw new require_errors.GrantRosterUnsettledError(`The ledger of ${address} never reflected the accepted call.`);
360
+ };
361
+ /**
362
+ * Starts a chain of gated calls at `start`. Each accepted call reports
363
+ * the counter it advanced this device to, then waits for the indexer to
364
+ * reflect it, so the next call binds the auth nonce it left; the local
365
+ * roster row follows the entry each call leaves it on.
366
+ */
367
+ const callChain = ({ account, start, indexer, seams, onProgress, onAdvanced }) => {
368
+ let current = start;
369
+ const submit = async (call) => {
370
+ const { advancedCounter, localCommitmentHex } = await require_account_call.submitAccountCall({
371
+ circuit: call.circuit,
372
+ args: call.args,
373
+ account,
374
+ localUseCounter: current.localUseCounter,
375
+ ledgerState: current.ledgerState,
376
+ onStage: onProgress
377
+ }, seams);
378
+ const localUseCounter = advancedCounter.toString();
379
+ await onAdvanced(localUseCounter);
380
+ current = {
381
+ ledgerState: await awaitAdvancedLedger(indexer, account.address, current.ledgerState.auth_nonce),
382
+ localUseCounter,
383
+ devices: require_account_call.withRefreshedLocalDevice(current.devices, localCommitmentHex)
384
+ };
385
+ };
386
+ return {
387
+ submit,
388
+ append: async (stage, entries) => {
389
+ if (entries.length > 0) onProgress(stage);
390
+ for (const entry of entries) await submit(prepareAppendInbox(entry));
391
+ },
392
+ position: () => current
393
+ };
394
+ };
395
+ /** The registrations of an inbox, by hex. */
396
+ const registrationsOf = (entries) => new Set(entries.filter(require_envelopes.isDeviceRegistration).map((entry) => require_bytes_vo.HexBytes.fromByteArray(entry)));
397
+ /**
398
+ * The roster a rotation seals to: the registrations the live roster key
399
+ * opens, resolved against the ledger's device set so only a registration
400
+ * whose device key derives a live entry counts, and only one whose entry
401
+ * is among `devices`. A registration naming nobody's live device is
402
+ * passed over, and named devices left without a reader, by a conflict or
403
+ * for want of a registration, are reported dropped, see
404
+ * {@link resolveLiveReaders}; this device, sealed to regardless, is never
405
+ * reported dropped. The registration whose live entry is `target`, the
406
+ * entry a removal takes out, is named so no reader paired with its device
407
+ * is sealed to.
408
+ */
409
+ const sealedRoster = async ({ account, encryptionKey, networkId, local, ledgerState, inbox, devices, target, localEntry }) => {
410
+ const registered = (await encryptionKey.openInbox(networkId, inbox.keyring, inbox.entries.filter(require_envelopes.isDeviceRegistration))).flatMap((opened) => opened?.kind === "device" ? [opened.device] : []);
411
+ const self = require_bytes_vo.HexBytes.fromByteArray(local.readerPublicKey);
412
+ const { readers, dropped, outside } = require_device_readers.resolveLiveReaders({
413
+ scope: {
414
+ address: account.address,
415
+ epoch: ledgerState.device_epoch
416
+ },
417
+ registered,
418
+ isLive: (entryHex) => ledgerState.devices.member(require_bytes_vo.ByteArray.fromHex(require_bytes_vo.HexBytes(entryHex))),
419
+ allowed: new Set(devices),
420
+ target
421
+ });
422
+ const removed = outside.find(({ entryHex }) => entryHex === target);
423
+ return {
424
+ readers: readers.filter(({ reader }) => reader.readerPublicKey !== self).map(({ reader }) => reader),
425
+ dropped: dropped.filter((entry) => entry !== localEntry),
426
+ ...removed ? { removed: {
427
+ devicePublicKey: removed.reader.devicePublicKey,
428
+ epoch: removed.reader.epoch,
429
+ counter: removed.reader.counter
430
+ } } : {}
431
+ };
432
+ };
433
+ /**
434
+ * Rotates the account encryption key to a fresh random secret and
435
+ * re-encrypts every live holding towards it, as the scoped grants
436
+ * standard requires before a read grant is issued and after one is
437
+ * revoked, and as a device removal requires so the removed device reads
438
+ * nothing newer. The holdings are walked with the keyring of the ledger's
439
+ * `enc_key` from the position the chain is at, a keyring this device's
440
+ * envelopes do not open rejecting with EncryptionKeyMismatchError before
441
+ * anything is submitted. The encryption key draws the new secret and
442
+ * seals it, with the roster key, to this device and to every registered
443
+ * device that is enrolled now and among `devices`, and answers the
444
+ * ciphertexts alone: one envelope per device and, when the roster key is
445
+ * fresh, one registration each. Should any registration the live roster
446
+ * key opens land while the rotation runs, as another device's enrolment
447
+ * does, the rotation refuses with DeviceRosterInvalidError before
448
+ * `enc_key` is replaced, so a device enrolled meanwhile is never left
449
+ * without the new key; registrations that open under no roster key are
450
+ * noise and do not stop it. Devices whose registrations conflict are
451
+ * left out and reported in the result. The holdings are
452
+ * re-encrypted towards the new key, the envelopes and registrations are
453
+ * appended, `enc_key` is replaced through `rotate_enc_key_with_jubjub`,
454
+ * and each re-encrypted holding is appended, one gated call each, all in
455
+ * the key session the caller holds. The envelopes land ahead of the
456
+ * rotation, so no device is left without the key the chain then holds;
457
+ * a rotation interrupted after it leaves holdings under the previous
458
+ * key, which every device still opens from its older envelopes and the
459
+ * next rotation re-encrypts. The previous secret is never derived again
460
+ * by anyone: entries sealed towards it stay readable to whoever held it,
461
+ * and nothing newer is.
462
+ */
463
+ const rotateEncKey = async ({ account, encryptionKey, view, local, chain, indexer, networkId, onProgress, devices, rotateRoster, target }) => {
464
+ const { ledgerState } = chain.position();
465
+ const inbox = readAccountInbox(account.address, ledgerState, local.readerPublicKey);
466
+ const { entries, keyring } = inbox;
467
+ const { holdingEntries } = await walkInbox({
468
+ address: account.address,
469
+ ledgerState,
470
+ indexer,
471
+ openEntries: async (coinEntries) => (await encryptionKey.openInbox(networkId, keyring, coinEntries)).map((opened) => opened?.kind === "coin" ? opened.coin : null)
472
+ });
473
+ const { readers, dropped, removed } = await sealedRoster({
474
+ account,
475
+ encryptionKey,
476
+ networkId,
477
+ local,
478
+ ledgerState,
479
+ inbox,
480
+ devices,
481
+ target,
482
+ localEntry: chain.position().devices.find((device) => device.isLocal)?.commitmentHex.toLowerCase()
483
+ });
484
+ const keys = await encryptionKey.createKeys(networkId, {
485
+ devicePublicKey: local.devicePublicKey,
486
+ keyring,
487
+ registrations: entries.filter(require_envelopes.isDeviceRegistration),
488
+ readers,
489
+ ...rotateRoster === void 0 ? {} : { rotateRoster },
490
+ ...removed ? { removed } : {}
491
+ });
492
+ const reencrypted = (await view.reencryptInbox(networkId, {
493
+ keyring,
494
+ to: keys.encKey
495
+ }, holdingEntries)).filter((entry) => entry !== null);
496
+ onProgress("rotating");
497
+ for (const entry of [...keys.envelopes, ...keys.registrations]) await chain.submit(prepareAppendInbox(entry));
498
+ const before = registrationsOf(entries);
499
+ const appended = registrationsOf(keys.registrations);
500
+ const landed = inboxEntries(chain.position().ledgerState).filter((entry) => require_envelopes.isDeviceRegistration(entry) && !before.has(require_bytes_vo.HexBytes.fromByteArray(entry)) && !appended.has(require_bytes_vo.HexBytes.fromByteArray(entry)));
501
+ if (landed.length > 0 && (await encryptionKey.openInbox(networkId, keyring, landed)).some((opened) => opened?.kind === "device")) throw new require_errors.DeviceRosterInvalidError(`Registrations landed in the inbox of ${account.address} while its keys rotated; rotate again.`);
502
+ await chain.submit(prepareRotateEncKey(keys.encKey));
503
+ await chain.append("reencrypting", reencrypted);
504
+ return {
505
+ encKey: keys.encKey,
506
+ dropped,
507
+ authority: keys.authority,
508
+ keyring: {
509
+ account: require_providers.AccAddress(account.address),
510
+ encKey: keys.encKey,
511
+ envelopes: keys.envelopes.filter((envelope) => require_envelopes.isAddressedTo(envelope, local.readerPublicKey))
512
+ }
513
+ };
514
+ };
515
+ /**
516
+ * Seals the live secret of `keyring` to each read grant's read key, with
517
+ * the grant record that keeps each seal on chain for this device. A
518
+ * grant the key refuses to seal to, an expired key session included, is
519
+ * listed unsealed rather than failing a rotation that already landed, or
520
+ * as revocation pending when the key refuses it with
521
+ * GrantRevocationPendingError.
522
+ */
523
+ const sealViews = async ({ view, networkId, keyring, account, targets }) => {
524
+ const sealedEncKey = require_bytes_vo.HexBytes.fromByteArray(keyring.encKey);
525
+ const result = {
526
+ sealed: [],
527
+ unsealed: [],
528
+ revocationPending: [],
529
+ entries: []
530
+ };
531
+ for (const target of targets) {
532
+ let refusal;
533
+ const delivery = await view.sealView(networkId, keyring, {
534
+ ...target,
535
+ account
536
+ }).catch((error) => {
537
+ refusal = error;
538
+ });
539
+ if (!delivery) {
540
+ (refusal instanceof require_errors.GrantRevocationPendingError ? result.revocationPending : result.unsealed).push(target.grantId);
541
+ continue;
542
+ }
543
+ result.sealed.push({
544
+ ...target,
545
+ seal: require_bytes_vo.HexBytes.fromByteArray(delivery.seal),
546
+ sealedEncKey
547
+ });
548
+ result.entries.push(delivery.entry);
549
+ }
550
+ return result;
551
+ };
552
+ //#endregion
553
+ //#region ../../packages/module/passport-account/src/flows/stored-grants.ts
554
+ /**
555
+ * The slot each stored grant was derived under, keyed by id, as the
556
+ * roster decoder takes them.
557
+ */
558
+ const storedGrantSlots = (stored) => new Map(Object.entries(stored ?? {}).map(([id, { slot }]) => [require_grant_rules.GrantId(id), require_grant_rules.GrantSlot(slot)]));
559
+ /**
560
+ * The opening of each stored grant whose object fields were kept, keyed by
561
+ * id, as the roster decoder attaches them. A read grant whose seal this
562
+ * client holds carries it in the opening's view, with the `enc_key` whose
563
+ * secret it seals.
564
+ */
565
+ const storedGrantOpenings = (stored) => new Map(Object.entries(stored ?? {}).flatMap(([id, grant]) => grant.object ? [[require_grant_rules.GrantId(id), {
566
+ scopeSalt: require_grant_rules.GrantScopeSalt(grant.scopeSalt),
567
+ granteeScheme: require_types.storedGranteeScheme(grant),
568
+ granteePublicKey: grant.granteePublicKey,
569
+ origin: grant.origin,
570
+ color: require_types.TokenColor(grant.object.color),
571
+ recipient: grant.object.recipient,
572
+ maxCoinValue: grant.object.maxCoinValue,
573
+ ...grant.view?.seal === void 0 || grant.view.sealedEncKey === void 0 ? {} : { view: {
574
+ readPublicKey: grant.view.readPublicKey,
575
+ seal: grant.view.seal,
576
+ sealedEncKey: grant.view.sealedEncKey
577
+ } }
578
+ }]] : []));
579
+ /**
580
+ * The stored grants with the view of one grant replaced: its read key
581
+ * and, when given, the seal towards it of the secret of `sealedEncKey`.
582
+ */
583
+ const withStoredGrantView = (stored, grantId, view) => {
584
+ const grant = stored[grantId];
585
+ if (!grant) return stored;
586
+ return {
587
+ ...stored,
588
+ [grantId]: {
589
+ ...grant,
590
+ view
591
+ }
592
+ };
593
+ };
594
+ /**
595
+ * The read grants this client holds the read key of and the chain still
596
+ * lists active, other than `except`: the grants a rotation seals the new
597
+ * secret to again. A read grant issued by another client has no stored
598
+ * key here, so it is not among them.
599
+ */
600
+ const liveStoredReadGrants = (stored, roster, except) => {
601
+ const active = new Set(roster.filter((record) => record.active && record.scope.read).map(({ id }) => id));
602
+ return Object.entries(stored ?? {}).flatMap(([id, grant]) => grant.view && active.has(require_grant_rules.GrantId(id)) && id !== except ? [{
603
+ grantId: require_grant_rules.GrantId(id),
604
+ readPublicKey: grant.view.readPublicKey
605
+ }] : []);
606
+ };
607
+ /**
608
+ * The active read grants this client holds the read key of whose stored
609
+ * seal does not hold the secret of `encKey`, the ledger's current one:
610
+ * their grantees read nothing newer until a rotation seals to them again
611
+ * or they are issued again. A read grant issued by another client has no
612
+ * stored key here, so it is not among them.
613
+ */
614
+ const unsealedStoredReadGrants = (stored, roster, encKey) => liveStoredReadGrants(stored, roster).filter(({ grantId }) => stored?.[grantId]?.view?.sealedEncKey !== encKey).map(({ grantId }) => grantId);
615
+ /** The stored grants with one more entry, under its grant id. */
616
+ const withStoredGrant = (stored, grantId, grant) => ({
617
+ ...stored,
618
+ [grantId]: grant
619
+ });
620
+ /**
621
+ * The record a completed account persists: its identity, this device's
622
+ * use counter, the grant bookkeeping when there is any, and the roster of
623
+ * devices the account keys were sealed to when it is known. It never
624
+ * carries a deployment, so a write of it completes a pending record.
625
+ */
626
+ const completedRecord = ({ address, bindingVersion }, localUseCounter, { grants }) => ({
627
+ address,
628
+ bindingVersion,
629
+ localUseCounter,
630
+ ...grants ? { grants } : {}
631
+ });
632
+ /**
633
+ * The stored grants with the seals of a rotation applied: each sealed
634
+ * grant's view carries its new seal and the `enc_key` it holds, and each
635
+ * unsealed one keeps its read key alone.
636
+ */
637
+ const withSealedViews = (stored, { sealed, unsealed }) => {
638
+ if (!stored) return stored;
639
+ let next = stored;
640
+ for (const { grantId, readPublicKey, seal, sealedEncKey } of sealed) next = withStoredGrantView(next, grantId, {
641
+ readPublicKey,
642
+ seal,
643
+ sealedEncKey
644
+ });
645
+ for (const grantId of unsealed) {
646
+ const readPublicKey = next[grantId]?.view?.readPublicKey;
647
+ if (readPublicKey !== void 0) next = withStoredGrantView(next, grantId, { readPublicKey });
648
+ }
649
+ return next;
650
+ };
651
+ /** Each grant's parts by id, the later of two parts of a kind winning. */
652
+ const partsByGrant = (parts) => {
653
+ const byGrant = /* @__PURE__ */ new Map();
654
+ for (const part of parts) {
655
+ const held = byGrant.get(part.grantId) ?? {};
656
+ byGrant.set(part.grantId, {
657
+ ...held,
658
+ [part.part]: part
659
+ });
660
+ }
661
+ return byGrant;
662
+ };
663
+ /**
664
+ * Whether a grant's bookkeeping opens its on-chain commitments under the
665
+ * account at `address`: the id derives from the account, the grantee
666
+ * key under its scheme, the origin and the slot, the object commitment
667
+ * from the salt and the object fields, and the read key hashes to the
668
+ * one the contract holds, or is absent for a grant that does not read.
669
+ * Anyone can append any bytes to the inbox, and another client of the
670
+ * account issuing to the same grantee from the same origin lands on the
671
+ * same id with its own salt, so this check, not where the bookkeeping
672
+ * came from, is what makes it trusted.
673
+ */
674
+ const opensOnChain = async (record, fields, address) => {
675
+ try {
676
+ const grantee = {
677
+ scheme: fields.granteeScheme,
678
+ publicKey: fields.granteePublicKey
679
+ };
680
+ const grantId = require_grants.deriveGrantId({
681
+ contractAddress: require_bytes_vo.ByteArray.fromHex(require_bytes_vo.HexBytes(address)),
682
+ granteePublicKey: require_grant_rules.granteePoint(grantee),
683
+ originHash: await require_grants.originHash(fields.origin),
684
+ slot: require_grant_rules.GrantSlot(fields.slot)
685
+ });
686
+ const encoded = require_grant_rules.circuitScope({
687
+ grantee,
688
+ operations: [],
689
+ color: require_types.TokenColor(fields.object.color),
690
+ recipient: fields.object.recipient,
691
+ maxCoinValue: fields.object.maxCoinValue,
692
+ perCallCap: "0",
693
+ cap: "0",
694
+ expiresAt: null,
695
+ ...fields.readPublicKey === void 0 ? {} : { readPublicKey: fields.readPublicKey }
696
+ });
697
+ return grantId === record.id && require_grants.grantObjectCommit(require_bytes_vo.ByteArray.fromHex(require_bytes_vo.HexBytes(fields.scopeSalt)), encoded) === record.scope.objectCommit && require_bytes_vo.HexBytes.fromByteArray(encoded.readPkHash) === record.scope.readPkHash && fields.readPublicKey !== void 0 === record.scope.read;
698
+ } catch {
699
+ return false;
700
+ }
701
+ };
702
+ /**
703
+ * The stored grants narrowed to the ones that still open what the chain
704
+ * holds under their id, or undefined once none is left. A grant the
705
+ * chain no longer holds is dropped, and so is one whose kept fields do
706
+ * not open its commitments: the entry an issue wrote before a call that
707
+ * never landed, once another client of the account issued over the same
708
+ * id, or an entry written before the object fields were kept, which
709
+ * cannot be checked. A revoked grant stays in the contract's map as a
710
+ * tombstone, so its slot and salt are kept until a revoke-all clears the
711
+ * map or a new grant overwrites the id.
712
+ */
713
+ const retainStoredGrants = async (stored, roster, address) => {
714
+ if (!stored) return void 0;
715
+ const retained = {};
716
+ for (const record of roster) {
717
+ const grant = stored[record.id];
718
+ if (!grant?.object) continue;
719
+ if (await opensOnChain(record, {
720
+ ...grant,
721
+ granteeScheme: require_types.storedGranteeScheme(grant),
722
+ object: grant.object,
723
+ ...grant.view === void 0 ? {} : { readPublicKey: grant.view.readPublicKey }
724
+ }, address)) retained[record.id] = grant;
725
+ }
726
+ return Object.keys(retained).length > 0 ? retained : void 0;
727
+ };
728
+ /**
729
+ * The bookkeeping of grants this device issued, rebuilt from the grant
730
+ * records it opened in the inbox, for the grants the chain still holds:
731
+ * each grant whose parts open its on-chain commitments under this
732
+ * account and origin, with its read key and, when the newest view record
733
+ * holds the seal of the ledger's `enc_key`, that seal. A grant whose
734
+ * parts do not check out, or whose grant or object part is missing, is
735
+ * left out. Undefined when nothing is rebuilt.
736
+ */
737
+ const rebuildStoredGrants = async (parts, roster, scope) => {
738
+ const byGrant = partsByGrant(parts);
739
+ const rebuilt = {};
740
+ for (const record of roster) {
741
+ const held = byGrant.get(record.id);
742
+ if (!held?.grant || !held.object) continue;
743
+ const { grant, object, view } = held;
744
+ if (!await opensOnChain(record, {
745
+ ...grant,
746
+ origin: scope.origin,
747
+ object: object.object
748
+ }, scope.address)) continue;
749
+ const readPublicKey = grant.readPublicKey;
750
+ const isCurrent = view !== void 0 && scope.encKey.startsWith(view.encKeyPrefix);
751
+ rebuilt[record.id] = {
752
+ slot: grant.slot,
753
+ scopeSalt: grant.scopeSalt,
754
+ granteeScheme: grant.granteeScheme,
755
+ granteePublicKey: grant.granteePublicKey,
756
+ origin: scope.origin,
757
+ object: object.object,
758
+ ...readPublicKey === void 0 ? {} : { view: isCurrent ? {
759
+ readPublicKey,
760
+ seal: view.seal,
761
+ sealedEncKey: scope.encKey
762
+ } : { readPublicKey } }
763
+ };
764
+ }
765
+ return Object.keys(rebuilt).length > 0 ? rebuilt : void 0;
766
+ };
767
+ /**
768
+ * The record's bookkeeping with every rebuilt grant it lacks added: what
769
+ * the record holds is kept, so a record that survived is never
770
+ * overwritten by the chain's copy, except for a view whose seal is not of
771
+ * `encKey`, the ledger's current one, when the rebuilt view's seal is:
772
+ * the record write after a rotation can be lost while its seal reached
773
+ * the inbox. Run over the bookkeeping {@link retainStoredGrants} checked,
774
+ * a rebuilt grant takes the place of an entry that did not open.
775
+ */
776
+ const withRebuiltGrants = (stored, rebuilt, encKey) => {
777
+ if (!rebuilt) return stored;
778
+ const merged = {
779
+ ...rebuilt,
780
+ ...stored
781
+ };
782
+ for (const [id, fromChain] of Object.entries(rebuilt)) {
783
+ const held = stored?.[id];
784
+ if (held && held.view?.sealedEncKey !== encKey && fromChain.view?.sealedEncKey === encKey) merged[id] = {
785
+ ...held,
786
+ view: fromChain.view
787
+ };
788
+ }
789
+ return merged;
790
+ };
791
+ //#endregion
792
+ //#region ../../packages/module/passport-account/src/flows/create-account.ts
793
+ /** The initial device entry is inserted at epoch 0 with use counter 0. */
794
+ const INITIAL_DEVICE_EPOCH = require_gated_call.DeviceEpoch(0n);
795
+ const INITIAL_USE_COUNTER_VALUE = "0";
796
+ /**
797
+ * The record of a creation left unfinished on this device, read inside
798
+ * the session once presence reported one. A record that opens without a
799
+ * pending deployment, or that this authoriser cannot open, refuses with
800
+ * AccountExistsError: it names an account that must not be created over.
801
+ * A record that carries only the marker of its creation, which a hosted
802
+ * signer shows no flow but the creation, cannot be resumed and is
803
+ * refused, as is one started under another lock choice than
804
+ * `lockAccount`, with CreationLockMismatchError naming the recorded one.
805
+ */
806
+ const pendingRecord = async (accountRecords, lockAccount) => {
807
+ const record = await accountRecords.read();
808
+ if (!record?.deployment) throw new require_errors.AccountExistsError();
809
+ if (!isPendingDeployment(record.deployment)) throw new Error(`The unfinished creation of the account at ${record.address} was read without its deployment, so it cannot be resumed.`);
810
+ if (record.deployment.lockAccount !== lockAccount) throw lockChoiceMismatch(record.deployment.lockAccount);
811
+ return {
812
+ ...record,
813
+ deployment: record.deployment
814
+ };
815
+ };
816
+ /**
817
+ * Runs the deployment: a fresh one, which draws the account keys first,
818
+ * deploys with their `enc_key`, hands an unlocked account's maintenance
819
+ * to the authority their roster key derives, and records the pending
820
+ * deployment, with this device's envelope and registration, as soon as
821
+ * the deploy lands so an interruption can be resumed; or the remaining
822
+ * waves of a recorded one, with the key entries and the handover
823
+ * authority it recorded. Both wait for the fee sponsor to be ready
824
+ * before building each transaction.
825
+ */
826
+ const deploy = async ({ providers, lockAccount, pending, local, context: { seams, accountRecords } }) => {
827
+ const { passportAuthoriser, passportEncryptionKey, passportNetwork, passportSponsor } = seams;
828
+ const sponsorReady = async () => passportSponsor.ready?.();
829
+ if (pending) return {
830
+ deployed: await require_gated_call.resumeAccountDeployment({
831
+ providers,
832
+ authoriser: passportAuthoriser,
833
+ address: pending.address,
834
+ pending: pending.deployment,
835
+ sponsorReady
836
+ }),
837
+ pending: pending.deployment
838
+ };
839
+ const keys = await passportEncryptionKey.createKeys(passportNetwork.networkId, { devicePublicKey: local.devicePublicKey });
840
+ const keyEntries = [...keys.envelopes, ...keys.registrations].map((entry) => require_bytes_vo.HexBytes.fromByteArray(entry));
841
+ const progress = {};
842
+ const deployed = await require_gated_call.deployAccount({
843
+ providers,
844
+ authoriser: passportAuthoriser,
845
+ encPublicKey: keys.encKey,
846
+ lockAccount,
847
+ handoverAuthority: keys.authority,
848
+ sponsorReady,
849
+ onDeployed: async (address, deployment) => {
850
+ progress.recorded = {
851
+ ...deployment,
852
+ keyEntries
853
+ };
854
+ await accountRecords.write({
855
+ address,
856
+ bindingVersion: require_artefact_loader.accManifest.bindingVersion,
857
+ localUseCounter: INITIAL_USE_COUNTER_VALUE,
858
+ deployment: progress.recorded
859
+ });
860
+ }
861
+ });
862
+ if (!progress.recorded) throw new Error("The deployment landed without reporting its progress.");
863
+ return {
864
+ deployed,
865
+ pending: progress.recorded
866
+ };
867
+ };
868
+ /**
869
+ * Creates a Passport account: the passkey ceremony (implicit in the first
870
+ * authoriser use), the account keys drawn through the encryption key
871
+ * seam, the Account Custody Contract deployment in waves with sponsored
872
+ * fees, the initial device activation, the delivery of the account keys
873
+ * to this device's inbox, and the local persistence of the account
874
+ * record. Every step runs inside one key session, so the whole flow costs
875
+ * a single ceremony rather than one per authoriser call. The flow never
876
+ * holds the encryption secret: the seam draws it at random, seals it to
877
+ * this device's reader key and hands back only its public key, which the
878
+ * constructor takes as `enc_key`, with this device's envelope and its
879
+ * registration under a fresh roster key; once the device is active, both
880
+ * are appended to the inbox, one gated call each, so the account keys
881
+ * live on chain from the start.
882
+ *
883
+ * Progress is reported through `onProgress`: 'deploying' once the
884
+ * authoriser has produced the device key, 'proving' and 'sponsoring' from
885
+ * inside each transaction submission (a transaction proves first, then
886
+ * the sponsor balances it), 'activating' before the activation call and
887
+ * 'recording' before the key entries are appended. The record is written
888
+ * before the promise resolves, so a process that dies right after still
889
+ * recognises the account on next startup.
890
+ *
891
+ * The flow runs to completion once started: cancelling mid-deploy could
892
+ * orphan a contract the sponsor already paid for. The caller's flow gate
893
+ * is what keeps a second request from starting one. Should the process
894
+ * die between waves anyway, the record written when the deploy landed
895
+ * names the contract, what it still needs and the key entries, and the
896
+ * next create request on this device resumes from the operations the
897
+ * chain holds, activates once every wave is in and appends whichever key
898
+ * entry the inbox still lacks; a creation that died after its activation
899
+ * landed resumes from there. A resumed creation finishes exactly as it
900
+ * began, with the keys recorded then: a request under another lock choice
901
+ * than the recorded one is refused with CreationLockMismatchError, and a
902
+ * build pinning other verifier keys than the recorded deployment with
903
+ * CreationArtefactMismatchError, both before any wave. The session names
904
+ * the lock choice, so an authoriser that asks consent first shows it; a
905
+ * hosted signer refuses the same lock mismatch itself, and a resume
906
+ * started from another origin than the creation it resumes.
907
+ *
908
+ * A device already holding a completed account record refuses with
909
+ * AccountExistsError before the ceremony runs: the record is the only
910
+ * pointer to the deployed contract and there is no recovery path, so
911
+ * creating over it would orphan the existing account. Presence is read
912
+ * without opening the record, and it tells a completed record from an
913
+ * unfinished one, so a sealed record blocks the flow without costing a
914
+ * prompt and only a record the flow may resume is opened, inside the
915
+ * session, where it costs no extra prompt.
916
+ */
917
+ const createAccount = async ({ lockAccount }, context) => {
918
+ const { seams, accountRecords, onProgress } = context;
919
+ const presence = await accountRecords.presence();
920
+ if (presence === "complete") throw new require_errors.AccountExistsError();
921
+ const { passportAuthoriser, passportNetwork, passportProver, passportSponsor } = seams;
922
+ return inKeySession(passportAuthoriser, {
923
+ kind: "create-account",
924
+ lockAccount
925
+ }, async () => {
926
+ const local = await readLocalDevice(seams);
927
+ const pending = presence === "pending" ? await pendingRecord(accountRecords, lockAccount) : void 0;
928
+ onProgress("deploying");
929
+ const { deployed, pending: deployment } = await deploy({
930
+ providers: await require_providers.createMidnightProviders({
931
+ network: passportNetwork,
932
+ prover: passportProver,
933
+ sponsor: passportSponsor,
934
+ onStage: onProgress
935
+ }),
936
+ lockAccount,
937
+ pending,
938
+ local,
939
+ context
940
+ });
941
+ if (!deployed.isActivated) {
942
+ onProgress("activating");
943
+ await deployed.activate();
944
+ }
945
+ const account = {
946
+ address: deployed.address,
947
+ bindingVersion: require_artefact_loader.accManifest.bindingVersion,
948
+ status: "ready",
949
+ networkId: passportNetwork.networkId
950
+ };
951
+ const startCounter = pending?.localUseCounter ?? INITIAL_USE_COUNTER_VALUE;
952
+ const { indexer, ledgerState } = await require_account_call.readAccountLedger(deployed.address, passportNetwork);
953
+ const held = new Set(inboxEntries(ledgerState).map((entry) => require_bytes_vo.HexBytes.fromByteArray(entry)));
954
+ const chain = callChain({
955
+ account,
956
+ start: {
957
+ ledgerState,
958
+ localUseCounter: startCounter,
959
+ devices: [{
960
+ commitmentHex: await passportAuthoriser.deviceCommitment(require_providers.AccAddress(deployed.address), INITIAL_DEVICE_EPOCH, require_gated_call.UseCounter(BigInt(startCounter))),
961
+ isLocal: true
962
+ }]
963
+ },
964
+ indexer,
965
+ seams,
966
+ onProgress,
967
+ onAdvanced: async (localUseCounter) => accountRecords.write({
968
+ address: account.address,
969
+ bindingVersion: account.bindingVersion,
970
+ localUseCounter,
971
+ deployment
972
+ })
973
+ });
974
+ await chain.append("recording", deployment.keyEntries.filter((entry) => !held.has(entry)).map((entry) => require_bytes_vo.ByteArray.fromHex(require_bytes_vo.HexBytes(entry))));
975
+ const { localUseCounter, devices } = chain.position();
976
+ const counter = localUseCounter ?? startCounter;
977
+ await accountRecords.write(completedRecord(account, counter, {}));
978
+ return {
979
+ account,
980
+ devices,
981
+ localUseCounter: counter,
982
+ grants: [],
983
+ encKey: require_bytes_vo.HexBytes.fromByteArray(ledgerState.enc_key)
984
+ };
985
+ });
986
+ };
987
+ //#endregion
988
+ //#region ../../packages/module/passport-account/src/acc/read-grants.ts
989
+ const NO_SLOTS = /* @__PURE__ */ new Map();
990
+ const NO_OPENINGS = /* @__PURE__ */ new Map();
991
+ /** The slot reported for a grant whose slot this client never learned. */
992
+ const UNKNOWN_SLOT = require_grant_rules.GrantSlot(0);
993
+ const toNumber = (field, value) => {
994
+ if (value > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error(`Grant field ${field} exceeds the safe integer range`);
995
+ return Number(value);
996
+ };
997
+ const toOperations = (scope) => {
998
+ const operations = [];
999
+ if (scope.op_withdraw_unshielded) operations.push("withdraw-unshielded");
1000
+ if (scope.op_withdraw_shielded) operations.push("withdraw-shielded");
1001
+ if (scope.op_withdraw_shielded_to_contract) operations.push("withdraw-shielded-to-contract");
1002
+ return operations;
1003
+ };
1004
+ const toScope = (scope) => ({
1005
+ operations: toOperations(scope),
1006
+ read: scope.read,
1007
+ objectCommit: require_bytes_vo.HexBytes.fromByteArray(scope.object_commit),
1008
+ perCallCap: scope.per_call_cap.toString(),
1009
+ cap: scope.cap.toString(),
1010
+ expiresAt: scope.expires_at === 0n ? null : toNumber("expires_at", scope.expires_at),
1011
+ rpCommit: require_bytes_vo.HexBytes.fromByteArray(scope.rp_commit),
1012
+ readPkHash: require_bytes_vo.HexBytes.fromByteArray(scope.read_pk_hash),
1013
+ window: scope.window_len === 0n ? null : {
1014
+ lengthSeconds: toNumber("window_len", scope.window_len),
1015
+ cap: scope.window_cap.toString()
1016
+ },
1017
+ callerCommit: require_bytes_vo.HexBytes.fromByteArray(scope.caller_commit)
1018
+ });
1019
+ const toRecord = (id, record, { slot, opening }) => ({
1020
+ id,
1021
+ slot,
1022
+ active: record.active,
1023
+ epoch: toNumber("epoch", record.epoch),
1024
+ generation: toNumber("gen", record.gen),
1025
+ issuedAt: toNumber("issued_at", record.issued_at),
1026
+ scope: toScope(record.scope),
1027
+ spentWindow: {
1028
+ start: toNumber("window_start", record.window_start),
1029
+ spent: record.window_spent.toString()
1030
+ },
1031
+ ...opening ? { opening } : {}
1032
+ });
1033
+ /**
1034
+ * Decodes the account's grant roster from the ledger's grants map, in the
1035
+ * map's order. Plain fields decode to the read model's numbers and
1036
+ * decimal strings, commitments stay as hex, and an expiry or window of
1037
+ * zero reads as none. A revoked grant stays in the map as an inactive
1038
+ * tombstone and is listed as such. The contract stores no slot: the
1039
+ * caller supplies the slots of the grants it issued, and a grant issued by
1040
+ * another client of the account reports slot 0. The openings the caller
1041
+ * holds are attached to their grants.
1042
+ */
1043
+ const readGrants = (ledgerState, slots = NO_SLOTS, openings = NO_OPENINGS) => Array.from(ledgerState.grants, ([rawId, record]) => {
1044
+ const id = require_grant_rules.GrantId(require_bytes_vo.HexBytes.fromByteArray(rawId));
1045
+ return toRecord(id, record, {
1046
+ slot: slots.get(id) ?? UNKNOWN_SLOT,
1047
+ opening: openings.get(id)
1048
+ });
1049
+ });
1050
+ //#endregion
1051
+ //#region ../../packages/module/passport-account/src/flows/maintenance.ts
1052
+ /** Loads the ledger and the network id the update is built and submitted with. */
1053
+ const importLedgerModules = async () => Promise.all([import("@midnightntwrk/ledger-v9"), import("@midnight-ntwrk/midnight-js-network-id")]);
1054
+ /**
1055
+ * The maintenance authority of the account at `address` as `publicData`
1056
+ * serves it. An update must read its counter through the provider its
1057
+ * submission polls, so a lagging read cannot hand the next update a
1058
+ * counter the chain already moved past.
1059
+ */
1060
+ const authorityOnChain = async (ledger, publicData, address) => require_gated_call.readMaintenanceAuthority({
1061
+ providers: { publicDataProvider: publicData },
1062
+ ledger,
1063
+ address
1064
+ });
1065
+ /** The refusal of a key that cannot sign over an account whose authority is live. */
1066
+ const missingSigner = (address) => new require_errors.MaintenanceUpdateRefusedError(`The encryption key held here cannot sign maintenance updates, so the maintenance authority of the account at ${address} cannot follow its roster.`);
1067
+ /**
1068
+ * Refuses a roster rotation the maintenance authority could not follow,
1069
+ * before any prompt: an encryption key that signs no maintenance update
1070
+ * over an account whose authority is live. A locked account, whose
1071
+ * committee is empty, has no authority to follow.
1072
+ *
1073
+ * @throws {@link MaintenanceUpdateRefusedError} when the key cannot sign
1074
+ * and the account's authority is live.
1075
+ */
1076
+ const assertMaintenanceSigner = async ({ address, indexer, encryptionKey }) => {
1077
+ if (encryptionKey.signMaintenanceUpdate) return;
1078
+ const [ledger] = await importLedgerModules();
1079
+ const { committee } = await authorityOnChain(ledger, indexer, address);
1080
+ if (committee.length > 0) throw missingSigner(address);
1081
+ };
1082
+ /** The refusal of a key that cannot sign the install of missing operations. */
1083
+ const missingInstallSigner = (address) => new require_errors.MaintenanceUpdateRefusedError(`The encryption key held here cannot sign maintenance updates, so no operation can be installed on the account at ${address}.`);
1084
+ /** The refusal of an install on an account whose authority was retired. */
1085
+ const lockedAccount = (address) => new require_errors.AuthorityRetiredError(`The account at ${address} is locked: its maintenance authority is retired, so no operation can be installed.`);
1086
+ /**
1087
+ * The one key of a live committee, which some roster key must derive.
1088
+ *
1089
+ * @throws {@link MaintenanceAuthorityMismatchError} when the committee
1090
+ * is not one key.
1091
+ */
1092
+ const soleMember = (address, committee) => {
1093
+ if (committee.length !== 1) throw new require_errors.MaintenanceAuthorityMismatchError(`The maintenance authority of the account at ${address} is a committee of ${committee.length} keys, not one a roster key derives.`);
1094
+ const [{ tag, value }] = committee;
1095
+ return {
1096
+ tag,
1097
+ value
1098
+ };
1099
+ };
1100
+ /**
1101
+ * Refuses a roster rotation whose move of the maintenance authority would
1102
+ * fail, inside the key session and before anything is submitted: an
1103
+ * unlocked account whose one-key committee no roster key of the keyring
1104
+ * derives. A locked account, whose committee is empty, has no authority
1105
+ * to move. The encryption key answers only whether the keyring holds the
1106
+ * authority; a key that cannot answer leaves the refusal to the move.
1107
+ *
1108
+ * @throws {@link MaintenanceAuthorityMismatchError} when the committee
1109
+ * is not one key some roster key of the keyring derives.
1110
+ */
1111
+ const assertHoldsRosterAuthority = async ({ address, seams, indexer, keyring }) => {
1112
+ const holds = seams.passportEncryptionKey.holdsMaintenanceAuthority;
1113
+ if (!holds) return;
1114
+ const [ledger] = await importLedgerModules();
1115
+ const { committee } = await authorityOnChain(ledger, indexer, address);
1116
+ if (committee.length === 0) return;
1117
+ const authority = soleMember(address, committee);
1118
+ if (!await holds(seams.passportNetwork.networkId, keyring, authority)) throw new require_errors.MaintenanceAuthorityMismatchError(`No roster key this device holds derives the maintenance authority of the account at ${address}, so the roster cannot rotate.`);
1119
+ };
1120
+ /**
1121
+ * Has the encryption key sign `changes` over the keyring with the roster
1122
+ * key whose authority the chain holds, rebuilds the update as it was
1123
+ * signed and submits it, resolving once the chain shows the advanced
1124
+ * counter.
1125
+ */
1126
+ const signAndSubmit = async ({ ledger, networkId, providers, address, seams, sign, keyring, authority, counter, changes, label }) => {
1127
+ const { signature } = await sign(seams.passportNetwork.networkId, {
1128
+ keyring,
1129
+ authority,
1130
+ counter,
1131
+ changes
1132
+ });
1133
+ await require_gated_call.submitMaintenanceUpdate({
1134
+ providers,
1135
+ ledger,
1136
+ address,
1137
+ networkId,
1138
+ update: require_submit.maintenanceUpdateOf(ledger, {
1139
+ address: keyring.account,
1140
+ counter,
1141
+ changes
1142
+ }),
1143
+ signature,
1144
+ label
1145
+ });
1146
+ };
1147
+ /**
1148
+ * Moves the maintenance authority of an unlocked account to the one the
1149
+ * new live roster key derives, after a rotation drew a fresh roster key,
1150
+ * so a removed device loses its upgrade rights and every device sealed
1151
+ * the new roster key holds them. The authority is read from chain: an
1152
+ * empty committee is a locked account and nothing is submitted. Otherwise
1153
+ * the encryption key signs the replace with the roster key among the
1154
+ * keyring's whose authority the chain holds, the update is rebuilt as it
1155
+ * was signed and submitted, and this resolves once the chain shows the
1156
+ * advanced counter, so the next call runs under the new authority.
1157
+ *
1158
+ * @throws {@link MaintenanceUpdateRefusedError} when the key cannot sign
1159
+ * maintenance updates, or refuses the replace.
1160
+ * @throws {@link MaintenanceAuthorityMismatchError} when the committee
1161
+ * is not one key some roster key of the keyring derives.
1162
+ */
1163
+ const followRosterAuthority = async ({ address, seams, keyring, authority, onProgress }) => {
1164
+ const [ledger, networkId] = await importLedgerModules();
1165
+ const providers = await require_providers.createMidnightProviders({
1166
+ network: seams.passportNetwork,
1167
+ prover: seams.passportProver,
1168
+ sponsor: seams.passportSponsor,
1169
+ onStage: onProgress
1170
+ });
1171
+ const onChain = await authorityOnChain(ledger, providers.publicDataProvider, address);
1172
+ if (onChain.committee.length === 0) return;
1173
+ const sign = seams.passportEncryptionKey.signMaintenanceUpdate;
1174
+ if (!sign) throw missingSigner(address);
1175
+ await signAndSubmit({
1176
+ ledger,
1177
+ networkId,
1178
+ providers,
1179
+ address,
1180
+ seams,
1181
+ sign,
1182
+ keyring,
1183
+ authority: soleMember(address, onChain.committee),
1184
+ counter: onChain.counter,
1185
+ changes: {
1186
+ kind: "replace-authority",
1187
+ authority
1188
+ },
1189
+ label: "Maintenance authority rotation"
1190
+ });
1191
+ };
1192
+ /**
1193
+ * Refuses an install of missing operations the account's authority
1194
+ * could not sign, before any prompt: a locked account, whose committee
1195
+ * is empty, and an encryption key that signs no maintenance update.
1196
+ *
1197
+ * @throws {@link AuthorityRetiredError} when the account is locked.
1198
+ * @throws {@link MaintenanceUpdateRefusedError} when the key cannot sign.
1199
+ */
1200
+ const assertInstallSigner = async ({ address, indexer, encryptionKey }) => {
1201
+ const [ledger] = await importLedgerModules();
1202
+ const { committee } = await authorityOnChain(ledger, indexer, address);
1203
+ if (committee.length === 0) throw lockedAccount(address);
1204
+ if (!encryptionKey.signMaintenanceUpdate) throw missingInstallSigner(address);
1205
+ };
1206
+ /**
1207
+ * Installs verifier keys on an unlocked account, one maintenance update
1208
+ * per wave, each signed by the encryption key with the roster key whose
1209
+ * authority the chain holds. The authority is read from chain before
1210
+ * every wave, because each applied update advances its counter, and
1211
+ * every wave resolves only once the chain shows the advanced counter, so
1212
+ * this resolves once the chain holds every key. The authority stays the
1213
+ * one that signs: no wave replaces it.
1214
+ *
1215
+ * @throws {@link MaintenanceUpdateRefusedError} when the key cannot sign
1216
+ * maintenance updates, or refuses a verifier key.
1217
+ * @throws {@link AuthorityRetiredError} when the account is locked.
1218
+ * @throws {@link MaintenanceAuthorityMismatchError} when the committee
1219
+ * is not one key some roster key of the keyring derives.
1220
+ */
1221
+ const installVerifierKeys = async ({ address, seams, providers, keyring, waves }) => {
1222
+ const [ledger, networkId] = await importLedgerModules();
1223
+ const sign = seams.passportEncryptionKey.signMaintenanceUpdate;
1224
+ if (!sign) throw missingInstallSigner(address);
1225
+ for (const [index, inserts] of waves.entries()) {
1226
+ const { committee, counter } = await authorityOnChain(ledger, providers.publicDataProvider, address);
1227
+ if (committee.length === 0) throw lockedAccount(address);
1228
+ await signAndSubmit({
1229
+ ledger,
1230
+ networkId,
1231
+ providers,
1232
+ address,
1233
+ seams,
1234
+ sign,
1235
+ keyring,
1236
+ authority: soleMember(address, committee),
1237
+ counter,
1238
+ changes: {
1239
+ kind: "insert-verifier-keys",
1240
+ inserts
1241
+ },
1242
+ label: `Operation install ${index + 1} of ${waves.length}`
1243
+ });
1244
+ }
1245
+ };
1246
+ //#endregion
1247
+ //#region ../../packages/module/passport-account/src/flows/devices.ts
1248
+ /**
1249
+ * Contract aborts meaning the removal would leave the account without an
1250
+ * authorising device: the caller rule and the redundant count floor.
1251
+ */
1252
+ const LAST_DEVICE_ABORTS = ["failed assert: cannot remove the authorising device", "failed assert: cannot remove last device"];
1253
+ /**
1254
+ * Types a raw contract abort by the assert message the Account Custody
1255
+ * Contract raises; anything unrecognised passes through unchanged.
1256
+ * An unknown-entry abort on the remove circuit is attributed to the
1257
+ * removal target, not the caller: the circuit checks the caller first,
1258
+ * and the caller's entry was resolved against live ledger state inside
1259
+ * the same gate, so only a mid-flight roll of the caller's own entry
1260
+ * could produce it, while a stale target commitment always does.
1261
+ */
1262
+ const classifyContractAbort$1 = (error, circuit) => {
1263
+ const message = error instanceof Error ? error.message : String(error);
1264
+ if (LAST_DEVICE_ABORTS.some((abort) => message.includes(abort))) return new require_errors.LastDeviceError(message);
1265
+ if (message.includes("failed assert: unknown device entry")) return circuit === "remove_device_with_jubjub" ? new require_errors.RemovalTargetNotFoundError(message) : new require_errors.NotAuthorisedError(message);
1266
+ if (require_account_call.isNotAuthorisedAbort(message)) return new require_errors.NotAuthorisedError(message);
1267
+ return error;
1268
+ };
1269
+ /**
1270
+ * Whether any read grant of the account is live: its grantees hold the
1271
+ * live account secret, so a device handed the keys while one is live
1272
+ * gets a fresh secret first.
1273
+ */
1274
+ const hasLiveReadGrant = (ledgerState) => readGrants(ledgerState).some((record) => record.active && record.scope.read);
1275
+ /** The devices list with the enrolled commitment appended. */
1276
+ const withDevice = (devices, commitmentHex) => [...devices, {
1277
+ commitmentHex,
1278
+ isLocal: false
1279
+ }];
1280
+ /** The devices list with the revoked commitment dropped. */
1281
+ const withoutDevice = (devices, commitmentHex) => devices.filter((device) => device.commitmentHex !== commitmentHex);
1282
+ /** A device's entry as the call that targets it takes it. */
1283
+ const entryArgument = (commitmentHex) => require_bytes_vo.ByteArray.fromHex(require_bytes_vo.HexBytes(commitmentHex.toLowerCase()));
1284
+ /**
1285
+ * This device's keyring over the ledger the chain is at: the live secret
1286
+ * and the earlier ones its envelopes deliver.
1287
+ */
1288
+ const keyringAt = (account, chain, local) => readAccountInbox(account.address, chain.position().ledgerState, local.readerPublicKey).keyring;
1289
+ /** The counter the chain left this device at, or the one it started from. */
1290
+ const chainCounter = (chain, input) => chain.position().localUseCounter ?? input.localUseCounter ?? "0";
1291
+ /**
1292
+ * Opens what a device flow runs on inside its key session: the grant
1293
+ * bookkeeping of the record, read where it costs no extra prompt so every
1294
+ * write keeps it, and a chain of gated calls from the ledger read ahead
1295
+ * of the session, each persisting the counter it advanced this device to
1296
+ * before the next call. The bookkeeping is checked against the ledger
1297
+ * first, so an entry that does not open the grant the chain holds under
1298
+ * its id, as one an issue wrote before a call that never landed, is
1299
+ * neither re-sealed to nor kept.
1300
+ */
1301
+ const openDeviceSession = async (input, { ledgerState, indexer }, { seams, accountRecords, onProgress }) => {
1302
+ const { account, devices, localUseCounter } = input;
1303
+ let grants = await retainStoredGrants((await accountRecords.read())?.grants, readGrants(ledgerState), account.address);
1304
+ const write = async (counter) => accountRecords.write(completedRecord(account, counter, { grants }));
1305
+ const chain = callChain({
1306
+ account,
1307
+ start: {
1308
+ ledgerState,
1309
+ localUseCounter,
1310
+ devices
1311
+ },
1312
+ indexer,
1313
+ seams,
1314
+ onProgress,
1315
+ onAdvanced: write
1316
+ });
1317
+ return {
1318
+ grants,
1319
+ chain,
1320
+ keep: async (next) => {
1321
+ grants = next;
1322
+ await write(chainCounter(chain, input));
1323
+ }
1324
+ };
1325
+ };
1326
+ /**
1327
+ * Enrols a new device key on the account. `commitmentHex` carries the new
1328
+ * device's entry commitment, derived out of band by the joining device;
1329
+ * the local device authorises `add_device_with_jubjub` through the
1330
+ * gated-call pattern. The roster gains the commitment on success only,
1331
+ * marked as remote, and the same result moves the local device's own row
1332
+ * to the successor entry the call left it on, so the roster and the use
1333
+ * counter never disagree about where this device stands.
1334
+ *
1335
+ * When `reader` names the joining device's reader and device keys, the
1336
+ * account keys are delivered to it in the same session: its device key
1337
+ * must derive the enrolled commitment at use counter 0 and its reader key
1338
+ * must be one the account seals to, both checked before any prompt. The
1339
+ * encryption key seals the live secret and the roster key to the reader
1340
+ * key, and the envelope and the device's registration under the roster
1341
+ * key are appended to the inbox, one gated call each, ahead of the
1342
+ * enrolment, so an enrolment that lands never leaves the device without
1343
+ * its keys; until it lands the registration names no live entry and a
1344
+ * rotation passes over it. While any read grant is live its grantees
1345
+ * hold the live secret, and one could seal it to the joining reader with
1346
+ * a roster key of its own ahead of the genuine envelope, so the account
1347
+ * keys are rotated first in the same session, with a fresh roster key,
1348
+ * as a removal rotates them, the joining device handed the new secret no
1349
+ * grantee has seen, and the live read grants this client holds the read
1350
+ * key of sealed it after; with no live read grant only enrolled devices
1351
+ * hold the secret, and the live keys are handed over as they are. A
1352
+ * rotation moves the maintenance authority of an unlocked account to the
1353
+ * one the new roster key derives before the keys are handed over, so the
1354
+ * joining device can sign maintenance updates once enrolled; an
1355
+ * encryption key that cannot sign them refuses such an enrolment with
1356
+ * MaintenanceUpdateRefusedError before any prompt, and a device none of
1357
+ * whose roster keys derives the authority on chain refuses it with
1358
+ * MaintenanceAuthorityMismatchError inside the session, before the
1359
+ * rotation. The
1360
+ * joining device then signs in and reads the holdings with its own
1361
+ * passkey alone. Without `reader` the
1362
+ * device can authorise but reads nothing until some device delivers it
1363
+ * the keys.
1364
+ *
1365
+ * The advanced counter is persisted to the account record after every
1366
+ * call, before the roster is returned, so the next flow rescans from a
1367
+ * fresh anchor. A failure leaves the roster unreturned.
1368
+ *
1369
+ * A rejected authorisation (bad signature, malformed key, or a caller
1370
+ * entry the on-chain set no longer holds) rejects with NotAuthorisedError;
1371
+ * a record whose address holds no live contract rejects with
1372
+ * AccountContractMissingError; joining keys that do not match the entry
1373
+ * reject with DeviceReaderInvalidError. The transactions' proving and
1374
+ * sponsoring stages and 'recording' are reported through `onProgress`.
1375
+ */
1376
+ const addDevice = async ({ reader, ...input }, context) => {
1377
+ const { seams, onProgress } = context;
1378
+ const { account, commitmentHex } = input;
1379
+ const { networkId } = seams.passportNetwork;
1380
+ try {
1381
+ const ledger = await require_account_call.readAccountLedger(account.address, seams.passportNetwork);
1382
+ if (reader) require_device_readers.assertJoiningDevice({
1383
+ address: account.address,
1384
+ epoch: ledger.ledgerState.device_epoch
1385
+ }, reader, commitmentHex);
1386
+ const view = reader && hasLiveReadGrant(ledger.ledgerState) ? viewCapability(seams.passportEncryptionKey) : void 0;
1387
+ const devices = view ? liveDeviceEntries(ledger.ledgerState) : void 0;
1388
+ if (view) await assertMaintenanceSigner({
1389
+ address: account.address,
1390
+ indexer: ledger.indexer,
1391
+ encryptionKey: seams.passportEncryptionKey
1392
+ });
1393
+ return await inKeySession(seams.passportAuthoriser, {
1394
+ kind: "add-device",
1395
+ commitmentHex,
1396
+ account: require_providers.AccAddress(account.address),
1397
+ ...reader ? { reader } : {},
1398
+ ...devices ? { devices } : {}
1399
+ }, async () => {
1400
+ const { grants, chain, keep } = await openDeviceSession(input, ledger, context);
1401
+ let rotated;
1402
+ if (reader) {
1403
+ const local = await readLocalDevice(seams);
1404
+ if (view && devices) {
1405
+ await assertHoldsRosterAuthority({
1406
+ address: account.address,
1407
+ seams,
1408
+ indexer: ledger.indexer,
1409
+ keyring: keyringAt(account, chain, local)
1410
+ });
1411
+ rotated = await rotateEncKey({
1412
+ account,
1413
+ encryptionKey: seams.passportEncryptionKey,
1414
+ view,
1415
+ local,
1416
+ chain,
1417
+ indexer: ledger.indexer,
1418
+ networkId,
1419
+ onProgress,
1420
+ devices,
1421
+ rotateRoster: true
1422
+ });
1423
+ await followRosterAuthority({
1424
+ address: account.address,
1425
+ seams,
1426
+ keyring: keyringAt(account, chain, local),
1427
+ authority: rotated.authority,
1428
+ onProgress
1429
+ });
1430
+ }
1431
+ const delivery = await seams.passportEncryptionKey.sealToDevice(networkId, rotated?.keyring ?? keyringAt(account, chain, local), {
1432
+ ...reader,
1433
+ epoch: ledger.ledgerState.device_epoch,
1434
+ counter: 0n
1435
+ });
1436
+ await chain.append("recording", [delivery.envelope, delivery.registration]);
1437
+ if (view && rotated) {
1438
+ const views = await sealViews({
1439
+ view,
1440
+ networkId,
1441
+ keyring: rotated.keyring,
1442
+ account: require_providers.AccAddress(account.address),
1443
+ targets: liveStoredReadGrants(grants, readGrants(chain.position().ledgerState))
1444
+ });
1445
+ await keep(withSealedViews(grants, views));
1446
+ await chain.append("recording", views.entries);
1447
+ }
1448
+ }
1449
+ await chain.submit({
1450
+ circuit: ADD_DEVICE_CIRCUIT,
1451
+ args: [entryArgument(commitmentHex)]
1452
+ });
1453
+ return {
1454
+ devices: withDevice(chain.position().devices, commitmentHex),
1455
+ localUseCounter: chainCounter(chain, input),
1456
+ ...rotated ? {
1457
+ encKey: require_bytes_vo.HexBytes.fromByteArray(rotated.encKey),
1458
+ droppedDevices: rotated.dropped
1459
+ } : {}
1460
+ };
1461
+ });
1462
+ } catch (error) {
1463
+ throw classifyContractAbort$1(error, ADD_DEVICE_CIRCUIT);
1464
+ }
1465
+ };
1466
+ /**
1467
+ * Refuses a removal the contract would refuse before any prompt: a
1468
+ * target the on-chain device set does not hold, and the local device,
1469
+ * which cannot remove itself. The rotation a removal runs comes first,
1470
+ * so a removal refused only after it would have cost the target device
1471
+ * its keys for nothing.
1472
+ *
1473
+ * @throws {@link RemovalTargetNotFoundError} when the target is gone.
1474
+ * @throws {@link LastDeviceError} when the target is this device.
1475
+ */
1476
+ const assertRemovable = ({ devices, commitmentHex }, { ledgerState }) => {
1477
+ if (!ledgerState.devices.member(entryArgument(commitmentHex))) throw new require_errors.RemovalTargetNotFoundError(`The account holds no device entry ${commitmentHex}.`);
1478
+ if (devices.find((device) => device.isLocal)?.commitmentHex.toLowerCase() === commitmentHex.toLowerCase()) throw new require_errors.LastDeviceError("The device that authorises a removal cannot remove itself.");
1479
+ };
1480
+ /**
1481
+ * Revokes a device key from the account and rotates the account keys so
1482
+ * the removed device reads nothing newer. `commitmentHex` names the
1483
+ * target by its current entry commitment, which is the literal set
1484
+ * element the contract removes; the local device authorises
1485
+ * `remove_device_with_jubjub` through the gated-call pattern.
1486
+ *
1487
+ * Ahead of the removal, in the same session, the encryption key is
1488
+ * rotated with a fresh roster key and sealed only to this device and to
1489
+ * the registered devices whose device key derives a live entry other
1490
+ * than the target, which the key session names to the user before it
1491
+ * opens; the live holdings are re-encrypted towards it, and it is sealed
1492
+ * again to every live read grant this client holds the read key of, each
1493
+ * seal kept on chain in a grant record. The removed device's envelopes
1494
+ * stay in the inbox and keep opening the secrets they held, but none
1495
+ * opens the new one, and the registrations under the old roster key no
1496
+ * longer count, any it planted for itself included. Once `enc_key` is
1497
+ * replaced, and before the removal, the maintenance authority of an
1498
+ * unlocked account is moved to the one the new roster key derives, so
1499
+ * the removed device loses its upgrade rights; a locked account has no
1500
+ * authority to move. An encryption key that cannot sign maintenance
1501
+ * updates refuses the removal of a device from an unlocked account with
1502
+ * MaintenanceUpdateRefusedError before any prompt, and a device none of
1503
+ * whose roster keys derives the authority on chain refuses it with
1504
+ * MaintenanceAuthorityMismatchError inside the session, before the
1505
+ * rotation, so nothing is submitted. A roster in which
1506
+ * two readers claim one live device, or one reader two, refuses with
1507
+ * DeviceRosterInvalidError rather than guess.
1508
+ *
1509
+ * The roster drops the commitment on success only, and the same result
1510
+ * moves the local device's own row to the successor entry the call left
1511
+ * it on. The advanced counter is persisted to the account record after
1512
+ * every call. The contract's own guards surface as typed errors: a
1513
+ * removal that would strand the account (the authorising device, or the
1514
+ * count floor) rejects with LastDeviceError, a rejected authorisation
1515
+ * (bad signature or malformed key) with NotAuthorisedError, and a target
1516
+ * entry that is no longer in the on-chain set with
1517
+ * RemovalTargetNotFoundError; the target's presence and the local device
1518
+ * are checked before any prompt, and an encryption key that cannot
1519
+ * rotate refuses with ReadUnavailableError before any prompt too. The
1520
+ * rotation's stages, 'recording' and the transactions' proving and
1521
+ * sponsoring are reported through `onProgress`.
1522
+ */
1523
+ const removeDevice = async (input, context) => {
1524
+ const { seams, onProgress } = context;
1525
+ const { account, commitmentHex } = input;
1526
+ const { networkId } = seams.passportNetwork;
1527
+ try {
1528
+ const view = viewCapability(seams.passportEncryptionKey);
1529
+ const ledger = await require_account_call.readAccountLedger(account.address, seams.passportNetwork);
1530
+ assertRemovable(input, ledger);
1531
+ const devices = liveDeviceEntries(ledger.ledgerState, commitmentHex);
1532
+ await assertMaintenanceSigner({
1533
+ address: account.address,
1534
+ indexer: ledger.indexer,
1535
+ encryptionKey: seams.passportEncryptionKey
1536
+ });
1537
+ return await inKeySession(seams.passportAuthoriser, {
1538
+ kind: "remove-device",
1539
+ commitmentHex,
1540
+ account: require_providers.AccAddress(account.address),
1541
+ devices
1542
+ }, async () => {
1543
+ const { grants, chain, keep } = await openDeviceSession(input, ledger, context);
1544
+ const local = await readLocalDevice(seams);
1545
+ await assertHoldsRosterAuthority({
1546
+ address: account.address,
1547
+ seams,
1548
+ indexer: ledger.indexer,
1549
+ keyring: keyringAt(account, chain, local)
1550
+ });
1551
+ const rotated = await rotateEncKey({
1552
+ account,
1553
+ encryptionKey: seams.passportEncryptionKey,
1554
+ view,
1555
+ local,
1556
+ chain,
1557
+ indexer: ledger.indexer,
1558
+ networkId,
1559
+ onProgress,
1560
+ devices,
1561
+ rotateRoster: true,
1562
+ target: commitmentHex.toLowerCase()
1563
+ });
1564
+ await followRosterAuthority({
1565
+ address: account.address,
1566
+ seams,
1567
+ keyring: keyringAt(account, chain, local),
1568
+ authority: rotated.authority,
1569
+ onProgress
1570
+ });
1571
+ const views = await sealViews({
1572
+ view,
1573
+ networkId,
1574
+ keyring: rotated.keyring,
1575
+ account: require_providers.AccAddress(account.address),
1576
+ targets: liveStoredReadGrants(grants, readGrants(chain.position().ledgerState))
1577
+ });
1578
+ await keep(withSealedViews(grants, views));
1579
+ await chain.append("recording", views.entries);
1580
+ await chain.submit({
1581
+ circuit: REMOVE_DEVICE_CIRCUIT,
1582
+ args: [entryArgument(commitmentHex)]
1583
+ });
1584
+ return {
1585
+ devices: withoutDevice(chain.position().devices, commitmentHex),
1586
+ localUseCounter: chainCounter(chain, input),
1587
+ encKey: require_bytes_vo.HexBytes.fromByteArray(rotated.encKey),
1588
+ droppedDevices: rotated.dropped
1589
+ };
1590
+ });
1591
+ } catch (error) {
1592
+ throw classifyContractAbort$1(error, REMOVE_DEVICE_CIRCUIT);
1593
+ }
1594
+ };
1595
+ //#endregion
1596
+ //#region ../../packages/module/passport-account/src/flows/grants.ts
1597
+ /** Contract abort naming a grant the grants map does not hold. */
1598
+ const UNKNOWN_GRANT_ABORT = "failed assert: unknown grant";
1599
+ /** Contract abort refusing a revoke over a grant already inactive. */
1600
+ const GRANT_NOT_LIVE_ABORT = "failed assert: grant not live";
1601
+ /** Contract abort refusing an issue over a grant id that is still active. */
1602
+ const GRANT_ACTIVE_ABORT = "failed assert: grant already active";
1603
+ /**
1604
+ * How long an accepted call is given to show in the indexer's view of the
1605
+ * grants map, which can lag the block the call landed in.
1606
+ */
1607
+ const ROSTER_POLL_ATTEMPTS = 30;
1608
+ const ROSTER_POLL_DELAY_MS = 1e3;
1609
+ const MILLISECONDS_PER_SECOND = 1e3;
1610
+ const sleep = async (ms) => new Promise((resolve) => {
1611
+ setTimeout(resolve, ms);
1612
+ });
1613
+ const isActive = (roster, grantId) => roster.some((record) => record.id === grantId && record.active);
1614
+ const activeCount = (roster) => roster.filter((record) => record.active).length;
1615
+ const nowSeconds = () => Math.floor(Date.now() / MILLISECONDS_PER_SECOND);
1616
+ /**
1617
+ * Types a raw contract abort by the assert message the Account Custody
1618
+ * Contract raises; anything unrecognised passes through unchanged. An
1619
+ * unknown-entry abort can only come from the caller check here, since no
1620
+ * grant circuit names a device entry.
1621
+ */
1622
+ const classifyContractAbort = (error) => {
1623
+ const message = error instanceof Error ? error.message : String(error);
1624
+ if (message.includes(UNKNOWN_GRANT_ABORT)) return new require_errors.GrantNotFoundError(message);
1625
+ if (message.includes(GRANT_NOT_LIVE_ABORT)) return new require_errors.GrantRevokedError(message);
1626
+ if (message.includes(GRANT_ACTIVE_ABORT)) return new require_errors.GrantIssueRejectedError(message);
1627
+ if (message.includes("failed assert: unknown device entry") || require_account_call.isNotAuthorisedAbort(message)) return new require_errors.NotAuthorisedError(message);
1628
+ return error;
1629
+ };
1630
+ /**
1631
+ * Refuses a device whose storage holds no account record before any
1632
+ * ceremony. The gate only runs a call over an account in state, so an
1633
+ * absent record means the storage was cleared underneath it.
1634
+ */
1635
+ const assertRecordPresent = async (accountRecords) => {
1636
+ if (await accountRecords.presence() === "absent") throw new require_errors.AccountNotFoundError("No Passport account record is stored on this device.");
1637
+ };
1638
+ /**
1639
+ * The stored record of the account a grant call acts on, read inside the
1640
+ * session where it costs no extra prompt. Presence was checked ahead of
1641
+ * the session, so a missing record here vanished in between.
1642
+ */
1643
+ const storedRecord = async (accountRecords) => {
1644
+ const record = await accountRecords.read();
1645
+ if (!record) throw new require_errors.AccountNotFoundError("No Passport account record is stored on this device.");
1646
+ return record;
1647
+ };
1648
+ /**
1649
+ * The ledger state once the indexer reflects the accepted call, with the
1650
+ * roster read from it. The submission settled in a block, but the
1651
+ * indexer can serve the state from before it for a moment, and a roster
1652
+ * read then would deny the call its own result.
1653
+ *
1654
+ * @throws {@link GrantRosterUnsettledError} when the indexer never
1655
+ * reflects the call within the polling window.
1656
+ */
1657
+ const awaitSettledRoster = async ({ indexer, address, slots, openings, isSettled }) => {
1658
+ for (let attempt = 0; attempt < ROSTER_POLL_ATTEMPTS; attempt++) {
1659
+ const ledgerState = await require_account_call.queryAccountLedger(indexer, address);
1660
+ const roster = readGrants(ledgerState, slots, openings);
1661
+ if (isSettled(roster)) return {
1662
+ ledgerState,
1663
+ roster
1664
+ };
1665
+ await sleep(ROSTER_POLL_DELAY_MS);
1666
+ }
1667
+ throw new require_errors.GrantRosterUnsettledError(`The grant roster of ${address} never reflected the accepted call.`);
1668
+ };
1669
+ /**
1670
+ * Runs one gated grant call inside a single key session, so the use
1671
+ * counter rescan and the authorisation cost one ceremony rather than one
1672
+ * per authoriser call. A device whose storage holds no record is refused
1673
+ * ahead of the session. The record is read inside it, where it costs no
1674
+ * extra prompt, its grant bookkeeping narrowed to the grants whose kept
1675
+ * fields open what the ledger read before the call holds under their id,
1676
+ * so an entry an issue wrote before a call that never landed is neither
1677
+ * an opening nor a re-seal target. It is rewritten with the advanced
1678
+ * counter and the grant bookkeeping after every call, so a roster is
1679
+ * never published ahead of the record that anchors the next rescan and
1680
+ * names the slots. A call
1681
+ * that keeps its bookkeeping ahead of the submission writes it once more
1682
+ * before it, under the counter the record holds. A call that rotates the
1683
+ * encryption key does so in the same session ahead of its own circuit.
1684
+ * The other live read grants are sealed the new secret right after the
1685
+ * rotation lands, their seals kept in the record and on chain before the
1686
+ * call is submitted, so they keep reading whatever the call's outcome.
1687
+ * Once the roster shows an issued grant active, its bookkeeping is
1688
+ * appended to the inbox, sealed to this device alone, and the grantee of
1689
+ * a read grant is sealed the new secret, the seal kept the same way. The
1690
+ * wait for the indexer is reported as 'confirming' and the appends as
1691
+ * 'recording'. The contract's own aborts surface as typed errors;
1692
+ * anything else, and a failing on-chain status in particular, rejects
1693
+ * unchanged with the counter unmoved.
1694
+ */
1695
+ const runGrantCall = async ({ config: { call, flow, nextStoredGrants, writeBeforeCall, isSettled, rotation, reseal, issued }, input: { account, devices, localUseCounter }, ledger: { indexer, ledgerState }, roster: listed, context: { seams, accountRecords, onProgress } }) => {
1696
+ const { networkId } = seams.passportNetwork;
1697
+ const accountAddress = require_providers.AccAddress(account.address);
1698
+ try {
1699
+ await assertRecordPresent(accountRecords);
1700
+ const rotationDevices = rotation ? liveDeviceEntries(ledgerState) : void 0;
1701
+ return await inKeySession(seams.passportAuthoriser, rotationDevices ? {
1702
+ ...flow,
1703
+ devices: rotationDevices
1704
+ } : flow, async () => {
1705
+ const record = await storedRecord(accountRecords);
1706
+ let stored = nextStoredGrants(await retainStoredGrants(record.grants, listed, account.address));
1707
+ const write = async (counter) => accountRecords.write(completedRecord(account, counter, { grants: stored }));
1708
+ if (writeBeforeCall) await write(record.localUseCounter);
1709
+ const chain = callChain({
1710
+ account,
1711
+ start: {
1712
+ ledgerState,
1713
+ localUseCounter,
1714
+ devices
1715
+ },
1716
+ indexer,
1717
+ seams,
1718
+ onProgress,
1719
+ onAdvanced: write
1720
+ });
1721
+ const counter = () => chain.position().localUseCounter ?? record.localUseCounter;
1722
+ const roster = (state) => readGrants(state, storedGrantSlots(stored), storedGrantOpenings(stored));
1723
+ let rotated;
1724
+ let unsealedGrantIds = [];
1725
+ let revocationPendingGrantIds = [];
1726
+ if (rotation) {
1727
+ rotated = await rotateEncKey({
1728
+ account,
1729
+ encryptionKey: seams.passportEncryptionKey,
1730
+ view: rotation,
1731
+ local: await readLocalDevice(seams),
1732
+ chain,
1733
+ indexer,
1734
+ networkId,
1735
+ onProgress,
1736
+ devices: rotationDevices ?? []
1737
+ });
1738
+ if (reseal) {
1739
+ const views = await sealViews({
1740
+ view: rotation,
1741
+ networkId,
1742
+ keyring: rotated.keyring,
1743
+ account: accountAddress,
1744
+ targets: liveStoredReadGrants(stored, roster(chain.position().ledgerState), reseal.except)
1745
+ });
1746
+ stored = withSealedViews(stored, views);
1747
+ unsealedGrantIds = views.unsealed;
1748
+ revocationPendingGrantIds = views.revocationPending;
1749
+ await write(counter());
1750
+ await chain.append("recording", views.entries);
1751
+ }
1752
+ }
1753
+ await chain.submit(call);
1754
+ onProgress("confirming");
1755
+ const settled = await awaitSettledRoster({
1756
+ indexer,
1757
+ address: account.address,
1758
+ slots: storedGrantSlots(stored),
1759
+ openings: storedGrantOpenings(stored),
1760
+ isSettled
1761
+ });
1762
+ let grants = settled.roster;
1763
+ if (issued) {
1764
+ const entries = await seams.passportEncryptionKey.sealGrant(networkId, accountAddress, issued.bookkeeping);
1765
+ if (rotation && rotated && issued.readPublicKey !== void 0) {
1766
+ const views = await sealViews({
1767
+ view: rotation,
1768
+ networkId,
1769
+ keyring: rotated.keyring,
1770
+ account: accountAddress,
1771
+ targets: [{
1772
+ grantId: issued.bookkeeping.grantId,
1773
+ readPublicKey: issued.readPublicKey
1774
+ }]
1775
+ });
1776
+ stored = withSealedViews(stored, views);
1777
+ unsealedGrantIds = [...unsealedGrantIds, ...views.unsealed];
1778
+ revocationPendingGrantIds = [...revocationPendingGrantIds, ...views.revocationPending];
1779
+ entries.push(...views.entries);
1780
+ await write(counter());
1781
+ grants = roster(settled.ledgerState);
1782
+ }
1783
+ await chain.append("recording", entries);
1784
+ }
1785
+ return {
1786
+ grants,
1787
+ devices: chain.position().devices,
1788
+ localUseCounter: counter(),
1789
+ ...rotated ? {
1790
+ encKey: require_bytes_vo.HexBytes.fromByteArray(rotated.encKey),
1791
+ droppedDevices: rotated.dropped
1792
+ } : {},
1793
+ unsealedGrantIds,
1794
+ revocationPendingGrantIds
1795
+ };
1796
+ });
1797
+ } catch (error) {
1798
+ throw classifyContractAbort(error);
1799
+ }
1800
+ };
1801
+ /**
1802
+ * Refuses a grant its grantee could never spend: one naming a scheme
1803
+ * whose withdrawals the account was deployed without. Installing them
1804
+ * takes a maintenance update, which a locked account can no longer sign
1805
+ * and an unlocked one no longer holds the key for, so the issue is
1806
+ * refused before the owner is asked to authorise it.
1807
+ *
1808
+ * @throws {@link GrantScopeInvalidError} when a withdrawal of the
1809
+ * grantee's scheme is not installed on the account.
1810
+ */
1811
+ const requireGranteeWithdrawals = async (indexer, address, scheme) => {
1812
+ const installed = new Set(await require_account_call.queryAccountOperations(indexer, address));
1813
+ const missing = require_grant_circuits.GRANTEE_WITHDRAWAL_CIRCUITS[scheme].filter((circuit) => !installed.has(circuit));
1814
+ if (missing.length > 0) throw new require_errors.GrantScopeInvalidError(`the account cannot serve a ${scheme} grantee, it lacks ${missing.join(", ")}`);
1815
+ };
1816
+ /**
1817
+ * Issues a scoped grant to an agent key. The scope is checked against the
1818
+ * rules the contract asserts before any prompt: a shielded operation
1819
+ * needs read, a read grant needs a canonical read public key, a rolling
1820
+ * window is refused because the contract reserves its bounds, and a
1821
+ * grant beyond sixteen active ones is refused as Lace's limit. Each
1822
+ * refusal is a GrantScopeInvalidError or GrantLimitReachedError and costs
1823
+ * no ceremony; a host with no relying party id is refused with
1824
+ * GrantOriginMissingError before the chain is read, and a read grant
1825
+ * with ReadUnavailableError when the encryption key held here cannot
1826
+ * share the viewing secret. The grant id is derived from the account,
1827
+ * the grantee key, the hash of this host's relying party id and the
1828
+ * first slot free for that pair, and the scope salt is drawn fresh; the
1829
+ * local device then authorises `issue_grant_with_jubjub` over the id and
1830
+ * the salted scope digest, which binds the hash of the read key. The key
1831
+ * session is opened with the account, the slot, the salt and the digest
1832
+ * beside the id and the scope, so an authoriser that does not share this
1833
+ * flow's memory can re-derive both before it signs.
1834
+ *
1835
+ * A read grant delivers the account's viewing secret, and read is total:
1836
+ * the grantee sees every shielded holding, current and future, until the
1837
+ * key is rotated again. So the key is rotated first, in the same session:
1838
+ * a fresh random secret is sealed to every device, the live holdings are
1839
+ * re-encrypted towards it, its public key replaces `enc_key`, and only
1840
+ * then is the grant issued, so the grantee reads nothing of the
1841
+ * account's spent history. Right after the rotation lands the new secret
1842
+ * is sealed to every other live read grant this client holds the key of,
1843
+ * so they keep reading even should the issue fail, and once the grant
1844
+ * shows active it is sealed to the grantee's read key; the seals join
1845
+ * the bookkeeping, and the grantee's reaches it through the agent
1846
+ * policy, never the chain. A grantee seal the key seam refuses after the
1847
+ * grant landed leaves the grant active and unsealed, named among the
1848
+ * result's unsealed grants, rather than failing the issue.
1849
+ *
1850
+ * The slot and salt are kept in the account record, since the contract
1851
+ * stores neither, together with the object fields its commitment hides
1852
+ * (colour, recipient and coin bound) and the read key, which the grantee
1853
+ * needs to open it and which reach the roster as the grant's opening.
1854
+ * They are written before the call is submitted: a grant that lands
1855
+ * without its salt could never be opened for its grantee. A call that
1856
+ * never lands leaves a stale entry, which the next sign-in prunes against
1857
+ * the chain and a later issue over the same id overwrites. Once the
1858
+ * grant shows active the same bookkeeping, and each seal, is appended to
1859
+ * the inbox in grant records only this device opens, so a sign-in after
1860
+ * the record is lost rebuilds it. The roster is read back from chain
1861
+ * once the grant shows active in it.
1862
+ *
1863
+ * A grant id the contract still holds active rejects with
1864
+ * GrantIssueRejectedError, and a rejected authorisation with
1865
+ * NotAuthorisedError. The transaction's proving and sponsoring stages,
1866
+ * the rotation, the wait for the indexer and the appends are reported
1867
+ * through `onProgress`.
1868
+ */
1869
+ const issueGrant = async ({ scope, ...input }, context) => {
1870
+ const { rpId, seams } = context;
1871
+ if (rpId === void 0) throw new require_errors.GrantOriginMissingError();
1872
+ const view = scope.read ? viewCapability(seams.passportEncryptionKey) : null;
1873
+ const { account } = input;
1874
+ const ledger = await require_account_call.readAccountLedger(account.address, seams.passportNetwork);
1875
+ await requireGranteeWithdrawals(ledger.indexer, account.address, scope.grantee.scheme);
1876
+ const roster = readGrants(ledger.ledgerState);
1877
+ require_grant_rules.validateGrantScope(scope, {
1878
+ nowSeconds: nowSeconds(),
1879
+ grantCount: activeCount(roster)
1880
+ });
1881
+ const { grantId, slot, scopeSalt, scopeDigest, call } = require_grants.prepareIssueGrant({
1882
+ contractAddress: require_bytes_vo.ByteArray.fromHex(require_bytes_vo.HexBytes(account.address)),
1883
+ scope,
1884
+ originHash: await require_grants.originHash(rpId),
1885
+ roster
1886
+ });
1887
+ const readPublicKey = view ? scope.readPublicKey : void 0;
1888
+ const object = {
1889
+ color: scope.color,
1890
+ recipient: scope.recipient,
1891
+ maxCoinValue: scope.maxCoinValue
1892
+ };
1893
+ return {
1894
+ grantId,
1895
+ ...await runGrantCall({
1896
+ config: {
1897
+ call,
1898
+ flow: {
1899
+ kind: "issue-grant",
1900
+ grantId,
1901
+ scope,
1902
+ account: require_providers.AccAddress(account.address),
1903
+ slot,
1904
+ scopeSalt,
1905
+ scopeDigest
1906
+ },
1907
+ nextStoredGrants: (stored) => withStoredGrant(stored, grantId, {
1908
+ slot,
1909
+ scopeSalt,
1910
+ granteeScheme: scope.grantee.scheme,
1911
+ granteePublicKey: scope.grantee.publicKey,
1912
+ origin: rpId,
1913
+ object,
1914
+ ...readPublicKey === void 0 ? {} : { view: { readPublicKey } }
1915
+ }),
1916
+ writeBeforeCall: true,
1917
+ isSettled: (settled) => isActive(settled, grantId),
1918
+ ...view && readPublicKey !== void 0 ? {
1919
+ rotation: view,
1920
+ reseal: { except: grantId }
1921
+ } : {},
1922
+ issued: {
1923
+ bookkeeping: {
1924
+ grantId,
1925
+ slot,
1926
+ scopeSalt,
1927
+ granteeScheme: scope.grantee.scheme,
1928
+ granteePublicKey: scope.grantee.publicKey,
1929
+ object,
1930
+ ...readPublicKey === void 0 ? {} : { readPublicKey }
1931
+ },
1932
+ ...readPublicKey === void 0 ? {} : { readPublicKey }
1933
+ }
1934
+ },
1935
+ input,
1936
+ ledger,
1937
+ roster,
1938
+ context
1939
+ })
1940
+ };
1941
+ };
1942
+ /**
1943
+ * The stored grants with the seal of one grant dropped, its read key
1944
+ * kept: a revoked read grant's seal holds a secret the rotation that
1945
+ * follows retires.
1946
+ */
1947
+ const withoutSeal = (stored, grantId) => {
1948
+ const view = stored?.[grantId]?.view;
1949
+ return stored && view ? withStoredGrantView(stored, grantId, { readPublicKey: view.readPublicKey }) : stored;
1950
+ };
1951
+ /**
1952
+ * Revokes one grant by its id. A grant the chain does not hold is refused
1953
+ * with GrantNotFoundError before any prompt, and one it lists inactive
1954
+ * with GrantRevokedError; the same errors type the contract's own aborts
1955
+ * should the grant vanish or be revoked elsewhere in between. The local
1956
+ * device authorises `revoke_grant_with_jubjub`. The grant stays in the
1957
+ * roster as an inactive tombstone, so its stored slot and salt are kept,
1958
+ * and the roster is read back from chain once the grant shows inactive.
1959
+ *
1960
+ * Revoking a read grant also rotates the encryption key, in the same
1961
+ * session ahead of the revocation: a fresh random secret is sealed to
1962
+ * every device and the live holdings are re-encrypted towards it, so the
1963
+ * revoked grantee keeps what it already read and nothing newer, and
1964
+ * right after the rotation lands the new secret is sealed again to every
1965
+ * other read grant this client holds the read key of, ahead of the
1966
+ * revocation. A read grant is refused with ReadUnavailableError before
1967
+ * any prompt when the encryption key held here cannot re-encrypt and
1968
+ * seal.
1969
+ */
1970
+ const revokeGrant = async ({ grantId, ...input }, context) => {
1971
+ const { account } = input;
1972
+ const ledger = await require_account_call.readAccountLedger(account.address, context.seams.passportNetwork);
1973
+ const roster = readGrants(ledger.ledgerState);
1974
+ const listed = roster.find((record) => record.id === grantId);
1975
+ if (!listed) throw new require_errors.GrantNotFoundError(`The account at ${account.address} holds no grant ${grantId}.`);
1976
+ if (!listed.active) throw new require_errors.GrantRevokedError(`The grant ${grantId} of the account at ${account.address} is already revoked.`);
1977
+ const rotation = listed.scope.read ? viewCapability(context.seams.passportEncryptionKey) : void 0;
1978
+ return runGrantCall({
1979
+ config: {
1980
+ call: require_grants.prepareRevokeGrant(grantId),
1981
+ flow: {
1982
+ kind: "revoke-grant",
1983
+ grantId,
1984
+ account: require_providers.AccAddress(account.address),
1985
+ ...rotation ? { read: true } : {}
1986
+ },
1987
+ nextStoredGrants: (stored) => rotation ? withoutSeal(stored, grantId) : stored,
1988
+ writeBeforeCall: false,
1989
+ isSettled: (settled) => !isActive(settled, grantId),
1990
+ ...rotation ? {
1991
+ rotation,
1992
+ reseal: { except: grantId }
1993
+ } : {}
1994
+ },
1995
+ input,
1996
+ ledger,
1997
+ roster,
1998
+ context
1999
+ });
2000
+ };
2001
+ /**
2002
+ * Revokes every grant the account has issued: the local device
2003
+ * authorises `revoke_all_grants_with_jubjub`, which clears the grants
2004
+ * map and advances the grant generation. The record's grant bookkeeping
2005
+ * is dropped with the map, and the roster is read back from chain once
2006
+ * it lists nothing, tombstones included. While any read grant is live,
2007
+ * the encryption key is rotated in the same session ahead of the call
2008
+ * and the live holdings are re-encrypted, so no revoked grantee reads
2009
+ * on; with every grant gone there is nobody to re-seal to. The rotation
2010
+ * is refused with ReadUnavailableError before any prompt when the
2011
+ * encryption key held here cannot re-encrypt.
2012
+ */
2013
+ const revokeAllGrants = async (input, context) => {
2014
+ const { account } = input;
2015
+ const ledger = await require_account_call.readAccountLedger(account.address, context.seams.passportNetwork);
2016
+ const roster = readGrants(ledger.ledgerState);
2017
+ const rotation = roster.some((record) => record.active && record.scope.read) ? viewCapability(context.seams.passportEncryptionKey) : void 0;
2018
+ return runGrantCall({
2019
+ config: {
2020
+ call: require_grants.prepareRevokeAllGrants(),
2021
+ flow: {
2022
+ kind: "revoke-all-grants",
2023
+ account: require_providers.AccAddress(account.address),
2024
+ ...rotation ? { read: true } : {}
2025
+ },
2026
+ nextStoredGrants: () => void 0,
2027
+ writeBeforeCall: false,
2028
+ isSettled: (settled) => settled.length === 0,
2029
+ ...rotation ? { rotation } : {}
2030
+ },
2031
+ input,
2032
+ ledger,
2033
+ roster,
2034
+ context
2035
+ });
2036
+ };
2037
+ //#endregion
2038
+ //#region ../../packages/module/passport-account/src/flows/holdings.ts
2039
+ /**
2040
+ * Reads the account's shielded holdings from public chain data: the
2041
+ * contract's inbox is walked with the account secrets this device's own
2042
+ * envelopes deliver, the one deriving the ledger's `enc_key` first and
2043
+ * the older ones after it, so a coin a racing sender or an interrupted
2044
+ * rotation left under an earlier key is still found, and each coin that
2045
+ * opens is kept only once its commitment is found among the outputs the
2046
+ * chain created for the account, with the tree index it took, and only
2047
+ * while its nullifier is not among those the chain recorded. The
2048
+ * envelopes are opened inside one key session, so the walk costs one
2049
+ * ceremony however many entries the inbox holds, and none at all when
2050
+ * it holds no coin to open; only the opened coin descriptions reach this
2051
+ * flow, never a secret. Nothing is written: no record, no chain call.
2052
+ *
2053
+ * A record whose address holds no live contract rejects with
2054
+ * AccountContractMissingError, and an inbox holding no envelope of this
2055
+ * device that opens the ledger's `enc_key` with EncryptionKeyMismatchError
2056
+ * rather than an inbox read as empty. An entry that fails to open or
2057
+ * describes a coin the chain never created is skipped and counted, never
2058
+ * an error; a coin the account spent is left out without being counted;
2059
+ * the account keys and grant bookkeeping the inbox also holds
2060
+ * are not coins and are not counted.
2061
+ *
2062
+ * Reports no intermediate stage: the ceremony and the indexer reads all
2063
+ * settle before there is anything to report.
2064
+ */
2065
+ const readShieldedHoldings = async ({ account }, { seams }) => {
2066
+ const { passportAuthoriser, passportEncryptionKey, passportNetwork } = seams;
2067
+ const indexer = require_account_call.createIndexerClient({
2068
+ indexerUrl: passportNetwork.indexerUrl,
2069
+ indexerWsUrl: passportNetwork.indexerWsUrl
2070
+ });
2071
+ const ledgerState = await require_account_call.queryAccountLedger(indexer, account.address);
2072
+ const { holdings, skippedEntries } = await walkInbox({
2073
+ address: account.address,
2074
+ ledgerState,
2075
+ indexer,
2076
+ openEntries: async (entries) => inKeySession(passportAuthoriser, { kind: "read-holdings" }, async () => {
2077
+ const { keyring } = readAccountInbox(account.address, ledgerState, await passportEncryptionKey.readerPublicKey(passportNetwork.networkId));
2078
+ return (await passportEncryptionKey.openInbox(passportNetwork.networkId, keyring, entries)).map((entry) => entry?.kind === "coin" ? entry.coin : null);
2079
+ })
2080
+ });
2081
+ return {
2082
+ holdings,
2083
+ skippedEntries
2084
+ };
2085
+ };
2086
+ //#endregion
2087
+ //#region ../../packages/module/passport-account/src/flows/install-operations.ts
2088
+ /**
2089
+ * The requested operations split by whether the account already holds
2090
+ * them, each in installation order, the deployed roster's.
2091
+ *
2092
+ * @throws {@link OperationNotInstallableError} naming every requested
2093
+ * operation outside the deployed roster, or when nothing is requested.
2094
+ */
2095
+ const installableOperations = (requested, installed) => {
2096
+ const outside = requested.filter((operation) => !require_submit.DEPLOYED_OPERATIONS.includes(operation));
2097
+ if (outside.length > 0) throw new require_errors.OperationNotInstallableError(`Not operations this module deploys: ${outside.join(", ")}.`);
2098
+ if (requested.length === 0) throw new require_errors.OperationNotInstallableError("No operation was named to install.");
2099
+ const named = require_submit.DEPLOYED_OPERATIONS.filter((operation) => requested.includes(operation));
2100
+ return {
2101
+ operations: named.filter((operation) => !installed.includes(operation)),
2102
+ alreadyInstalled: named.filter((operation) => installed.includes(operation))
2103
+ };
2104
+ };
2105
+ /** The inserts of one wave, each operation with the verifier key loaded for it. */
2106
+ const insertsOf = (operations, keys) => operations.map((operation) => {
2107
+ const verifierKey = keys.get(operation);
2108
+ if (!verifierKey) throw new Error(`No verifier key loaded for '${operation}'`);
2109
+ return {
2110
+ operation,
2111
+ verifierKey
2112
+ };
2113
+ });
2114
+ /**
2115
+ * Installs on an unlocked account the verifier keys of operations of the
2116
+ * deployed roster it lacks, so an account created under an earlier
2117
+ * roster gains the circuits added since without a new deployment. Only
2118
+ * operations this module deploys can be installed, never a recovery
2119
+ * circuit or another device arm, and each key is the one pinned for its
2120
+ * operation, which the encryption key checks again before it signs.
2121
+ *
2122
+ * The operations the account holds and its maintenance authority are
2123
+ * read from chain before any prompt: a request naming no operation, or an
2124
+ * operation outside the roster, is refused with
2125
+ * OperationNotInstallableError naming them. Requested operations the
2126
+ * account already holds are skipped, so an install interrupted after some
2127
+ * waves landed is finished by running it again; when it holds every one,
2128
+ * this resolves with nothing submitted and no prompt. Otherwise a locked
2129
+ * account is refused with AuthorityRetiredError, and an encryption key
2130
+ * that cannot sign maintenance updates with
2131
+ * MaintenanceUpdateRefusedError. The verifier
2132
+ * keys are then downloaded against their pins and packed into waves
2133
+ * under the per-transaction budget. In one key session naming the
2134
+ * account and the operations, the keyring is read from the ledger as it
2135
+ * stands then, so a rotation that landed during the downloads is
2136
+ * followed, and the encryption key signs each wave with the roster key
2137
+ * of this device's keyring whose authority the chain holds; an authority none of them derives is refused with
2138
+ * MaintenanceAuthorityMismatchError before anything is submitted. Each
2139
+ * wave waits for the chain to show the advanced counter, so this
2140
+ * resolves once the chain holds every installed key, with the operations
2141
+ * it installed and those it skipped.
2142
+ *
2143
+ * No gated call is made, so the use counter and the record are left as
2144
+ * they are. The transactions' sponsoring stage is reported through
2145
+ * `onProgress`.
2146
+ */
2147
+ const installOperations = async ({ account, operations: requested }, { seams, onProgress }) => {
2148
+ const { address } = account;
2149
+ const { indexerUrl, indexerWsUrl } = seams.passportNetwork;
2150
+ const indexer = require_account_call.createIndexerClient({
2151
+ indexerUrl,
2152
+ indexerWsUrl
2153
+ });
2154
+ const { operations, alreadyInstalled } = installableOperations(requested, await require_account_call.queryAccountOperations(indexer, address));
2155
+ if (operations.length === 0) return {
2156
+ operations,
2157
+ alreadyInstalled
2158
+ };
2159
+ await assertInstallSigner({
2160
+ address,
2161
+ indexer,
2162
+ encryptionKey: seams.passportEncryptionKey
2163
+ });
2164
+ const providers = await require_providers.createMidnightProviders({
2165
+ network: seams.passportNetwork,
2166
+ prover: seams.passportProver,
2167
+ sponsor: seams.passportSponsor,
2168
+ onStage: onProgress
2169
+ });
2170
+ const { keys, sizes } = await require_gated_call.loadVerifierKeys(providers, operations);
2171
+ const waves = require_gated_call.planInstallWaves(operations, sizes).map((wave) => insertsOf(wave.operations, keys));
2172
+ return inKeySession(seams.passportAuthoriser, {
2173
+ kind: "install-operations",
2174
+ account: require_providers.AccAddress(address),
2175
+ operations
2176
+ }, async () => {
2177
+ const { keyring } = readAccountInbox(address, await require_account_call.queryAccountLedger(indexer, address), await seams.passportEncryptionKey.readerPublicKey(seams.passportNetwork.networkId));
2178
+ await installVerifierKeys({
2179
+ address,
2180
+ seams,
2181
+ providers,
2182
+ keyring,
2183
+ waves
2184
+ });
2185
+ return {
2186
+ operations,
2187
+ alreadyInstalled
2188
+ };
2189
+ });
2190
+ };
2191
+ //#endregion
2192
+ //#region ../../packages/module/passport-account/src/flows/sign-in.ts
2193
+ /**
2194
+ * The account's whole device roster, read from the on-chain device set.
2195
+ * Entries are opaque rolling commitments, so a remote entry carries no
2196
+ * identity beyond its commitment: only the local device can be named, by
2197
+ * the commitment its own rescan resolved.
2198
+ */
2199
+ const toDeviceRoster = (entries, localCommitmentHex) => Array.from(entries, (entry) => {
2200
+ const commitmentHex = require_bytes_vo.HexBytes.fromByteArray(entry);
2201
+ return {
2202
+ commitmentHex,
2203
+ isLocal: commitmentHex === localCommitmentHex
2204
+ };
2205
+ });
2206
+ /**
2207
+ * Refuses a record whose creation is still unfinished on chain. A record
2208
+ * that carries a pending deployment names a contract that may still miss
2209
+ * verifier keys, its initial device or the entries that deliver the
2210
+ * account keys, which only the create flow can complete; but a creation
2211
+ * that appended its key entries and then died before its final record
2212
+ * write left a usable account behind, and the chain's boot flag and inbox
2213
+ * tell the two apart.
2214
+ */
2215
+ const assertCreationComplete = (record, isActivated, entries) => {
2216
+ const { deployment } = record;
2217
+ if (deployment === void 0) return;
2218
+ const held = new Set(entries.map((entry) => require_bytes_vo.HexBytes.fromByteArray(entry)));
2219
+ if (!isActivated || deployment.keyEntries.some((entry) => !held.has(entry))) throw new require_errors.AccountCreationPendingError(`The account at ${record.address} is deployed but not yet ready; resume the creation on this device, from the site that started it.`);
2220
+ };
2221
+ /** The grant record parts the Lace records of an inbox opened to. */
2222
+ const grantPartsOf = (opened) => opened.flatMap((entry) => entry?.kind === "grant" ? [entry.record] : []);
2223
+ /**
2224
+ * Recognises an existing Passport account on this device: the passkey
2225
+ * ceremony (implicit in the first authoriser use) proves the device key,
2226
+ * the persisted account record names the Account Custody Contract, the
2227
+ * indexer confirms the contract is live, this device's own envelopes in
2228
+ * the contract's inbox open a secret deriving the `enc_key` the contract
2229
+ * advertises, and the use-counter rescan restores this device's roster
2230
+ * position from the on-chain device set. The account secret is never
2231
+ * read from the record or derived from the passkey: it is random, and
2232
+ * this device reaches it only through an envelope sealed to its reader
2233
+ * key, so a device enrolled with its own passkey signs in without the
2234
+ * record of the device that created the account. The roster is rebuilt
2235
+ * from every entry in the device set, so a device enrolled elsewhere
2236
+ * survives a reload; only the local entry is named, the rest are opaque
2237
+ * commitments. The grant roster is read from the contract's grants map
2238
+ * the same way, with the slots and openings the record kept for the
2239
+ * grants this client issued; a grant the record lacks, as after the
2240
+ * record was lost, is rebuilt from the grant records this device keeps in
2241
+ * the inbox once they open its on-chain commitments under this host's
2242
+ * relying party id, its seal kept only while it holds the live secret.
2243
+ * A grant the record holds is kept only while its fields open the
2244
+ * commitments the chain holds under its id, so an entry an issue wrote
2245
+ * before a call that never landed gives way to the grant another client
2246
+ * of the account issued over the same id, and a rebuilt grant takes its
2247
+ * place when there is one. The live read grants whose kept seal does not
2248
+ * hold the live secret, as after an issue that failed before its seal,
2249
+ * are reported as unsealed.
2250
+ * The devices registered under the live roster key are kept in the
2251
+ * record as the roster the next rotation seals to. The whole chain runs
2252
+ * inside one key session, so it costs a single ceremony rather than one
2253
+ * per authoriser call.
2254
+ *
2255
+ * The healed counter is written back to the persisted record before this
2256
+ * resolves, so the next sign-in rescans from a fresh anchor instead of the
2257
+ * creation-time one. That write also completes a record a creation left
2258
+ * pending after its key entries landed, dropping the deployment and the
2259
+ * authority key it carries, and drops the bookkeeping of grants the chain
2260
+ * no longer holds or whose commitments it does not open. A missing record
2261
+ * raises AccountNotFoundError, a record whose address holds no live
2262
+ * contract raises AccountContractMissingError,
2263
+ * a record whose creation is unfinished on chain raises
2264
+ * AccountCreationPendingError, and an inbox holding no envelope of this
2265
+ * device that opens the live `enc_key`, as for a device never handed the
2266
+ * keys or removed since, raises EncryptionKeyMismatchError before the
2267
+ * rescan runs; rediscovering an account by name service when no record is
2268
+ * persisted is a separate flow and out of scope here.
2269
+ *
2270
+ * signIn reports no intermediate stage: the ceremony, the indexer read and
2271
+ * the rescan all settle before there is anything to report.
2272
+ */
2273
+ const signIn = async ({ seams, accountRecords, rpId }) => {
2274
+ const { passportAuthoriser, passportEncryptionKey, passportNetwork } = seams;
2275
+ return inKeySession(passportAuthoriser, { kind: "sign-in" }, async () => {
2276
+ const local = await readLocalDevice(seams);
2277
+ const record = await accountRecords.read();
2278
+ if (!record) throw new require_errors.AccountNotFoundError("No Passport account record is stored on this device.");
2279
+ const state = await require_account_call.createIndexerClient({
2280
+ indexerUrl: passportNetwork.indexerUrl,
2281
+ indexerWsUrl: passportNetwork.indexerWsUrl
2282
+ }).queryContractState(record.address);
2283
+ if (!state) throw new require_errors.AccountContractMissingError(`No live Account Custody Contract found at ${record.address}.`);
2284
+ const ledgerState = require_acc_module.ledger(state.data);
2285
+ const { entries, keyring } = readAccountInbox(record.address, ledgerState, local.readerPublicKey);
2286
+ assertCreationComplete(record, ledgerState.booted, entries);
2287
+ if (keyring.envelopes.length === 0) throw new require_errors.EncryptionKeyMismatchError(`The inbox of the account at ${record.address} holds no envelope for this device.`);
2288
+ const grantParts = grantPartsOf(await passportEncryptionKey.openInbox(passportNetwork.networkId, keyring, entries.filter(require_envelopes.isLaceRecord)));
2289
+ const { useCounter, commitmentHex } = await require_gated_call.resolveUseCounter({
2290
+ ledgerState,
2291
+ authoriser: passportAuthoriser,
2292
+ address: require_providers.AccAddress(record.address),
2293
+ knownUseCounter: require_gated_call.UseCounter(BigInt(record.localUseCounter))
2294
+ });
2295
+ const devices = toDeviceRoster(ledgerState.devices, commitmentHex);
2296
+ const encKey = require_bytes_vo.HexBytes.fromByteArray(ledgerState.enc_key);
2297
+ const onChain = readGrants(ledgerState);
2298
+ const stored = withRebuiltGrants(await retainStoredGrants(record.grants, onChain, record.address), rpId === void 0 ? void 0 : await rebuildStoredGrants(grantParts, onChain, {
2299
+ address: record.address,
2300
+ origin: rpId,
2301
+ encKey
2302
+ }), encKey);
2303
+ const grants = readGrants(ledgerState, storedGrantSlots(stored), storedGrantOpenings(stored));
2304
+ const localUseCounter = useCounter.toString();
2305
+ await accountRecords.write(completedRecord(record, localUseCounter, { grants: stored }));
2306
+ return {
2307
+ account: {
2308
+ address: record.address,
2309
+ bindingVersion: record.bindingVersion,
2310
+ status: "ready",
2311
+ networkId: passportNetwork.networkId
2312
+ },
2313
+ devices,
2314
+ localUseCounter,
2315
+ grants,
2316
+ encKey,
2317
+ unsealedGrantIds: unsealedStoredReadGrants(stored, onChain, encKey)
2318
+ };
2319
+ });
2320
+ };
2321
+ //#endregion
2322
+ //#region ../../packages/module/passport-account/src/store/dependencies.ts
2323
+ /**
2324
+ * Runs a flow as a cold stream of its events. `run` is invoked once per
2325
+ * subscription with a progress sink that emits synchronously as the flow
2326
+ * reports; the resolution emits 'done' and completes, and a rejection
2327
+ * errors the stream. Unsubscribing only stops delivery: the running
2328
+ * promise is not cancelled, because a flow must run to completion once
2329
+ * started (abandoning it mid-deploy could orphan a sponsored contract).
2330
+ */
2331
+ const observeFlow = (run) => new rxjs.Observable((subscriber) => {
2332
+ let isDelivering = true;
2333
+ run((stage) => {
2334
+ if (isDelivering) subscriber.next({
2335
+ type: "progress",
2336
+ stage
2337
+ });
2338
+ }).then((result) => {
2339
+ if (!isDelivering) return;
2340
+ subscriber.next({
2341
+ type: "done",
2342
+ result
2343
+ });
2344
+ subscriber.complete();
2345
+ }, (error) => {
2346
+ if (isDelivering) subscriber.error(error);
2347
+ });
2348
+ return () => {
2349
+ isDelivering = false;
2350
+ };
2351
+ });
2352
+ /**
2353
+ * The context a flow runs under, drawn from the side-effect dependency
2354
+ * bag: the five seams, the record store and the relying party id. An
2355
+ * injected store is used as is; otherwise the store is built over the
2356
+ * platform storage and keyed by the authoriser, so a record sealed by one
2357
+ * authoriser is only ever written through it. The sealed-record options
2358
+ * apply to that built store; signIn and the grant flows pass
2359
+ * `onSealedWithoutKey: 'error'` so a sealed record found without a
2360
+ * storage key surfaces as record-unreadable rather than as an absent
2361
+ * account.
2362
+ */
2363
+ const toFlowContext = ({ createKeyValueStorage, passportAuthoriser, passportEncryptionKey, passportNetwork, passportProver, passportSponsor }, onProgress, { store, rpId, ...recordOptions }) => ({
2364
+ seams: {
2365
+ passportAuthoriser,
2366
+ passportEncryptionKey,
2367
+ passportNetwork,
2368
+ passportProver,
2369
+ passportSponsor
2370
+ },
2371
+ accountRecords: toAccountRecords(store ?? createAccountRecordStore(createKeyValueStorage, passportAuthoriser.storageKey, recordOptions)),
2372
+ onProgress,
2373
+ rpId
2374
+ });
2375
+ /**
2376
+ * Maps the platform capabilities passed to `createPassportAccountModule`
2377
+ * onto the `passport-dependency` contract, and exposes the platform-neutral
2378
+ * flows observable-based. The flows are promise functions; wrapping them
2379
+ * here rather than in the side effects is what keeps promise handling out
2380
+ * of the side effects, so they compose the flows as plain streams and stay
2381
+ * testable on a virtual scheduler. The relying party id defaults to the
2382
+ * page's hostname, as the passkey ceremony's does.
2383
+ */
2384
+ const createDependencies = ({ authoriser, encryptionKey, sponsor, prover, network, accountRecordStore, rpId = globalThis.location?.hostname }) => {
2385
+ const host = {
2386
+ store: accountRecordStore,
2387
+ rpId
2388
+ };
2389
+ const recordRequired = {
2390
+ ...host,
2391
+ onSealedWithoutKey: "error"
2392
+ };
2393
+ return {
2394
+ passportAuthoriser: authoriser,
2395
+ passportEncryptionKey: encryptionKey,
2396
+ passportSponsor: sponsor,
2397
+ passportProver: prover,
2398
+ passportNetwork: network,
2399
+ passportFlows: {
2400
+ createAccount: (input, dependencies) => observeFlow(async (onProgress) => createAccount(input, toFlowContext(dependencies, onProgress, host))),
2401
+ signIn: (dependencies) => observeFlow(async (onProgress) => signIn(toFlowContext(dependencies, onProgress, recordRequired))),
2402
+ addDevice: (input, dependencies) => observeFlow(async (onProgress) => addDevice(input, toFlowContext(dependencies, onProgress, host))),
2403
+ removeDevice: (input, dependencies) => observeFlow(async (onProgress) => removeDevice(input, toFlowContext(dependencies, onProgress, host))),
2404
+ issueGrant: (input, dependencies) => observeFlow(async (onProgress) => issueGrant(input, toFlowContext(dependencies, onProgress, recordRequired))),
2405
+ revokeGrant: (input, dependencies) => observeFlow(async (onProgress) => revokeGrant(input, toFlowContext(dependencies, onProgress, recordRequired))),
2406
+ revokeAllGrants: (input, dependencies) => observeFlow(async (onProgress) => revokeAllGrants(input, toFlowContext(dependencies, onProgress, recordRequired))),
2407
+ readShieldedHoldings: (input, dependencies) => observeFlow(async (onProgress) => readShieldedHoldings(input, toFlowContext(dependencies, onProgress, host))),
2408
+ installOperations: (input, dependencies) => observeFlow(async (onProgress) => installOperations(input, toFlowContext(dependencies, onProgress, host)))
2409
+ }
2410
+ };
2411
+ };
2412
+ //#endregion
2413
+ //#region ../../packages/module/passport-account/src/store/side-effects/flow-actions.ts
2414
+ /**
2415
+ * The flow error a failure reports: the error's own typed code when it
2416
+ * carries one, otherwise the flow's fallback code.
2417
+ */
2418
+ const toFlowError = (error, fallbackCode) => ({
2419
+ code: error instanceof Object && "code" in error && typeof error.code === "string" ? error.code : fallbackCode,
2420
+ message: error instanceof Error ? error.message : String(error)
2421
+ });
2422
+ /**
2423
+ * Maps a flow's events onto the passport flow state: each stage to
2424
+ * setFlow, the result to its publishing actions followed by
2425
+ * setFlow('ready'), and a failure to its `onFailed` actions followed by
2426
+ * a single setFlowError, so a flow always settles in 'ready' or 'error'.
2427
+ */
2428
+ const flowActions = (events$, { actions, fallbackCode, onDone, onFailed = [] }) => events$.pipe((0, rxjs.mergeMap)((event) => event.type === "progress" ? [actions.passport.setFlow(event.stage)] : [...onDone(event.result), actions.passport.setFlow("ready")]), (0, rxjs.catchError)((error) => (0, rxjs.of)(...onFailed, actions.passport.setFlowError(toFlowError(error, fallbackCode)))));
2429
+ //#endregion
2430
+ //#region ../../packages/module/passport-account/src/store/side-effects/passport-flows.ts
2431
+ /** Flow error code for creation failures that carry no typed code of their own. */
2432
+ const CREATE_ACCOUNT_FALLBACK_ERROR_CODE = "account-create-failed";
2433
+ /** Flow error code for sign-in failures that carry no typed code of their own. */
2434
+ const SIGN_IN_FALLBACK_ERROR_CODE = "account-sign-in-failed";
2435
+ /** Flow error codes for device call failures that carry no typed code of their own. */
2436
+ const ADD_DEVICE_FALLBACK_ERROR_CODE = "device-add-failed";
2437
+ const REMOVE_DEVICE_FALLBACK_ERROR_CODE = "device-remove-failed";
2438
+ /** Flow error codes for grant call failures that carry no typed code of their own. */
2439
+ const ISSUE_GRANT_FALLBACK_ERROR_CODE = "grant-issue-failed";
2440
+ const REVOKE_GRANT_FALLBACK_ERROR_CODE = "grant-revoke-failed";
2441
+ const REVOKE_ALL_GRANTS_FALLBACK_ERROR_CODE = "grant-revoke-all-failed";
2442
+ /** Flow error code for holdings reads that carry no typed code of their own. */
2443
+ const READ_HOLDINGS_FALLBACK_ERROR_CODE = "holdings-read-failed";
2444
+ /** Flow error code for installs that carry no typed code of their own. */
2445
+ const INSTALL_OPERATIONS_FALLBACK_ERROR_CODE = "operations-install-failed";
2446
+ /**
2447
+ * Flows an account call may start from. 'error' is a settled state, not
2448
+ * an operation in progress, so a call that failed can be retried without
2449
+ * a fresh sign-in ceremony; every other non-ready flow is an operation
2450
+ * left unfinished, whose account and use counter cannot be trusted yet.
2451
+ */
2452
+ const SETTLED_FLOWS = ["error", "ready"];
2453
+ /**
2454
+ * The actions that publish a roster a flow left behind: the devices, then
2455
+ * the counter the local row sits at. Both land ahead of 'ready', in the
2456
+ * order the flows have always published them.
2457
+ */
2458
+ const publishDeviceRoster = (actions) => ({ devices, localUseCounter }) => [actions.passport.setDevices(devices), actions.passport.setLocalUseCounter(localUseCounter)];
2459
+ /**
2460
+ * The actions that publish what a grant call left behind: the device
2461
+ * roster and counter it advanced, the `enc_key` a rotation it ran stepped
2462
+ * the encryption key to and the devices it left without the new key, the
2463
+ * grant roster, then the read grants the
2464
+ * call could not seal the viewing secret to, empty when it sealed all,
2465
+ * and those the signer refused because their revocation is pending.
2466
+ */
2467
+ const publishGrantRoster = (actions) => ({ grants, encKey, unsealedGrantIds, revocationPendingGrantIds, droppedDevices, ...roster }) => [
2468
+ ...publishDeviceRoster(actions)(roster),
2469
+ ...encKey === void 0 ? [] : [actions.passport.setEncKey(encKey)],
2470
+ ...droppedDevices === void 0 ? [] : [actions.passport.setDroppedDevices(droppedDevices)],
2471
+ actions.passport.setGrants(grants),
2472
+ actions.passport.setUnsealedGrantIds(unsealedGrantIds),
2473
+ actions.passport.setRevocationPendingGrantIds(revocationPendingGrantIds)
2474
+ ];
2475
+ /**
2476
+ * The actions that publish an issued grant: the rosters, then the id of
2477
+ * the grant, last so a consumer reacting to it finds the record in state.
2478
+ */
2479
+ const publishIssuedGrant = (actions) => ({ grantId, ...roster }) => [...publishGrantRoster(actions)(roster), actions.passport.setLastIssuedGrantId(grantId)];
2480
+ /**
2481
+ * The actions that publish an account a flow left ready: the account,
2482
+ * its device roster and counter, the `enc_key` its encryption key is
2483
+ * at, then its grant roster.
2484
+ */
2485
+ const publishAccountState = (actions) => ({ account, grants, encKey, ...roster }) => [
2486
+ actions.passport.setAccount(account),
2487
+ ...publishDeviceRoster(actions)(roster),
2488
+ actions.passport.setEncKey(encKey),
2489
+ actions.passport.setGrants(grants)
2490
+ ];
2491
+ /**
2492
+ * The actions that publish an account a sign-in recognised: the account
2493
+ * state, see {@link publishAccountState}, then the read grants whose kept
2494
+ * seal does not hold the live viewing secret, and no grants pending a
2495
+ * revocation, which only a grant call learns from the signer.
2496
+ */
2497
+ const publishRecognisedAccount = (actions) => ({ unsealedGrantIds, ...state }) => [
2498
+ ...publishAccountState(actions)(state),
2499
+ actions.passport.setUnsealedGrantIds(unsealedGrantIds),
2500
+ actions.passport.setRevocationPendingGrantIds([])
2501
+ ];
2502
+ /**
2503
+ * A device or grant call may rotate the account keys before it fails, so
2504
+ * the `enc_key` state holds may be the one rotated away: it is forgotten
2505
+ * until the next sign-in or rotation publishes it again, rather than let
2506
+ * a stale seal pass as current. The read grants an earlier call reported
2507
+ * unsealed or pending revocation were judged against that key, so they
2508
+ * are emptied with it.
2509
+ */
2510
+ const forgetKeyState = (actions) => [
2511
+ actions.passport.clearEncKey(),
2512
+ actions.passport.setUnsealedGrantIds([]),
2513
+ actions.passport.setRevocationPendingGrantIds([])
2514
+ ];
2515
+ /**
2516
+ * The createAccount flow behind the createAccount action, as the actions it
2517
+ * publishes: each reported stage as setFlow, then the created account, its
2518
+ * one-device roster, this device's counter and its empty grant roster,
2519
+ * then 'ready'. The flow itself persists the account record before it
2520
+ * resolves, so the account is never published ahead of the record that
2521
+ * recognises it.
2522
+ */
2523
+ const runCreateAccount = (input, dependencies) => {
2524
+ const { actions, passportFlows } = dependencies;
2525
+ return flowActions(passportFlows.createAccount(input, dependencies), {
2526
+ actions,
2527
+ fallbackCode: CREATE_ACCOUNT_FALLBACK_ERROR_CODE,
2528
+ onDone: publishAccountState(actions)
2529
+ });
2530
+ };
2531
+ /**
2532
+ * The signIn flow behind the signIn action, as the actions it publishes:
2533
+ * the recognised account, its whole device roster, this device's healed
2534
+ * use counter, the grant roster and its unsealed read grants, then
2535
+ * 'ready'. The flow itself persists the healed counter before it
2536
+ * resolves, so the record is never left behind the state it publishes.
2537
+ */
2538
+ const runSignIn = (dependencies) => {
2539
+ const { actions, passportFlows } = dependencies;
2540
+ return flowActions(passportFlows.signIn(dependencies), {
2541
+ actions,
2542
+ fallbackCode: SIGN_IN_FALLBACK_ERROR_CODE,
2543
+ onDone: publishRecognisedAccount(actions)
2544
+ });
2545
+ };
2546
+ /**
2547
+ * The actions that publish what a device call left behind: its roster and
2548
+ * counter, then, when it rotated the account keys, the `enc_key` it
2549
+ * stepped to and the devices it left without the new key.
2550
+ */
2551
+ const publishDeviceCall = (actions) => ({ encKey, droppedDevices, ...roster }) => [
2552
+ ...publishDeviceRoster(actions)(roster),
2553
+ ...encKey === void 0 ? [] : [actions.passport.setEncKey(encKey)],
2554
+ ...droppedDevices === void 0 ? [] : [actions.passport.setDroppedDevices(droppedDevices)]
2555
+ ];
2556
+ /**
2557
+ * The addDevice flow behind its action: what the call left behind, see
2558
+ * {@link publishDeviceCall}, then 'ready'. The joining device's keys
2559
+ * travel with the request when the action names them.
2560
+ */
2561
+ const addDeviceCall = {
2562
+ start: ({ commitmentHex, reader }, input, dependencies) => dependencies.passportFlows.addDevice({
2563
+ commitmentHex,
2564
+ ...reader ? { reader } : {},
2565
+ ...input
2566
+ }, dependencies),
2567
+ onDone: publishDeviceCall,
2568
+ fallbackCode: ADD_DEVICE_FALLBACK_ERROR_CODE,
2569
+ onFailed: forgetKeyState
2570
+ };
2571
+ /**
2572
+ * The removeDevice flow behind its action: what the call left behind, see
2573
+ * {@link publishDeviceCall}, then 'ready'.
2574
+ */
2575
+ const removeDeviceCall = {
2576
+ start: ({ commitmentHex }, input, dependencies) => dependencies.passportFlows.removeDevice({
2577
+ commitmentHex,
2578
+ ...input
2579
+ }, dependencies),
2580
+ onDone: publishDeviceCall,
2581
+ fallbackCode: REMOVE_DEVICE_FALLBACK_ERROR_CODE,
2582
+ onFailed: forgetKeyState
2583
+ };
2584
+ /**
2585
+ * The issueGrant flow behind its action: the rosters the call left
2586
+ * behind and the issued grant's id, then 'ready'.
2587
+ */
2588
+ const issueGrantCall = {
2589
+ start: (scope, input, dependencies) => dependencies.passportFlows.issueGrant({
2590
+ scope,
2591
+ ...input
2592
+ }, dependencies),
2593
+ onDone: publishIssuedGrant,
2594
+ fallbackCode: ISSUE_GRANT_FALLBACK_ERROR_CODE,
2595
+ onFailed: forgetKeyState
2596
+ };
2597
+ /** The revokeGrant flow behind its action: the rosters it left behind, then 'ready'. */
2598
+ const revokeGrantCall = {
2599
+ start: ({ grantId }, input, dependencies) => dependencies.passportFlows.revokeGrant({
2600
+ grantId,
2601
+ ...input
2602
+ }, dependencies),
2603
+ onDone: publishGrantRoster,
2604
+ fallbackCode: REVOKE_GRANT_FALLBACK_ERROR_CODE,
2605
+ onFailed: forgetKeyState
2606
+ };
2607
+ /** The revokeAllGrants flow behind its action: the rosters it left behind, then 'ready'. */
2608
+ const revokeAllGrantsCall = {
2609
+ start: (_payload, input, dependencies) => dependencies.passportFlows.revokeAllGrants(input, dependencies),
2610
+ onDone: publishGrantRoster,
2611
+ fallbackCode: REVOKE_ALL_GRANTS_FALLBACK_ERROR_CODE,
2612
+ onFailed: forgetKeyState
2613
+ };
2614
+ /**
2615
+ * The readShieldedHoldings flow behind its action: the holdings the walk
2616
+ * verified, then 'ready'. The skip count is the flow's to report, not the
2617
+ * store's to keep: a skipped entry is noise the inbox will carry forever.
2618
+ */
2619
+ const readShieldedHoldingsCall = {
2620
+ start: (_payload, input, dependencies) => dependencies.passportFlows.readShieldedHoldings(input, dependencies),
2621
+ onDone: (actions) => ({ holdings }) => [actions.passport.setShieldedHoldings(holdings)],
2622
+ fallbackCode: READ_HOLDINGS_FALLBACK_ERROR_CODE
2623
+ };
2624
+ /**
2625
+ * The installOperations flow behind its action, then 'ready'. A landed
2626
+ * install changes nothing state holds, so it publishes nothing of its
2627
+ * own.
2628
+ */
2629
+ const installOperationsCall = {
2630
+ start: ({ operations }, { account }, dependencies) => dependencies.passportFlows.installOperations({
2631
+ account,
2632
+ operations
2633
+ }, dependencies),
2634
+ onDone: () => () => [],
2635
+ fallbackCode: INSTALL_OPERATIONS_FALLBACK_ERROR_CODE
2636
+ };
2637
+ /**
2638
+ * An account call behind its action, as the actions it publishes: what
2639
+ * the call left behind, then 'ready', or, when it fails, the actions
2640
+ * undoing what state may wrongly claim, then the error. The flow itself
2641
+ * persists the counter before it resolves.
2642
+ *
2643
+ * Requires an account and a settled flow: with no account in state, or
2644
+ * with an operation left unfinished, the flow is never called (so the
2645
+ * chain and the key session are never touched) and the call settles in
2646
+ * 'error' with 'no-account'. The refusal still runs behind the gate, so
2647
+ * it enters 'ceremony' first like any accepted request and clears the
2648
+ * error a previous flow left behind.
2649
+ */
2650
+ const runAccountCall = ({ start, onDone, fallbackCode, onFailed }, { payload, flow, account, devices, localUseCounter }, dependencies) => {
2651
+ const { actions } = dependencies;
2652
+ if (!account || !SETTLED_FLOWS.includes(flow)) return (0, rxjs.of)(actions.passport.setFlowError(toFlowError(new require_errors.NoAccountError(), fallbackCode)));
2653
+ return flowActions(start(payload, {
2654
+ account,
2655
+ devices,
2656
+ localUseCounter
2657
+ }, dependencies), {
2658
+ actions,
2659
+ fallbackCode,
2660
+ onDone: onDone(actions),
2661
+ ...onFailed ? { onFailed: onFailed(actions) } : {}
2662
+ });
2663
+ };
2664
+ /**
2665
+ * One start per dispatched account call action, each carrying the state
2666
+ * the call needs. State is sampled when the action arrives rather than
2667
+ * when the shared gate starts the call, so a call reads the account,
2668
+ * roster and counter the user acted on.
2669
+ */
2670
+ const accountCallStarts = ({ config, trigger$, stateObservables: { passport: { selectAccount$, selectDevices$, selectFlow$, selectLocalUseCounter$ } }, dependencies }) => trigger$.pipe((0, rxjs.withLatestFrom)(selectFlow$, selectAccount$, selectDevices$, selectLocalUseCounter$), (0, rxjs.map)(([{ payload }, flow, account, devices, localUseCounter]) => () => runAccountCall(config, {
2671
+ payload,
2672
+ flow,
2673
+ account,
2674
+ devices,
2675
+ localUseCounter
2676
+ }, dependencies)));
2677
+ /**
2678
+ * Runs whichever passport flow was requested: {@link runCreateAccount}
2679
+ * behind the createAccount action, {@link runSignIn} behind signIn, and
2680
+ * {@link runAccountCall} behind addDevice, removeDevice, issueGrant,
2681
+ * revokeGrant, revokeAllGrants, readShieldedHoldings and
2682
+ * installOperations.
2683
+ *
2684
+ * Every flow owns the same account record, the same device key and the
2685
+ * same flow state, and each chain call resolves the account's use counter
2686
+ * and auth nonce, so they must never overlap: two calls resolving the same
2687
+ * counter both spend the same entry, one transaction is rejected, and the
2688
+ * loser reports its own failure over state the winner already moved on.
2689
+ * A holdings read spends no entry, but it opens the same key session and
2690
+ * reports through the same flow state, so it runs behind the same gate.
2691
+ * One exhaustMap over the merged starts, rather than one per action,
2692
+ * makes them mutually exclusive: a dispatch arriving while any flow is in
2693
+ * flight emits nothing at all, and the next one is only accepted once the
2694
+ * running flow settles. Dropping it silently is the stance exhaustMap
2695
+ * already took for a repeated dispatch of one action; a flow the user
2696
+ * never saw start is not a failure to report.
2697
+ *
2698
+ * Because only the gate knows which requests it accepted, it also owns
2699
+ * the entry into 'ceremony': the trigger reducers are pure, so a dropped
2700
+ * request cannot overwrite the running flow's state, and setFlow clears
2701
+ * the error a previous flow left behind. The emission is synchronous on
2702
+ * the accepted start, so a consumer sees the ceremony within the same
2703
+ * dispatch cycle as the trigger.
2704
+ *
2705
+ * The flow state cannot carry this guard the other way round: state
2706
+ * reaches a side effect before the action does, and an inner flow is
2707
+ * still subscribed while its own terminal setFlow('ready') is delivered,
2708
+ * so a reducer reading the flow back cannot tell a request the gate will
2709
+ * accept from one it will drop.
2710
+ */
2711
+ const runPassportFlow = ({ passport: { addDevice$, createAccount$, installOperations$, issueGrant$, readShieldedHoldings$, removeDevice$, revokeAllGrants$, revokeGrant$, signIn$ } }, stateObservables, dependencies) => (0, rxjs.merge)(createAccount$.pipe((0, rxjs.map)(({ payload }) => () => runCreateAccount(payload, dependencies))), signIn$.pipe((0, rxjs.map)(() => () => runSignIn(dependencies))), accountCallStarts({
2712
+ config: addDeviceCall,
2713
+ trigger$: addDevice$,
2714
+ stateObservables,
2715
+ dependencies
2716
+ }), accountCallStarts({
2717
+ config: removeDeviceCall,
2718
+ trigger$: removeDevice$,
2719
+ stateObservables,
2720
+ dependencies
2721
+ }), accountCallStarts({
2722
+ config: issueGrantCall,
2723
+ trigger$: issueGrant$,
2724
+ stateObservables,
2725
+ dependencies
2726
+ }), accountCallStarts({
2727
+ config: revokeGrantCall,
2728
+ trigger$: revokeGrant$,
2729
+ stateObservables,
2730
+ dependencies
2731
+ }), accountCallStarts({
2732
+ config: revokeAllGrantsCall,
2733
+ trigger$: revokeAllGrants$,
2734
+ stateObservables,
2735
+ dependencies
2736
+ }), accountCallStarts({
2737
+ config: readShieldedHoldingsCall,
2738
+ trigger$: readShieldedHoldings$,
2739
+ stateObservables,
2740
+ dependencies
2741
+ }), accountCallStarts({
2742
+ config: installOperationsCall,
2743
+ trigger$: installOperations$,
2744
+ stateObservables,
2745
+ dependencies
2746
+ })).pipe((0, rxjs.exhaustMap)((start) => (0, rxjs.concat)((0, rxjs.of)(dependencies.actions.passport.setFlow("ceremony")), start())));
2747
+ //#endregion
2748
+ //#region ../../packages/module/passport-account/src/store/side-effects/restore-account.ts
2749
+ /** Whether a stored record names an activated account rather than a deployment in progress. */
2750
+ const isActivatedRecord = (record) => record !== void 0 && record.deployment === void 0;
2751
+ /**
2752
+ * The grant roster of the restored account, read from chain with the
2753
+ * slots the record kept. The read needs no ceremony, but it does need
2754
+ * the indexer: a contract it does not serve, or a read it fails, leaves
2755
+ * the roster empty rather than failing the restore, and the next sign-in
2756
+ * or grant call reads it again.
2757
+ */
2758
+ const restoreGrants = (record, { indexerUrl, indexerWsUrl }, actions) => (0, rxjs.defer)(async () => require_account_call.createIndexerClient({
2759
+ indexerUrl,
2760
+ indexerWsUrl
2761
+ }).queryContractState(record.address)).pipe((0, rxjs.filter)((state) => state !== null && state !== void 0), (0, rxjs.map)((state) => actions.passport.setGrants(readGrants(require_acc_module.ledger(state.data), storedGrantSlots(record.grants)))), (0, rxjs.catchError)(() => rxjs.EMPTY));
2762
+ /**
2763
+ * Rehydrates the persisted account record into the store on startup, so a
2764
+ * returning user's account is recognised without a new passkey ceremony.
2765
+ * Only a record whose activation succeeded is restored, hence status
2766
+ * 'ready'; a record still carrying a pending deployment names no usable
2767
+ * account and is left for the create flow to resume, or for sign-in to
2768
+ * complete once the chain shows it activated. The device set is not
2769
+ * persisted and is rebuilt from chain by the device-management flows; the
2770
+ * grant roster is read from chain here, once the account is published,
2771
+ * so a consumer lists grants without a ceremony. A record sealed by a
2772
+ * passkey authoriser cannot be opened without a ceremony, and a ceremony
2773
+ * cannot run without a user gesture, so no storage key is passed here: a
2774
+ * sealed record reads as absent and the sign-in flow recognises the
2775
+ * account instead.
2776
+ */
2777
+ const restorePassportAccount = (_actionObservables, _stateObservables, { actions, createKeyValueStorage, passportNetwork }) => createAccountRecordStore(createKeyValueStorage).read().pipe((0, rxjs.filter)(isActivatedRecord), (0, rxjs.mergeMap)((record) => (0, rxjs.concat)((0, rxjs.of)(actions.passport.setAccount({
2778
+ address: record.address,
2779
+ bindingVersion: record.bindingVersion,
2780
+ status: "ready",
2781
+ networkId: passportNetwork.networkId
2782
+ }), actions.passport.setLocalUseCounter(record.localUseCounter)), restoreGrants(record, passportNetwork, actions))));
2783
+ //#endregion
2784
+ //#region ../../packages/module/passport-account/src/store/init.ts
2785
+ /**
2786
+ * Builds the passport-account module's store initializer for the given
2787
+ * platform capabilities: the mutually exclusive passport flows behind the
2788
+ * createAccount, signIn, addDevice, removeDevice, issueGrant, revokeGrant
2789
+ * and revokeAllGrants actions, and the restore of a previously created
2790
+ * account from its persisted record.
2791
+ */
2792
+ const createInit = (props) => () => ({
2793
+ sideEffects: [runPassportFlow, restorePassportAccount],
2794
+ sideEffectDependencies: createDependencies(props)
2795
+ });
2796
+ //#endregion
2797
+ exports.createInit = createInit;