@laceio/sdk 0.1.0 → 0.1.1-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (408) hide show
  1. package/dist/{Ed25519PublicKey-lvu1s54U.cjs → Address-AMKTo-dA.cjs} +17150 -61
  2. package/dist/{Ed25519PublicKey-CjnuAYRR.js → Address-_c_-9hLC.js} +16955 -264
  3. package/dist/{Bip32Account-r3KAhCgU.js → Bip32Account-CaW1zC9Y.js} +4 -5
  4. package/dist/{Bip32Account-Cz3h0ZUa.cjs → Bip32Account-DOaGmKpv.cjs} +4 -5
  5. package/dist/Cardano-B-gyqBBD.js +311 -0
  6. package/dist/Cardano-BQ_af0Y3.cjs +322 -0
  7. package/dist/Ed25519PublicKey-B3QcR0J2.cjs +88 -0
  8. package/dist/Ed25519PublicKey-DIE19xHg.js +83 -0
  9. package/dist/InMemoryKeyAgent-Bc_xZY-v.cjs +652 -0
  10. package/dist/InMemoryKeyAgent-D8T3vtXy.js +631 -0
  11. package/dist/{PaymentAddress-DhbtuYb7.cjs → PaymentAddress-B4n1KZMx.cjs} +1 -2
  12. package/dist/{PaymentAddress-xSmGUQ-8.js → PaymentAddress-Cml3EA1n.js} +1 -2
  13. package/dist/Percent-Cfuw5YyL.cjs +11 -0
  14. package/dist/Percent-DpvyJ_93.js +5 -0
  15. package/dist/SodiumBip32Ed25519-BaD-kjyx.cjs +380 -0
  16. package/dist/SodiumBip32Ed25519-DCikAMsl.js +363 -0
  17. package/dist/{TransactionBuilder-CeJTNzm-.cjs → TransactionBuilder-BakTJvsJ.cjs} +59 -89
  18. package/dist/{TransactionBuilder-DGNDM4kt.js → TransactionBuilder-D2ErwVWv.js} +5 -71
  19. package/dist/_md-6zV2Cme0.cjs +684 -0
  20. package/dist/_md-gbUqlhqd.js +399 -0
  21. package/dist/_web-crypto-random-bytes-BvzwTYPG.js +197 -0
  22. package/dist/acc-module-NL_8mEHH.cjs +27654 -0
  23. package/dist/acc-module-ur8CkBd1.js +27624 -0
  24. package/dist/account-call-B7cF8ly4.cjs +293 -0
  25. package/dist/account-call-BF4rBcNw.js +3 -0
  26. package/dist/account-call-CoCq3V55.js +252 -0
  27. package/dist/account-call-DFcD257E.cjs +4 -0
  28. package/dist/account-guards-BeKbV9D1.cjs +41 -0
  29. package/dist/account-guards-qnFXuXSE.js +36 -0
  30. package/dist/{activity-type.stable-ClO9j_PH.cjs → activity-type-BPXxdHBP.cjs} +2 -1
  31. package/dist/{activity-type.stable-Ml0SOYv9.js → activity-type-C1geyPsH.js} +2 -1
  32. package/dist/address-OxwXIwbF.js +15502 -0
  33. package/dist/address-book-address-validator-BqYM0Uyp.cjs +41 -0
  34. package/dist/{address-book-address-validator-BhqRfmlL.cjs → address-book-address-validator-CGIcqn5W.cjs} +5 -4
  35. package/dist/{address-book-address-validator-VqIOB8PY.js → address-book-address-validator-FkkYim3z.js} +6 -4
  36. package/dist/address-book-address-validator-RSx2Jozy.js +39 -0
  37. package/dist/address-lECYBd9-.cjs +13043 -0
  38. package/dist/{apply-vkey-witnesses-Ctzk7eye.cjs → apply-vkey-witnesses-1jyczTkw.cjs} +1 -1
  39. package/dist/{apply-vkey-witnesses-Dfo7XfhL.js → apply-vkey-witnesses-Byb40-yJ.js} +1 -1
  40. package/dist/artefact-loader-Cqc9eSkL.cjs +182 -0
  41. package/dist/artefact-loader-D0T2f6F7.js +159 -0
  42. package/dist/auto-dismiss-failure-on-success-BWJceNXd.cjs +37 -0
  43. package/dist/auto-dismiss-failure-on-success-Bpod-v4r.js +31 -0
  44. package/dist/bip32-EPpcFrpH.cjs +3851 -0
  45. package/dist/{bip32ed25519-D2In8WzR.cjs → bip32ed25519-CRiG-Kjg.cjs} +5 -4
  46. package/dist/{bip32ed25519-qsLH_eWO.js → bip32ed25519-E-fCUZNv.js} +6 -4
  47. package/dist/bip39-DZomfqeY.js +9 -0
  48. package/dist/bip39-MAck9iod.cjs +26 -0
  49. package/dist/bitcoin-data-provider-D0OT_Uiu.cjs +26 -0
  50. package/dist/bitcoin-data-provider-LN_8oamW.js +21 -0
  51. package/dist/bitcoin.cjs +48 -0
  52. package/dist/bitcoin.d.ts +29805 -1
  53. package/dist/bitcoin.js +22 -0
  54. package/dist/{blake2b-BGHUWMsV.js → blake2b-BL-eR5G2.js} +6 -4
  55. package/dist/{blake2b-DspUqw68.cjs → blake2b-Iy324FwH.cjs} +5 -4
  56. package/dist/{blockfrost-tx-submit-provider-BUEOmdvc.js → blockfrost-tx-submit-provider-BHDLgOIE.js} +9 -162
  57. package/dist/{blockfrost-tx-submit-provider-B5dWbdrZ.cjs → blockfrost-tx-submit-provider-BxC19smv.cjs} +7 -171
  58. package/dist/{blockingWithLatestFrom-BhaE7pnA.js → blockingWithLatestFrom-FUfZFEUG.js} +1 -1
  59. package/dist/{blockingWithLatestFrom-DkuT3G2a.cjs → blockingWithLatestFrom-bmCPgA3c.cjs} +1 -1
  60. package/dist/bn-BFq5a4dk.cjs +2553 -0
  61. package/dist/{SodiumBip32Ed25519-BEfo1Q9m.js → browser-D5jRKv_L.js} +4 -401
  62. package/dist/bytes.vo-CziaOsba.js +24 -0
  63. package/dist/bytes.vo-hecRu3YZ.cjs +35 -0
  64. package/dist/{cardano-network-id-to-network-type-PXvn1ycQ.cjs → cardano-network-id-to-network-type-BwG_d6QP.cjs} +1 -1
  65. package/dist/{cardano-network-id-to-network-type-DHgKhDAK.js → cardano-network-id-to-network-type-CYJheetp.js} +1 -1
  66. package/dist/{cardano-network-id.vo-CbNNKd-k.cjs → cardano-network-id.vo-C5VuBXT3.cjs} +2 -2
  67. package/dist/{cardano-network-id.vo-CTYe-Oix.js → cardano-network-id.vo-CeLJqmb_.js} +2 -2
  68. package/dist/{cardano-tx-id.vo-BMwemq5x.js → cardano-tx-id.vo-DJ70NC0-.js} +1 -1
  69. package/dist/{cardano-tx-id.vo-NY-gCOWJ.cjs → cardano-tx-id.vo-lnyO9XaW.cjs} +1 -1
  70. package/dist/cardano.cjs +31 -24
  71. package/dist/cardano.d.ts +27602 -8121
  72. package/dist/cardano.js +21 -13
  73. package/dist/client-Bw2foG5E.cjs +808 -0
  74. package/dist/const-BB3MccLt.js +153 -0
  75. package/dist/{const-BErUlcZw.cjs → const-BWBAzOmN.cjs} +1 -1
  76. package/dist/const-CSWmOyzn.cjs +42 -0
  77. package/dist/{const-Dy8Ctnpd.cjs → const-D3mCkwrM.cjs} +5 -5
  78. package/dist/const-DW7z3R5k.js +13 -0
  79. package/dist/{const-DeUA7zYl.js → const-DbuUHSzR.js} +5 -5
  80. package/dist/const-DgBzKfz6.cjs +31995 -0
  81. package/dist/{const-DVW6DGkZ.js → const-xGSNCtwQ.js} +1 -1
  82. package/dist/contract-BsIGhsYa.js +171 -0
  83. package/dist/contract-DD6CGnpB.cjs +189 -0
  84. package/dist/create-add-accounts-2mQA0ST2.js +102 -0
  85. package/dist/create-add-accounts-B6mR2NMn.cjs +114 -0
  86. package/dist/{delegation-tx-builder-CF0VunDW.cjs → delegation-tx-builder-Cp30jtj9.cjs} +9 -8
  87. package/dist/{delegation-tx-builder-C8LQxU2R.js → delegation-tx-builder-DUMikSTg.js} +9 -7
  88. package/dist/{deregistration-tx-builder-DcUXA3Ls.cjs → deregistration-tx-builder-B-ZeY-dR.cjs} +10 -9
  89. package/dist/{deregistration-tx-builder-BOBPP8m_.js → deregistration-tx-builder-CRLrK99U.js} +10 -8
  90. package/dist/device-commitment-hex.vo-BIUYUNz1.cjs +31 -0
  91. package/dist/device-commitment-hex.vo-D9FsYLn7.js +26 -0
  92. package/dist/device-key-BToCoS4D.js +2394 -0
  93. package/dist/device-key-D6euvxFi.cjs +2489 -0
  94. package/dist/device-readers-BHJeNR8I.js +3 -0
  95. package/dist/device-readers-Dw7uTNM5.cjs +167 -0
  96. package/dist/device-readers-b4g2j3QL.js +144 -0
  97. package/dist/device-readers-cxcJcWq5.cjs +7 -0
  98. package/dist/dist-B00GN41m.js +141 -0
  99. package/dist/dist-BA1xnF9O.cjs +4413 -0
  100. package/dist/dist-BmHOXLe8.cjs +3814 -0
  101. package/dist/dist-Br0OVXKt.cjs +535 -0
  102. package/dist/dist-C1JK9C1I.cjs +3452 -0
  103. package/dist/dist-DKEViNEI.cjs +1306 -0
  104. package/dist/dist-Dwqx184a.cjs +43175 -0
  105. package/dist/dist-MJof_Ky1.cjs +145 -0
  106. package/dist/dist-p84sVVJ9.cjs +1394 -0
  107. package/dist/{earn-rewards-tx-builder-BRlTueHS.js → earn-rewards-tx-builder-8G_rouIk.js} +9 -7
  108. package/dist/{earn-rewards-tx-builder-RgO0QUY8.cjs → earn-rewards-tx-builder-cOUNV58E.cjs} +9 -8
  109. package/dist/enc-key-circuits-0Dud78IB.js +13 -0
  110. package/dist/enc-key-circuits-yjPHbuo_.cjs +24 -0
  111. package/dist/english-BZ_ebp2-.cjs +2057 -0
  112. package/dist/envelopes-B3QCzo5p.js +2658 -0
  113. package/dist/envelopes-BdE1xlgz.cjs +2777 -0
  114. package/dist/errors-BA3b8aVu.cjs +584 -0
  115. package/dist/errors-pkQdxW-7.js +392 -0
  116. package/dist/experimental.cjs +6820 -0
  117. package/dist/experimental.d.ts +63552 -0
  118. package/dist/experimental.js +6670 -0
  119. package/dist/fees-6C2uEWFK.js +653 -0
  120. package/dist/fees-DtC71fOx.cjs +820 -0
  121. package/dist/gated-call-BrPjKolT.js +3 -0
  122. package/dist/gated-call-CcsuW9L5.js +691 -0
  123. package/dist/gated-call-DdDrxEq5.cjs +732 -0
  124. package/dist/gated-call-Dk7PazhG.cjs +4 -0
  125. package/dist/grant-rules-B7eDuD5G.cjs +5468 -0
  126. package/dist/grant-rules-qLrKUIR3.js +5217 -0
  127. package/dist/grant-status-CDmZpx32.js +296 -0
  128. package/dist/grant-status-CQtwjLsA.cjs +337 -0
  129. package/dist/grants-Bnb-3Hkv.cjs +16 -0
  130. package/dist/grants-CIb6nOV7.cjs +273 -0
  131. package/dist/grants-CYY0AquW.js +202 -0
  132. package/dist/grants-DOsFSotP.js +4 -0
  133. package/dist/{group-cardano-addresses-by-account-C-1GHUu8.cjs → group-cardano-addresses-by-account-BGOtPV-Y.cjs} +4 -40
  134. package/dist/{group-cardano-addresses-by-account-p4dXmCoj.js → group-cardano-addresses-by-account-TqvESg9x.js} +6 -37
  135. package/dist/{guarded-transaction-signer-DZSGHp1K.js → guarded-transaction-signer-BbVDE9Nn.js} +12 -18
  136. package/dist/{guarded-transaction-signer-BLSezbfV.cjs → guarded-transaction-signer-DTLjAkif.cjs} +26 -26
  137. package/dist/hex-BVtOKJki.cjs +263 -0
  138. package/dist/hex-CGYJPK_Z.js +192 -0
  139. package/dist/http-client-B7_kBTYn.js +155 -0
  140. package/dist/http-client-DYQ_7Rdf.cjs +167 -0
  141. package/dist/identity-DxPhxkZs.js +239 -0
  142. package/dist/identity-y41Yle03.cjs +274 -0
  143. package/dist/in-memory-wallet-integration-5BKiAvPT.js +20861 -0
  144. package/dist/{in-memory-wallet-integration-CUTa68ET.js → in-memory-wallet-integration-BCOqDgbt.js} +10 -107
  145. package/dist/in-memory-wallet-integration-Bi8I9Dso.js +128 -0
  146. package/dist/in-memory-wallet-integration-Bsl2ONua.cjs +239 -0
  147. package/dist/in-memory-wallet-integration-DZ0CAyvY.cjs +20865 -0
  148. package/dist/{in-memory-wallet-integration-Dw_S7amX.cjs → in-memory-wallet-integration-DsFm8gLk.cjs} +21 -112
  149. package/dist/{in-memory-wallet-integration-Z4Bp_DTv.cjs → in-memory-wallet-integration-aW_SF-go.cjs} +8 -5
  150. package/dist/{in-memory-wallet-integration-B6cbAlvD.js → in-memory-wallet-integration-mIEc8EfR.js} +9 -5
  151. package/dist/index.cjs +95 -58
  152. package/dist/index.d.ts +45152 -13448
  153. package/dist/index.js +55 -28
  154. package/dist/{init-CVi8J0zk.cjs → init-5SfWgCYD.cjs} +3 -3
  155. package/dist/{init-Ci7aqWCf2.cjs → init-8NVWbo302.cjs} +2 -2
  156. package/dist/init-B-C7n6u6.js +5 -0
  157. package/dist/{init-BFMWRf-L.js → init-B-ppfGO-.js} +1 -1
  158. package/dist/{init-IiR093Jw.cjs → init-B2OUhY5T.cjs} +2 -2
  159. package/dist/{init-DXnus199.cjs → init-B6Eg_o6G.cjs} +5 -5
  160. package/dist/{init-4hDl5bpr.js → init-BAWMHL1k.js} +2 -2
  161. package/dist/init-BC2CZZOu2.cjs +2002 -0
  162. package/dist/{init-a228hXnS.cjs → init-BF-a2p6E.cjs} +65 -73
  163. package/dist/{init-fFirX8OY.cjs → init-BFxlQAD8.cjs} +1 -1
  164. package/dist/init-BJE1ry0O.js +17 -0
  165. package/dist/{init-BVnI4z2J.js → init-BPKBVOHU.js} +2 -2
  166. package/dist/{init-bdqZBd-o.js → init-BSP_xcpB.js} +2 -2
  167. package/dist/{init-CFNOn3lo.js → init-BSd7mznK.js} +3 -3
  168. package/dist/{init-MjR4swi6.js → init-BUJTT0MN.js} +36 -43
  169. package/dist/init-BUpFzYsD.cjs +143 -0
  170. package/dist/{init-BvpYHyLH.cjs → init-BZ-WyAIL.cjs} +3 -3
  171. package/dist/init-BZdfdLX7.js +422 -0
  172. package/dist/{init-CJeOys1w.cjs → init-B_HWNXMD.cjs} +2 -2
  173. package/dist/init-BbIMt6xU.cjs +6 -0
  174. package/dist/{init-BYdN1aqE.cjs → init-BewG6swt.cjs} +2 -2
  175. package/dist/{init-BX0AZAVH2.js → init-Bilqsbng.js} +2 -2
  176. package/dist/{init-Tpn9VkFu.cjs → init-BpwlQ6wF.cjs} +1 -1
  177. package/dist/{init-D5-P0HXK.js → init-BtQOzUjX.js} +1 -1
  178. package/dist/{init-BvGz3qUd2.js → init-CCTscPaY2.js} +2 -2
  179. package/dist/{init-D4hPipCD.cjs → init-CH0yyBof.cjs} +1 -1
  180. package/dist/init-CaIkrXal.cjs +121 -0
  181. package/dist/init-CkZy4IMf.cjs +107 -0
  182. package/dist/{init-Drr2f_Pr.js → init-CmfcY7SV.js} +1 -1
  183. package/dist/{init-Kf3_OLv6.js → init-CnJAlks_.js} +1 -1
  184. package/dist/{init-Cdjt_z-b.cjs → init-CqqmtAIK.cjs} +4 -4
  185. package/dist/{init-Bb0avZeZ.js → init-Cu2znmIc.js} +5 -5
  186. package/dist/init-Cz5LAizN.js +2001 -0
  187. package/dist/init-D4WM3-Mn.cjs +18 -0
  188. package/dist/init-DMi2FGpf.cjs +424 -0
  189. package/dist/{init-lcaUSOOG.js → init-DPTJohPd.js} +1 -1
  190. package/dist/{init-BTYLMF9F2.cjs → init-DexLSprL2.cjs} +258 -61
  191. package/dist/init-DfKPgD21.js +106 -0
  192. package/dist/init-DgzRsI-C.js +141 -0
  193. package/dist/init-Dj4Cn7-P.js +4789 -0
  194. package/dist/init-DktAnYI0.cjs +2354 -0
  195. package/dist/{init-DFwhgert.cjs → init-DmyuM9Lo.cjs} +1 -1
  196. package/dist/{init-CX_zLXc6.js → init-IBnizp1U.js} +5 -5
  197. package/dist/init-MDKB6tGI2.cjs +4790 -0
  198. package/dist/{init-DbZ5byj42.js → init-TbB38KNt2.js} +258 -60
  199. package/dist/init-X6m_cqDy.js +2353 -0
  200. package/dist/{init-DvVgKCaP2.cjs → init-bzvl1JhF.cjs} +2 -2
  201. package/dist/{init-BgrY29gW.js → init-dsYkC_0B.js} +4 -4
  202. package/dist/{init-B5BsI3DR.cjs → init-kvn5FlPS.cjs} +7 -7
  203. package/dist/init-vfy9DW_E.js +119 -0
  204. package/dist/{init-BE_6Jyc4.js → init-w7xl6LsP.js} +3 -3
  205. package/dist/{init-DEsWVyn5.cjs → init-wgiBUmyW.cjs} +1 -1
  206. package/dist/{is-retriable-error-DWv_ob-f.js → is-retriable-error-CmFR20pe.js} +1 -1
  207. package/dist/{is-retriable-error-Dtb4JGjd.cjs → is-retriable-error-D2yo0_lS.cjs} +1 -1
  208. package/dist/jubjub-authoriser-A2c8vmkN.js +84 -0
  209. package/dist/jubjub-authoriser-D3ZDgzcF.cjs +85 -0
  210. package/dist/key-source-CcLTx8HK.js +2761 -0
  211. package/dist/key-source-D2tTAag-.cjs +806 -0
  212. package/dist/{key-value-store-DTX1quRS.js → key-value-store-DZcNDB3n.js} +1 -1
  213. package/dist/{key-value-store-B-XRIwaM.cjs → key-value-store-Htj4RgMe.cjs} +1 -1
  214. package/dist/keyDerivation-DcKRUCVh.cjs +274 -0
  215. package/dist/keyDerivation-QGesIMbS.js +4077 -0
  216. package/dist/{lace-context-FpU3iG3f.cjs → lace-context-BN0b_ury.cjs} +4 -4
  217. package/dist/{lace-context-BQvvxVLm.js → lace-context-DMRJ87Vg.js} +4 -4
  218. package/dist/lib-BC-62Byh.cjs +2359 -0
  219. package/dist/lib-JYqcAX0c.js +2354 -0
  220. package/dist/midnight-account-id.vo--Vcx34Xk.cjs +10 -0
  221. package/dist/midnight-account-id.vo-BuB8fiVO.js +5 -0
  222. package/dist/midnight-engine-B7D2hbHo.js +5 -0
  223. package/dist/midnight-engine-D5ikN_5F.cjs +6 -0
  224. package/dist/midnight-wallet-BOdFD6SX.cjs +24 -0
  225. package/dist/midnight-wallet-CAqZrNVe.js +18 -0
  226. package/dist/midnight.cjs +1412 -0
  227. package/dist/midnight.d.ts +28609 -0
  228. package/dist/midnight.js +1355 -0
  229. package/dist/modules-cardano-BiL9hoM3.js +996 -0
  230. package/dist/modules-cardano-UeXks3sd.cjs +1109 -0
  231. package/dist/node-relay-BOuvdZvC.cjs +49 -0
  232. package/dist/node-relay-CLtc5n1v.js +48 -0
  233. package/dist/outputs-5qdW8s-f.js +196 -0
  234. package/dist/outputs-BEM1GPMf.cjs +339 -0
  235. package/dist/{SodiumBip32Ed25519-BQUcrQWX.cjs → pbkdf2-B1veireP.cjs} +6 -411
  236. package/dist/pending-operation-CfTUNQVD.cjs +26 -0
  237. package/dist/pending-operation-Ljijb1Uh.js +21 -0
  238. package/dist/providers-1on_fwJO.cjs +364 -0
  239. package/dist/providers-C1MsxfMA.js +341 -0
  240. package/dist/redacted-4SJPqt-G.js +81 -0
  241. package/dist/redacted-DwxIDdIF.cjs +86 -0
  242. package/dist/{reselect-DPML1uFN.cjs → reselect-kP4DaAII.cjs} +1 -1
  243. package/dist/{retry-config-oNtfdyrG.cjs → retry-config-BGv5q5QM.cjs} +1 -1
  244. package/dist/{retry-config-VdbuqBsI.js → retry-config-Bu5Yn5ua.js} +1 -1
  245. package/dist/safe-buffer-CqGpSFCQ.js +43 -0
  246. package/dist/safe-buffer-DbXbwhUQ.cjs +48 -0
  247. package/dist/sdk-1SRu8Ype.cjs +1445 -0
  248. package/dist/sdk-BneJm7ey.js +1249 -0
  249. package/dist/sdk-init-9TG1ZvHF.cjs +3795 -0
  250. package/dist/sdk-init-Ce3mD-8F.js +1047 -0
  251. package/dist/secret-box-CSmRp6Tt.js +3777 -0
  252. package/dist/secret-box-ZfbYwBgS.cjs +6970 -0
  253. package/dist/send-proof-progress-DtAcrMP6.cjs +33 -0
  254. package/dist/send-proof-progress-Dx2cUz1B.js +22 -0
  255. package/dist/sha2-DaRJZOjk.cjs +390 -0
  256. package/dist/sha2-c4sB6peD.js +378 -0
  257. package/dist/side-effects-C3BhLrdP.js +768 -0
  258. package/dist/side-effects-DG0R-KFc.cjs +804 -0
  259. package/dist/sign-bitcoin-transaction-with-seed-C7vq9om8.js +147 -0
  260. package/dist/sign-bitcoin-transaction-with-seed-Ds1dwzan.cjs +158 -0
  261. package/dist/signer--2I6poMD.js +2 -0
  262. package/dist/signer-CX21Ups_.cjs +121 -0
  263. package/dist/signer-Cnn9q9oY.js +92 -0
  264. package/dist/signer-DbSxjk0F.cjs +7 -0
  265. package/dist/signer-factory-B5oFuUZ-.cjs +130 -0
  266. package/dist/signer-factory-BAt8FPNc.cjs +281 -0
  267. package/dist/signer-factory-BGE4nL4k.js +129 -0
  268. package/dist/signer-factory-C4pgsiJi.js +281 -0
  269. package/dist/{signer-factory-DySc1wRX.cjs → signer-factory-D95jQIPV.cjs} +9 -6
  270. package/dist/{signer-factory-D4HrwFPl.js → signer-factory-PrpsGX-8.js} +10 -6
  271. package/dist/{slice-BZTQgYSs.js → slice-B-xJcuT1.js} +4 -4
  272. package/dist/slice-B4QOYaOG.cjs +105 -0
  273. package/dist/{slice-CtIMw2YT.cjs → slice-B9QKuhi0.cjs} +3 -3
  274. package/dist/slice-BKnGkAkd.cjs +9031 -0
  275. package/dist/{slice-Bhwmqn1p.cjs → slice-Bf4enJKZ.cjs} +8 -8
  276. package/dist/{slice-DaNVz7_l.js → slice-ByNWnxKS.js} +8 -8
  277. package/dist/{slice-C2UDQrdt.js → slice-C1zOeseG.js} +449 -355
  278. package/dist/{slice-DFLyKRm5.cjs → slice-C4toRM6A.cjs} +2 -2
  279. package/dist/{slice-Cx4dULDA.cjs → slice-C8YVv4Qn.cjs} +1 -1
  280. package/dist/{slice-jnpOgiSZ.cjs → slice-CaMfviYu.cjs} +3 -3
  281. package/dist/{slice-DFvrwUur.js → slice-CgeRsYRg.js} +6 -6
  282. package/dist/slice-Cwt8jShq.js +9013 -0
  283. package/dist/{slice-zNEspZND.js → slice-D0VzpMbY.js} +2 -2
  284. package/dist/slice-D14F78Ca.js +87 -0
  285. package/dist/{slice-F6A1YKOF.cjs → slice-DLflttAu.cjs} +479 -366
  286. package/dist/{slice-C96EdeY1.js → slice-DcO-iNz2.js} +1 -1
  287. package/dist/{slice-C19quNWM.cjs → slice-DimtxfAT.cjs} +2 -2
  288. package/dist/{slice-KiNoGwD9.js → slice-DlTEqIoI.js} +1 -1
  289. package/dist/{slice-ws8PccRX.js → slice-Dn0WojD7.js} +3 -3
  290. package/dist/{slice-BLSxLmie.cjs → slice-Dongunf7.cjs} +4 -4
  291. package/dist/{slice-B6vPum19.js → slice-DpufJAjJ.js} +2 -2
  292. package/dist/{slice-exhhmucR.cjs → slice-DtitVc9L.cjs} +7 -7
  293. package/dist/{slice-Bjj2t2ut.cjs → slice-WMMMc1Xv.cjs} +2 -2
  294. package/dist/slice-a8VuIqzS.cjs +378 -0
  295. package/dist/slice-bRASyM-m.js +354 -0
  296. package/dist/{slice-CBFrgHSH.js → slice-hNyKbDry.js} +3 -3
  297. package/dist/{InMemoryKeyAgent-pyB2gA-N.cjs → src-BayYMIjw.cjs} +20 -8228
  298. package/dist/{InMemoryKeyAgent-D-3HejxN.js → src-CgINPlSx.js} +29 -4811
  299. package/dist/store-AUlTfcI_.js +37 -0
  300. package/dist/store-C7AneHEY.cjs +66 -0
  301. package/dist/submit-BuBCVi-l.cjs +87 -0
  302. package/dist/submit-D37lxFrK.js +70 -0
  303. package/dist/submit-bitcoin-tx-DFLnhidO.js +983 -0
  304. package/dist/submit-bitcoin-tx-sih2hSfb.cjs +1049 -0
  305. package/dist/{subtractValueQuantities-DfZg6oiG.js → subtractValueQuantities-BuDJVxq6.js} +3 -22
  306. package/dist/{subtractValueQuantities-C8qDTtgH.cjs → subtractValueQuantities-CqR606HV.cjs} +2 -32
  307. package/dist/{sync-operation-utils-BrQyWFS_.cjs → sync-operation-utils--Z_IQfyW.cjs} +0 -66
  308. package/dist/{sync-operation-utils-CAaCGvEN.js → sync-operation-utils-BimBpvI3.js} +1 -37
  309. package/dist/token-color.vo-CItgoOjw.js +26 -0
  310. package/dist/token-color.vo-Ca4hUXfh.cjs +37 -0
  311. package/dist/token-id-mapper-BVau2RXa.js +16 -0
  312. package/dist/token-id-mapper-CSeLzM4o.cjs +18 -0
  313. package/dist/{token-id-mapper-pDaTxpw2.js → token-id-mapper-ClBeIdJR.js} +2 -2
  314. package/dist/{token-id-mapper-DYcd8hhQ.cjs → token-id-mapper-D0wFdbOy.cjs} +2 -2
  315. package/dist/types-BhzypKMS.cjs +15 -0
  316. package/dist/types-DTE1ctDB.js +10 -0
  317. package/dist/types-DXSxXGz0.js +12 -0
  318. package/dist/types-Dls6Z5gV.cjs +23 -0
  319. package/dist/{util-P4PX2muj.cjs → util-D1YlKzrc.cjs} +2 -2
  320. package/dist/{util-CQSJU0wT.js → util-DHxtzUzY.js} +2 -2
  321. package/dist/{utils-Bv4OHT4S.cjs → utils-DYf4iZKX.cjs} +7 -1
  322. package/dist/{utils-DrtzChYN.js → utils-DcnoETEP.js} +2 -2
  323. package/dist/utils-Du0YXRcU.cjs +112 -0
  324. package/dist/utils-fF_rnZe3.js +65 -0
  325. package/dist/v8Signatures-ClGpA8mS.cjs +71 -0
  326. package/dist/{vote-delegation-tx-builder-69gOEkCB.cjs → vote-delegation-tx-builder-BjZxCOd3.cjs} +9 -8
  327. package/dist/{vote-delegation-tx-builder-BRJ_pf-c.js → vote-delegation-tx-builder-DRJwPXkD.js} +9 -7
  328. package/dist/watch-FGE4B_Ge.js +13056 -0
  329. package/dist/watch-imlsRZDp.cjs +52207 -0
  330. package/package.json +95 -3
  331. package/dist/Address-4KjeDt_x.cjs +0 -17269
  332. package/dist/Address-_VPkf7Xh.js +0 -17101
  333. package/dist/account-guards.stable-BKyYPj4z.cjs +0 -10
  334. package/dist/account-guards.stable-BdmQ43vC.js +0 -5
  335. package/dist/init-BR9ZIbED.cjs +0 -378
  336. package/dist/init-B_k1RUN_.cjs +0 -21
  337. package/dist/init-Bs3b5ZQH.js +0 -378
  338. package/dist/init-ChfcrHgG2.cjs +0 -4354
  339. package/dist/init-D1uUMMpG.js +0 -19
  340. package/dist/init-DV1OSZiK.js +0 -4352
  341. package/dist/modules-cardano-BINzpXpk.js +0 -5454
  342. package/dist/modules-cardano-Dt8OwoQb.cjs +0 -5640
  343. package/dist/slice-Caf98m0d2.js +0 -107
  344. package/dist/slice-PM6llajQ2.cjs +0 -125
  345. /package/dist/{BigIntMath-CoJWXt1h.cjs → BigIntMath-CBciEQn2.cjs} +0 -0
  346. /package/dist/{BigIntMath-BWUJOsOW.js → BigIntMath-X3EniYDo.js} +0 -0
  347. /package/dist/{ChainId-sdLmlIRk.cjs → ChainId-CNPSU9nn.cjs} +0 -0
  348. /package/dist/{ChainId-CKbm87yQ.js → ChainId-CeoG8Sov.js} +0 -0
  349. /package/dist/{_polyfill-node.process-4fnlkbBi.cjs → _polyfill-node.process-5UxyjoHG.cjs} +0 -0
  350. /package/dist/{account-id.vo-Xpgnu-av.cjs → account-id.vo-DYZBZQsz.cjs} +0 -0
  351. /package/dist/{account-id.vo-DP0BtOoF.js → account-id.vo-KDTWJ-W5.js} +0 -0
  352. /package/dist/{actions-BNagEO6m.cjs → actions-BnHFVSr_.cjs} +0 -0
  353. /package/dist/{actions-BWTyAr7B.js → actions-Cwj0ApZf.js} +0 -0
  354. /package/dist/{asyncoption-po7-RiA5.cjs → asyncoption-CYcZNBRM.cjs} +0 -0
  355. /package/dist/{asyncoption-CzIVH5Y1.js → asyncoption-DZQGfznq.js} +0 -0
  356. /package/dist/{bignum.vo-DhhZ9HP1.js → bignum.vo-Co6tMH2q.js} +0 -0
  357. /package/dist/{bignum.vo-CpkrIXjH.cjs → bignum.vo-Du-bpvar.cjs} +0 -0
  358. /package/dist/{blockchain-network-id.vo-CbgrAY_K.js → blockchain-network-id.vo-C6Jowh8t.js} +0 -0
  359. /package/dist/{blockchain-network-id.vo-5iC6Kffo.cjs → blockchain-network-id.vo-DEQ14dYj.cjs} +0 -0
  360. /package/dist/{composite-signer-factory-DABpCe1_.js → composite-signer-factory-D9RWY-1V.js} +0 -0
  361. /package/dist/{composite-signer-factory-BizuNPP7.cjs → composite-signer-factory-v4Pxn8bA.cjs} +0 -0
  362. /package/dist/{error-ggOihIfv.cjs → error-B6wj9nPv.cjs} +0 -0
  363. /package/dist/{error-DHKO2iS4.js → error-CvEYL98P.js} +0 -0
  364. /package/dist/{esm-browser-DnXNvpcn.cjs → esm-browser-CZbMeVod.cjs} +0 -0
  365. /package/dist/{esm-browser-BBVQuyQv.js → esm-browser-DpAD_RKv.js} +0 -0
  366. /package/dist/{feature-flag-key.vo-DoGK5Bfh.js → feature-flag-key.vo-9reURsOQ.js} +0 -0
  367. /package/dist/{feature-flag-key.vo-CP1Z9s8e.cjs → feature-flag-key.vo-KTIdnzYX.cjs} +0 -0
  368. /package/dist/{first-state-of-status-Bx4-QQ5m.cjs → first-state-of-status-9ICeDNSa.cjs} +0 -0
  369. /package/dist/{first-state-of-status-D7XzEuSa.js → first-state-of-status-BscbKj_t.js} +0 -0
  370. /package/dist/{init-4319psUJ.cjs → init-BT67pd9x.cjs} +0 -0
  371. /package/dist/{init-AFUM1TSg.js → init-CHk1fvJ2.js} +0 -0
  372. /package/dist/{init-ZX90nEJe2.js → init-DIyNx6DZ.js} +0 -0
  373. /package/dist/{init-CTQ2g6Xt.js → init-DtxxFaCx.js} +0 -0
  374. /package/dist/{init-Dt1Xifg_2.cjs → init-J8QobR93.cjs} +0 -0
  375. /package/dist/{init-BNewLCqw.cjs → init-xzZPlQwB.cjs} +0 -0
  376. /package/dist/{is-redux-persist-state-DxJSsvxB.cjs → is-redux-persist-state-C5luvPEH.cjs} +0 -0
  377. /package/dist/{is-redux-persist-state-Bl2n4VMr.js → is-redux-persist-state-CjcqyCoh.js} +0 -0
  378. /package/dist/{isNotNil-CZxBMfL5.js → isNotNil-Be7gCY0m.js} +0 -0
  379. /package/dist/{isNotNil-C_SO1tcC.cjs → isNotNil-CrYIUFry.cjs} +0 -0
  380. /package/dist/{logging-BvMDKgl0.js → logging-BAMMfn5j.js} +0 -0
  381. /package/dist/{logging-Bh9ikph3.cjs → logging-CbxgIX9V.cjs} +0 -0
  382. /package/dist/{metadatum-B-sxBnSY.cjs → metadatum-BGvFqGPt.cjs} +0 -0
  383. /package/dist/{metadatum-Ego4jVas.js → metadatum-BVocMYy_.js} +0 -0
  384. /package/dist/{repo-slice-BzZQUTj8.cjs → repo-slice-BB_W-6OL.cjs} +0 -0
  385. /package/dist/{repo-slice-vCcQJotS.js → repo-slice-BQlssw9M.js} +0 -0
  386. /package/dist/{reselect-BPkMzw3k.js → reselect-JIgawvPe.js} +0 -0
  387. /package/dist/{serializable-BxFUShKY.js → serializable-F7OtYl8d.js} +0 -0
  388. /package/dist/{serializable-DWgkHhv_.cjs → serializable-n3UoxbZc.cjs} +0 -0
  389. /package/dist/{slice-DY-DNtSi.js → slice-5dGBAz6w.js} +0 -0
  390. /package/dist/{slice-BEFoeqgr.cjs → slice-BzJgg9FS.cjs} +0 -0
  391. /package/dist/{src-CPmX4KUh.cjs → src-CeBNkonz.cjs} +0 -0
  392. /package/dist/{src-XOZXPKxe.js → src-Cv1ifqbe.js} +0 -0
  393. /package/dist/{time-CQPt4FdU.js → time-ByoKevdZ.js} +0 -0
  394. /package/dist/{time-Blvoyh9G.cjs → time-qD7edGU0.cjs} +0 -0
  395. /package/dist/{time.vo-Xln4yGBU.js → time.vo-C17-tYq3.js} +0 -0
  396. /package/dist/{time.vo-rwWwJzmP.cjs → time.vo-TmYE-_Y2.cjs} +0 -0
  397. /package/dist/{toEmpty-CzmoZVLN.cjs → toEmpty-Db6V94Mf.cjs} +0 -0
  398. /package/dist/{toEmpty-DzLd-ZCi.js → toEmpty-Djj-QbpI.js} +0 -0
  399. /package/dist/{token-id.vo-l8fuSpem.cjs → token-id.vo-CZ622zcZ.cjs} +0 -0
  400. /package/dist/{token-id.vo-DMKSaMlc.js → token-id.vo-IDI9C_Qm.js} +0 -0
  401. /package/dist/{types-B61DQoFW.js → types-By-XeXWg.js} +0 -0
  402. /package/dist/{types-CUC-Kcqh.cjs → types-Cn6WlQV6.cjs} +0 -0
  403. /package/dist/{util-BV3iGOrz.cjs → util-Cp1h58uD.cjs} +0 -0
  404. /package/dist/{util-BsQKLorV.js → util-DHppXj7U.js} +0 -0
  405. /package/dist/{util-selector-CKIyoAOz.js → util-selector-BWmHpo92.js} +0 -0
  406. /package/dist/{util-selector-C9H5RsNw.cjs → util-selector-glXkO-jR.cjs} +0 -0
  407. /package/dist/{while-active-CNXeLxre.js → while-active-CDBnUlQA.js} +0 -0
  408. /package/dist/{while-active-BsDWSNrO.cjs → while-active-I2TlHtm7.cjs} +0 -0
@@ -0,0 +1,2353 @@
1
+ import "./_polyfill-buffer-patch-JcrfT1gU.js";
2
+ import { n as HexBytes, t as ByteArray } from "./bytes.vo-CziaOsba.js";
3
+ import { B as GrantScopeSalt, V as GrantId, i as granteePoint, r as circuitScope, z as GrantSlot } from "./grant-rules-qLrKUIR3.js";
4
+ import { i as AccAddress, t as createMidnightProviders } from "./providers-C1MsxfMA.js";
5
+ import { a as resumeAccountDeployment, i as deployAccount, o as DeviceEpoch, r as resolveUseCounter, s as UseCounter } from "./gated-call-CcsuW9L5.js";
6
+ import { i as accManifest } from "./artefact-loader-D0T2f6F7.js";
7
+ import { t as TokenColor } from "./token-color.vo-CItgoOjw.js";
8
+ import { A as RecordUnreadableError, E as NotAuthorisedError, O as ReadUnavailableError, S as GrantRosterUnsettledError, T as NoAccountError, _ as GrantNotFoundError, b as GrantRevocationPendingError, h as GrantIssueRejectedError, i as AccountNotFoundError, j as RemovalTargetNotFoundError, k as RecordCorruptedError, m as EncryptionKeyMismatchError, n as AccountCreationPendingError, p as DeviceRosterInvalidError, r as AccountExistsError, t as AccountContractMissingError, u as CreationLockMismatchError, w as LastDeviceError, x as GrantRevokedError, y as GrantOriginMissingError } from "./errors-pkQdxW-7.js";
9
+ import { a as isDeviceRegistration, b as validateGrantScope, i as isAddressedTo, n as deviceEnvelopes, o as isLaceRecord, r as encodeDevicePublicKey } from "./envelopes-B3QCzo5p.js";
10
+ import { n as ROTATE_ENC_KEY_CIRCUIT, t as APPEND_INBOX_CIRCUIT } from "./enc-key-circuits-0Dud78IB.js";
11
+ import { a as prepareIssueGrant, i as originHash, n as grantObjectCommit, o as prepareRevokeAllGrants, s as prepareRevokeGrant, t as deriveGrantId } from "./grants-CYY0AquW.js";
12
+ import { i as ledger } from "./acc-module-ur8CkBd1.js";
13
+ import { i as resolveLiveReaders, t as assertJoiningDevice } from "./device-readers-b4g2j3QL.js";
14
+ import { a as submitAccountCall, i as readAccountLedger, n as isNotAuthorisedAbort, o as withRefreshedLocalDevice, r as queryAccountLedger, s as createIndexerClient } from "./account-call-CoCq3V55.js";
15
+ import { EMPTY, Observable, catchError, concat, concatMap, defaultIfEmpty, defer, exhaustMap, filter, firstValueFrom, map, merge, mergeMap, of, withLatestFrom } from "rxjs";
16
+ //#region ../../packages/module/passport-account/src/acc/device-circuits.ts
17
+ /**
18
+ * The gated circuits the device flows authorise, named once so the flow
19
+ * that submits a call and the signer that scopes a key session to it
20
+ * agree on the circuit.
21
+ */
22
+ const ADD_DEVICE_CIRCUIT = "add_device_with_jubjub";
23
+ const REMOVE_DEVICE_CIRCUIT = "remove_device_with_jubjub";
24
+ //#endregion
25
+ //#region ../../packages/module/passport-account/src/acc/pending-deployment.ts
26
+ /** Whether a record's pending creation carries the whole deployment, not only its marker. */
27
+ const isPendingDeployment = (pending) => "authorityKey" in pending;
28
+ /**
29
+ * The refusal of a creation that resumes an unfinished one under another
30
+ * lock choice than `recorded`, the one it was started with, which the
31
+ * message names so a retry can repeat it.
32
+ */
33
+ const lockChoiceMismatch = (recorded) => new CreationLockMismatchError(`The unfinished Passport account creation was started with lockAccount ${recorded}; resume it with the same lock choice.`);
34
+ //#endregion
35
+ //#region ../../packages/module/passport-account/src/store/envelope.ts
36
+ /** AES-GCM is specified for 96-bit IVs; each seal draws a fresh one. */
37
+ const IV_LENGTH = 12;
38
+ const utf8 = (value) => new TextEncoder().encode(value);
39
+ /** Whether a stored value has the sealed envelope layout. */
40
+ const isSealedRecord = (value) => {
41
+ if (typeof value !== "object" || value === null) return false;
42
+ const candidate = value;
43
+ return candidate.v === 1 && typeof candidate.iv === "string" && typeof candidate.ciphertext === "string";
44
+ };
45
+ /**
46
+ * Encrypts a record into a {@link SealedRecord} under the given key,
47
+ * marking the envelope pending when asked.
48
+ */
49
+ const sealRecord = async (key, record, { pending = false } = {}) => {
50
+ const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
51
+ const ciphertext = await crypto.subtle.encrypt({
52
+ name: "AES-GCM",
53
+ iv
54
+ }, key, utf8(JSON.stringify(record)));
55
+ return {
56
+ v: 1,
57
+ iv: HexBytes.fromByteArray(iv),
58
+ ciphertext: HexBytes.fromByteArray(new Uint8Array(ciphertext)),
59
+ ...pending ? { pending: true } : {}
60
+ };
61
+ };
62
+ /**
63
+ * Decrypts a {@link SealedRecord} back into the record it sealed. Any
64
+ * failure to open - a tampered ciphertext, a mismatched key, or a
65
+ * malformed envelope - throws RecordCorruptedError.
66
+ */
67
+ const openRecord = async (key, sealed) => {
68
+ try {
69
+ const plaintext = await crypto.subtle.decrypt({
70
+ name: "AES-GCM",
71
+ iv: ByteArray.fromHex(HexBytes(sealed.iv))
72
+ }, key, ByteArray.fromHex(HexBytes(sealed.ciphertext)));
73
+ return JSON.parse(new TextDecoder().decode(plaintext));
74
+ } catch {
75
+ throw new RecordCorruptedError();
76
+ }
77
+ };
78
+ //#endregion
79
+ //#region ../../packages/module/passport-account/src/store/account-record.ts
80
+ const COLLECTION_ID = "passport-account";
81
+ const ACCOUNT_RECORD_KEY = "account";
82
+ /**
83
+ * An {@link AccountRecordStore} over the platform key-value storage,
84
+ * namespaced under its own collection. With a `keyProvider` the record is
85
+ * sealed at rest and only sealed envelopes are readable. What a sealed
86
+ * record reads as without a `keyProvider` is the caller's choice:
87
+ * 'skip' (the default) reads it as undefined, for the ceremony-free
88
+ * restore peek that cannot open it by design; 'error' raises
89
+ * RecordUnreadableError, for the flows where a missing storage key is a
90
+ * persistence misconfiguration that must not look like an absent
91
+ * account. Without a `keyProvider` (the dev authoriser) the record
92
+ * persists in plaintext, which holds no secret once the account is
93
+ * complete; while a creation is unfinished it holds the contract's
94
+ * maintenance authority key (see {@link PassportAccountRecord}), so the
95
+ * dev authoriser is for development and tests only.
96
+ */
97
+ const createAccountRecordStore = (createKeyValueStorage, keyProvider, { onSealedWithoutKey = "skip" } = {}) => {
98
+ const storage = createKeyValueStorage({ collectionId: COLLECTION_ID });
99
+ const open = async (keyOf, value) => isSealedRecord(value) ? openRecord(await keyOf(), value) : void 0;
100
+ const stored = () => storage.getValues([ACCOUNT_RECORD_KEY]).pipe(map(([value]) => value), defaultIfEmpty(void 0));
101
+ const isPending = (record) => record.deployment !== void 0;
102
+ const presenceOf = (value) => {
103
+ if (value === void 0) return "absent";
104
+ return (isSealedRecord(value) ? value.pending === true : isPending(value)) ? "pending" : "complete";
105
+ };
106
+ return {
107
+ read: () => keyProvider ? stored().pipe(concatMap(async (value) => open(keyProvider, value))) : stored().pipe(map((value) => {
108
+ if (!isSealedRecord(value)) return value;
109
+ if (onSealedWithoutKey === "error") throw new RecordUnreadableError();
110
+ })),
111
+ write: (record) => keyProvider ? defer(async () => sealRecord(await keyProvider(), record, { pending: isPending(record) })).pipe(mergeMap((sealed) => storage.setValue(ACCOUNT_RECORD_KEY, sealed))) : storage.setValue(ACCOUNT_RECORD_KEY, record),
112
+ presence: () => stored().pipe(map(presenceOf))
113
+ };
114
+ };
115
+ /**
116
+ * The promise view of an {@link AccountRecordStore}, for the flows. A
117
+ * storage that completes without emitting (destroyed mid-flow) reads as
118
+ * absent and writes as done, mirroring the stream contract so a flow
119
+ * never hangs on it.
120
+ */
121
+ const toAccountRecords = (store) => ({
122
+ read: async () => firstValueFrom(store.read(), { defaultValue: void 0 }),
123
+ write: async (record) => firstValueFrom(store.write(record), { defaultValue: void 0 }),
124
+ presence: async () => firstValueFrom(store.presence(), { defaultValue: "absent" })
125
+ });
126
+ //#endregion
127
+ //#region ../../packages/module/passport-account/src/acc/inbox-walk.ts
128
+ const defaultLoadLedger = async () => import("@midnightntwrk/ledger-v9");
129
+ /** The entries the contract holds, in inbox order, the Lace records included. */
130
+ const inboxEntries = (ledgerState) => {
131
+ const entries = [];
132
+ for (let ordinal = 0n; ordinal < ledgerState.inbox_count; ordinal += 1n) if (ledgerState.inbox.member(ordinal)) entries.push(ledgerState.inbox.lookup(ordinal));
133
+ return entries;
134
+ };
135
+ /**
136
+ * The commitment the chain carries for a coin the account received,
137
+ * recomputed with the ledger's own output construction: a contract-owned
138
+ * output of this coin towards the account address, which is exactly what
139
+ * a depositor builds and the contract claims. A description the ledger
140
+ * refuses to commit to is no candidate.
141
+ */
142
+ const toCandidate = ({ ZswapOutput }, address, { coin, entry }) => {
143
+ try {
144
+ return {
145
+ coin,
146
+ commitment: ZswapOutput.newContractOwned({
147
+ type: HexBytes.fromByteArray(coin.color),
148
+ nonce: HexBytes.fromByteArray(coin.nonce),
149
+ value: coin.value
150
+ }, void 0, address).commitment.toLowerCase(),
151
+ entry
152
+ };
153
+ } catch {
154
+ return null;
155
+ }
156
+ };
157
+ /**
158
+ * The tree index of every shielded output the chain created for the
159
+ * account, by commitment, read from the ledger events of the transactions
160
+ * that acted on the contract. A deposit is one such action, so its
161
+ * transaction is among them.
162
+ */
163
+ const receivedOutputs = async (address, indexer) => {
164
+ const actions = await indexer.enumerateContractActions(address);
165
+ const hashes = [...new Set(actions.map((action) => action.txHash))];
166
+ const outputs = await Promise.all(hashes.map(async (hash) => indexer.queryShieldedOutputs(hash)));
167
+ const recipient = address.toLowerCase();
168
+ return new Map(outputs.flat().filter((output) => output.contractAddress === recipient).map((output) => [output.commitment, output.index]));
169
+ };
170
+ /**
171
+ * Walks the account's inbox the way the custody standard prescribes:
172
+ * every entry other than the Lace records, which hold the account keys
173
+ * and the grant bookkeeping rather than coins, is opened with the
174
+ * encryption secrets, each coin that opens is verified by recomputing
175
+ * its commitment and finding it among the outputs the chain created for
176
+ * the account, and the index that output took in the commitment tree is
177
+ * captured with it. Anyone can append any
178
+ * bytes to the inbox, so an entry that does not open, or opens to a coin
179
+ * the chain never created for this account, is skipped as noise and
180
+ * counted, never raised. Two entries describing the same coin, as a
181
+ * re-encryption after a key rotation leaves behind, yield one holding,
182
+ * read from the first entry that described it. An empty inbox, or one
183
+ * with nothing that opens, is answered without loading the ledger or
184
+ * reading the chain's history. A ledger binding that fails to load is a
185
+ * failure of the walk itself, not of any entry, so it is raised rather
186
+ * than reported as an inbox of noise.
187
+ */
188
+ const walkInbox = async ({ address, ledgerState, indexer, openEntries, loadLedger = defaultLoadLedger }) => {
189
+ const entries = inboxEntries(ledgerState).filter((entry) => !isLaceRecord(entry));
190
+ const nothing = (skippedEntries) => ({
191
+ holdings: [],
192
+ holdingEntries: [],
193
+ skippedEntries
194
+ });
195
+ if (entries.length === 0) return nothing(0);
196
+ const coins = await openEntries(entries);
197
+ if (coins.every((coin) => coin === null)) return nothing(entries.length);
198
+ const ledger = await loadLedger();
199
+ const candidates = coins.map((coin, ordinal) => coin && toCandidate(ledger, address, {
200
+ coin,
201
+ entry: entries[ordinal]
202
+ }));
203
+ if (candidates.every((candidate) => candidate === null)) return nothing(entries.length);
204
+ const outputs = await receivedOutputs(address, indexer);
205
+ const holdings = /* @__PURE__ */ new Map();
206
+ const holdingEntries = /* @__PURE__ */ new Map();
207
+ let skippedEntries = 0;
208
+ for (const candidate of candidates) {
209
+ const index = candidate && outputs.get(candidate.commitment);
210
+ if (!candidate || index === void 0) {
211
+ skippedEntries += 1;
212
+ continue;
213
+ }
214
+ if (holdings.has(candidate.commitment)) continue;
215
+ holdings.set(candidate.commitment, {
216
+ color: TokenColor(HexBytes.fromByteArray(candidate.coin.color)),
217
+ value: candidate.coin.value.toString(),
218
+ commitment: candidate.commitment,
219
+ index: Number(index)
220
+ });
221
+ holdingEntries.set(candidate.commitment, candidate.entry);
222
+ }
223
+ return {
224
+ holdings: [...holdings.values()],
225
+ holdingEntries: [...holdingEntries.values()],
226
+ skippedEntries
227
+ };
228
+ };
229
+ //#endregion
230
+ //#region ../../packages/module/passport-account/src/flows/key-session.ts
231
+ /**
232
+ * Runs an operation under a single key ceremony, so a flow that reaches
233
+ * for the device key several times costs one user prompt instead of one
234
+ * per authoriser call. `flow` names the flow for an authoriser that
235
+ * presents it before the ceremony. An authoriser with no interactive
236
+ * ceremony exposes no session, and the operation runs directly. The
237
+ * session function is used detached, so implementations must not rely on
238
+ * `this`.
239
+ */
240
+ const inKeySession = async (authoriser, flow, operation) => {
241
+ const { withKeySession } = authoriser;
242
+ return withKeySession ? withKeySession(operation, flow) : operation();
243
+ };
244
+ //#endregion
245
+ //#region ../../packages/module/passport-account/src/acc/enc-key-rotation.ts
246
+ /** Prepares `rotate_enc_key_with_jubjub` over the raw 32-byte X25519 key `enc_key` is replaced with. */
247
+ const prepareRotateEncKey = (newKey) => ({
248
+ circuit: ROTATE_ENC_KEY_CIRCUIT,
249
+ args: [newKey]
250
+ });
251
+ /** Prepares `append_inbox_with_jubjub` over one inbox entry: a re-encrypted coin or a Lace record. */
252
+ const prepareAppendInbox = (entry) => ({
253
+ circuit: APPEND_INBOX_CIRCUIT,
254
+ args: [entry]
255
+ });
256
+ //#endregion
257
+ //#region ../../packages/module/passport-account/src/flows/rotation.ts
258
+ /**
259
+ * How long an accepted call is given to show in the indexer's view of the
260
+ * ledger, which can lag the block the call landed in.
261
+ */
262
+ const LEDGER_POLL_ATTEMPTS = 30;
263
+ const LEDGER_POLL_DELAY_MS = 1e3;
264
+ const sleep$1 = async (ms) => new Promise((resolve) => {
265
+ setTimeout(resolve, ms);
266
+ });
267
+ /**
268
+ * The encryption key's view members, which a rotation and a read grant's
269
+ * issue need: the re-encryption a rotation moves the holdings through
270
+ * and the sealing a grantee receives the secret by.
271
+ *
272
+ * @throws {@link ReadUnavailableError} when the key offers either not,
273
+ * as a key that only opens entries does.
274
+ */
275
+ const viewCapability = (encryptionKey) => {
276
+ const { reencryptInbox, sealView } = encryptionKey;
277
+ if (!reencryptInbox || !sealView) throw new ReadUnavailableError();
278
+ return {
279
+ reencryptInbox,
280
+ sealView
281
+ };
282
+ };
283
+ /** Resolves this device's reader and device keys, inside the caller's key session. */
284
+ const readLocalDevice = async ({ passportAuthoriser, passportEncryptionKey, passportNetwork }) => ({
285
+ readerPublicKey: await passportEncryptionKey.readerPublicKey(passportNetwork.networkId),
286
+ devicePublicKey: encodeDevicePublicKey(await passportAuthoriser.devicePublicKey())
287
+ });
288
+ /** Reads the inbox and this device's keyring of the account from its ledger. */
289
+ const readAccountInbox = (account, ledgerState, readerPublicKey) => {
290
+ const entries = inboxEntries(ledgerState);
291
+ return {
292
+ entries,
293
+ keyring: {
294
+ account: AccAddress(account),
295
+ encKey: ledgerState.enc_key,
296
+ envelopes: deviceEnvelopes(entries, readerPublicKey)
297
+ }
298
+ };
299
+ };
300
+ /**
301
+ * The device entries the ledger holds, as lowercase hex, less `except`:
302
+ * the devices a rotation may seal the new secret to, which a key session
303
+ * names to the user before it opens.
304
+ *
305
+ * @throws {@link DeviceRosterInvalidError} when there are more than
306
+ * {@link MAX_DEVICE_ENTRIES}, which no signer would accept.
307
+ */
308
+ const liveDeviceEntries = (ledgerState, except) => {
309
+ const entries = [...ledgerState.devices].map((entry) => HexBytes.fromByteArray(entry)).filter((entry) => entry !== except?.toLowerCase());
310
+ if (entries.length > 64) throw new DeviceRosterInvalidError(`The account holds ${entries.length} device entries, more than the 64 a rotation names.`);
311
+ return entries;
312
+ };
313
+ /**
314
+ * The ledger once the indexer reflects a call that advanced the auth
315
+ * nonce past `authNonce`. A chain of gated calls must have each bind the
316
+ * nonce the previous one left behind, which the indexer can serve a
317
+ * moment late.
318
+ *
319
+ * @throws {@link GrantRosterUnsettledError} when the indexer never
320
+ * reflects the call within the polling window.
321
+ */
322
+ const awaitAdvancedLedger = async (indexer, address, authNonce) => {
323
+ for (let attempt = 0; attempt < LEDGER_POLL_ATTEMPTS; attempt++) {
324
+ const ledgerState = await queryAccountLedger(indexer, address);
325
+ if (ledgerState.auth_nonce > authNonce) return ledgerState;
326
+ await sleep$1(LEDGER_POLL_DELAY_MS);
327
+ }
328
+ throw new GrantRosterUnsettledError(`The ledger of ${address} never reflected the accepted call.`);
329
+ };
330
+ /**
331
+ * Starts a chain of gated calls at `start`. Each accepted call reports
332
+ * the counter it advanced this device to, then waits for the indexer to
333
+ * reflect it, so the next call binds the auth nonce it left; the local
334
+ * roster row follows the entry each call leaves it on.
335
+ */
336
+ const callChain = ({ account, start, indexer, seams, onProgress, onAdvanced }) => {
337
+ let current = start;
338
+ const submit = async (call) => {
339
+ const { advancedCounter, localCommitmentHex } = await submitAccountCall({
340
+ circuit: call.circuit,
341
+ args: call.args,
342
+ account,
343
+ localUseCounter: current.localUseCounter,
344
+ ledgerState: current.ledgerState,
345
+ onStage: onProgress
346
+ }, seams);
347
+ const localUseCounter = advancedCounter.toString();
348
+ await onAdvanced(localUseCounter);
349
+ current = {
350
+ ledgerState: await awaitAdvancedLedger(indexer, account.address, current.ledgerState.auth_nonce),
351
+ localUseCounter,
352
+ devices: withRefreshedLocalDevice(current.devices, localCommitmentHex)
353
+ };
354
+ };
355
+ return {
356
+ submit,
357
+ append: async (stage, entries) => {
358
+ if (entries.length > 0) onProgress(stage);
359
+ for (const entry of entries) await submit(prepareAppendInbox(entry));
360
+ },
361
+ position: () => current
362
+ };
363
+ };
364
+ /** The registrations of an inbox, by hex. */
365
+ const registrationsOf = (entries) => new Set(entries.filter(isDeviceRegistration).map((entry) => HexBytes.fromByteArray(entry)));
366
+ /**
367
+ * The roster a rotation seals to: the registrations the live roster key
368
+ * opens, resolved against the ledger's device set so only a registration
369
+ * whose device key derives a live entry counts, and only one whose entry
370
+ * is among `devices`. A registration naming nobody's live device is
371
+ * passed over, and named devices left without a reader, by a conflict or
372
+ * for want of a registration, are reported dropped, see
373
+ * {@link resolveLiveReaders}; this device, sealed to regardless, is never
374
+ * reported dropped. The registration whose live entry is `target`, the
375
+ * entry a removal takes out, is named so no reader paired with its device
376
+ * is sealed to.
377
+ */
378
+ const sealedRoster = async ({ account, encryptionKey, networkId, local, ledgerState, inbox, devices, target, localEntry }) => {
379
+ const registered = (await encryptionKey.openInbox(networkId, inbox.keyring, inbox.entries.filter(isDeviceRegistration))).flatMap((opened) => opened?.kind === "device" ? [opened.device] : []);
380
+ const self = HexBytes.fromByteArray(local.readerPublicKey);
381
+ const { readers, dropped, outside } = resolveLiveReaders({
382
+ scope: {
383
+ address: account.address,
384
+ epoch: ledgerState.device_epoch
385
+ },
386
+ registered,
387
+ isLive: (entryHex) => ledgerState.devices.member(ByteArray.fromHex(HexBytes(entryHex))),
388
+ allowed: new Set(devices),
389
+ target
390
+ });
391
+ const removed = outside.find(({ entryHex }) => entryHex === target);
392
+ return {
393
+ readers: readers.filter(({ reader }) => reader.readerPublicKey !== self).map(({ reader }) => reader),
394
+ dropped: dropped.filter((entry) => entry !== localEntry),
395
+ ...removed ? { removed: {
396
+ devicePublicKey: removed.reader.devicePublicKey,
397
+ epoch: removed.reader.epoch,
398
+ counter: removed.reader.counter
399
+ } } : {}
400
+ };
401
+ };
402
+ /**
403
+ * Rotates the account encryption key to a fresh random secret and
404
+ * re-encrypts every live holding towards it, as the scoped grants
405
+ * standard requires before a read grant is issued and after one is
406
+ * revoked, and as a device removal requires so the removed device reads
407
+ * nothing newer. The holdings are walked with the keyring of the ledger's
408
+ * `enc_key` from the position the chain is at, a keyring this device's
409
+ * envelopes do not open rejecting with EncryptionKeyMismatchError before
410
+ * anything is submitted. The encryption key draws the new secret and
411
+ * seals it, with the roster key, to this device and to every registered
412
+ * device that is enrolled now and among `devices`, and answers the
413
+ * ciphertexts alone: one envelope per device and, when the roster key is
414
+ * fresh, one registration each. Should any registration the live roster
415
+ * key opens land while the rotation runs, as another device's enrolment
416
+ * does, the rotation refuses with DeviceRosterInvalidError before
417
+ * `enc_key` is replaced, so a device enrolled meanwhile is never left
418
+ * without the new key; registrations that open under no roster key are
419
+ * noise and do not stop it. Devices whose registrations conflict are
420
+ * left out and reported in the result. The holdings are
421
+ * re-encrypted towards the new key, the envelopes and registrations are
422
+ * appended, `enc_key` is replaced through `rotate_enc_key_with_jubjub`,
423
+ * and each re-encrypted holding is appended, one gated call each, all in
424
+ * the key session the caller holds. The envelopes land ahead of the
425
+ * rotation, so no device is left without the key the chain then holds;
426
+ * a rotation interrupted after it leaves holdings under the previous
427
+ * key, which every device still opens from its older envelopes and the
428
+ * next rotation re-encrypts. The previous secret is never derived again
429
+ * by anyone: entries sealed towards it stay readable to whoever held it,
430
+ * and nothing newer is.
431
+ */
432
+ const rotateEncKey = async ({ account, encryptionKey, view, local, chain, indexer, networkId, onProgress, devices, rotateRoster, target }) => {
433
+ const { ledgerState } = chain.position();
434
+ const inbox = readAccountInbox(account.address, ledgerState, local.readerPublicKey);
435
+ const { entries, keyring } = inbox;
436
+ const { holdingEntries } = await walkInbox({
437
+ address: account.address,
438
+ ledgerState,
439
+ indexer,
440
+ openEntries: async (coinEntries) => (await encryptionKey.openInbox(networkId, keyring, coinEntries)).map((opened) => opened?.kind === "coin" ? opened.coin : null)
441
+ });
442
+ const { readers, dropped, removed } = await sealedRoster({
443
+ account,
444
+ encryptionKey,
445
+ networkId,
446
+ local,
447
+ ledgerState,
448
+ inbox,
449
+ devices,
450
+ target,
451
+ localEntry: chain.position().devices.find((device) => device.isLocal)?.commitmentHex.toLowerCase()
452
+ });
453
+ const keys = await encryptionKey.createKeys(networkId, {
454
+ devicePublicKey: local.devicePublicKey,
455
+ keyring,
456
+ registrations: entries.filter(isDeviceRegistration),
457
+ readers,
458
+ ...rotateRoster === void 0 ? {} : { rotateRoster },
459
+ ...removed ? { removed } : {}
460
+ });
461
+ const reencrypted = (await view.reencryptInbox(networkId, {
462
+ keyring,
463
+ to: keys.encKey
464
+ }, holdingEntries)).filter((entry) => entry !== null);
465
+ onProgress("rotating");
466
+ for (const entry of [...keys.envelopes, ...keys.registrations]) await chain.submit(prepareAppendInbox(entry));
467
+ const before = registrationsOf(entries);
468
+ const appended = registrationsOf(keys.registrations);
469
+ const landed = inboxEntries(chain.position().ledgerState).filter((entry) => isDeviceRegistration(entry) && !before.has(HexBytes.fromByteArray(entry)) && !appended.has(HexBytes.fromByteArray(entry)));
470
+ if (landed.length > 0 && (await encryptionKey.openInbox(networkId, keyring, landed)).some((opened) => opened?.kind === "device")) throw new DeviceRosterInvalidError(`Registrations landed in the inbox of ${account.address} while its keys rotated; rotate again.`);
471
+ await chain.submit(prepareRotateEncKey(keys.encKey));
472
+ await chain.append("reencrypting", reencrypted);
473
+ return {
474
+ encKey: keys.encKey,
475
+ dropped,
476
+ keyring: {
477
+ account: AccAddress(account.address),
478
+ encKey: keys.encKey,
479
+ envelopes: keys.envelopes.filter((envelope) => isAddressedTo(envelope, local.readerPublicKey))
480
+ }
481
+ };
482
+ };
483
+ /**
484
+ * Seals the live secret of `keyring` to each read grant's read key, with
485
+ * the grant record that keeps each seal on chain for this device. A
486
+ * grant the key refuses to seal to, an expired key session included, is
487
+ * listed unsealed rather than failing a rotation that already landed, or
488
+ * as revocation pending when the key refuses it with
489
+ * GrantRevocationPendingError.
490
+ */
491
+ const sealViews = async ({ view, networkId, keyring, account, targets }) => {
492
+ const sealedEncKey = HexBytes.fromByteArray(keyring.encKey);
493
+ const result = {
494
+ sealed: [],
495
+ unsealed: [],
496
+ revocationPending: [],
497
+ entries: []
498
+ };
499
+ for (const target of targets) {
500
+ let refusal;
501
+ const delivery = await view.sealView(networkId, keyring, {
502
+ ...target,
503
+ account
504
+ }).catch((error) => {
505
+ refusal = error;
506
+ });
507
+ if (!delivery) {
508
+ (refusal instanceof GrantRevocationPendingError ? result.revocationPending : result.unsealed).push(target.grantId);
509
+ continue;
510
+ }
511
+ result.sealed.push({
512
+ ...target,
513
+ seal: HexBytes.fromByteArray(delivery.seal),
514
+ sealedEncKey
515
+ });
516
+ result.entries.push(delivery.entry);
517
+ }
518
+ return result;
519
+ };
520
+ //#endregion
521
+ //#region ../../packages/module/passport-account/src/flows/stored-grants.ts
522
+ /**
523
+ * The slot each stored grant was derived under, keyed by id, as the
524
+ * roster decoder takes them.
525
+ */
526
+ const storedGrantSlots = (stored) => new Map(Object.entries(stored ?? {}).map(([id, { slot }]) => [GrantId(id), GrantSlot(slot)]));
527
+ /**
528
+ * The opening of each stored grant whose object fields were kept, keyed by
529
+ * id, as the roster decoder attaches them. A read grant whose seal this
530
+ * client holds carries it in the opening's view, with the `enc_key` whose
531
+ * secret it seals.
532
+ */
533
+ const storedGrantOpenings = (stored) => new Map(Object.entries(stored ?? {}).flatMap(([id, grant]) => grant.object ? [[GrantId(id), {
534
+ scopeSalt: GrantScopeSalt(grant.scopeSalt),
535
+ granteePublicKey: grant.granteePublicKey,
536
+ origin: grant.origin,
537
+ color: TokenColor(grant.object.color),
538
+ recipient: grant.object.recipient,
539
+ maxCoinValue: grant.object.maxCoinValue,
540
+ ...grant.view?.seal === void 0 || grant.view.sealedEncKey === void 0 ? {} : { view: {
541
+ readPublicKey: grant.view.readPublicKey,
542
+ seal: grant.view.seal,
543
+ sealedEncKey: grant.view.sealedEncKey
544
+ } }
545
+ }]] : []));
546
+ /**
547
+ * The stored grants with the view of one grant replaced: its read key
548
+ * and, when given, the seal towards it of the secret of `sealedEncKey`.
549
+ */
550
+ const withStoredGrantView = (stored, grantId, view) => {
551
+ const grant = stored[grantId];
552
+ if (!grant) return stored;
553
+ return {
554
+ ...stored,
555
+ [grantId]: {
556
+ ...grant,
557
+ view
558
+ }
559
+ };
560
+ };
561
+ /**
562
+ * The read grants this client holds the read key of and the chain still
563
+ * lists active, other than `except`: the grants a rotation seals the new
564
+ * secret to again. A read grant issued by another client has no stored
565
+ * key here, so it is not among them.
566
+ */
567
+ const liveStoredReadGrants = (stored, roster, except) => {
568
+ const active = new Set(roster.filter((record) => record.active && record.scope.read).map(({ id }) => id));
569
+ return Object.entries(stored ?? {}).flatMap(([id, grant]) => grant.view && active.has(GrantId(id)) && id !== except ? [{
570
+ grantId: GrantId(id),
571
+ readPublicKey: grant.view.readPublicKey
572
+ }] : []);
573
+ };
574
+ /**
575
+ * The active read grants this client holds the read key of whose stored
576
+ * seal does not hold the secret of `encKey`, the ledger's current one:
577
+ * their grantees read nothing newer until a rotation seals to them again
578
+ * or they are issued again. A read grant issued by another client has no
579
+ * stored key here, so it is not among them.
580
+ */
581
+ const unsealedStoredReadGrants = (stored, roster, encKey) => liveStoredReadGrants(stored, roster).filter(({ grantId }) => stored?.[grantId]?.view?.sealedEncKey !== encKey).map(({ grantId }) => grantId);
582
+ /** The stored grants with one more entry, under its grant id. */
583
+ const withStoredGrant = (stored, grantId, grant) => ({
584
+ ...stored,
585
+ [grantId]: grant
586
+ });
587
+ /**
588
+ * The record a completed account persists: its identity, this device's
589
+ * use counter, the grant bookkeeping when there is any, and the roster of
590
+ * devices the account keys were sealed to when it is known. It never
591
+ * carries a deployment, so a write of it completes a pending record.
592
+ */
593
+ const completedRecord = ({ address, bindingVersion }, localUseCounter, { grants }) => ({
594
+ address,
595
+ bindingVersion,
596
+ localUseCounter,
597
+ ...grants ? { grants } : {}
598
+ });
599
+ /**
600
+ * The stored grants with the seals of a rotation applied: each sealed
601
+ * grant's view carries its new seal and the `enc_key` it holds, and each
602
+ * unsealed one keeps its read key alone.
603
+ */
604
+ const withSealedViews = (stored, { sealed, unsealed }) => {
605
+ if (!stored) return stored;
606
+ let next = stored;
607
+ for (const { grantId, readPublicKey, seal, sealedEncKey } of sealed) next = withStoredGrantView(next, grantId, {
608
+ readPublicKey,
609
+ seal,
610
+ sealedEncKey
611
+ });
612
+ for (const grantId of unsealed) {
613
+ const readPublicKey = next[grantId]?.view?.readPublicKey;
614
+ if (readPublicKey !== void 0) next = withStoredGrantView(next, grantId, { readPublicKey });
615
+ }
616
+ return next;
617
+ };
618
+ /** Each grant's parts by id, the later of two parts of a kind winning. */
619
+ const partsByGrant = (parts) => {
620
+ const byGrant = /* @__PURE__ */ new Map();
621
+ for (const part of parts) {
622
+ const held = byGrant.get(part.grantId) ?? {};
623
+ byGrant.set(part.grantId, {
624
+ ...held,
625
+ [part.part]: part
626
+ });
627
+ }
628
+ return byGrant;
629
+ };
630
+ /**
631
+ * Whether a grant's bookkeeping opens its on-chain commitments under the
632
+ * account at `address`: the id derives from the account, the grantee
633
+ * key, the origin and the slot, the object commitment from the salt and
634
+ * the object fields, and the read key hashes to the one the contract
635
+ * holds, or is absent for a grant that does not read. Anyone can append
636
+ * any bytes to the inbox, and another client of the account issuing to
637
+ * the same grantee from the same origin lands on the same id with its
638
+ * own salt, so this check, not where the bookkeeping came from, is what
639
+ * makes it trusted.
640
+ */
641
+ const opensOnChain = async (record, fields, address) => {
642
+ try {
643
+ const grantId = deriveGrantId({
644
+ contractAddress: ByteArray.fromHex(HexBytes(address)),
645
+ granteePublicKey: granteePoint({
646
+ scheme: "jubjub-schnorr",
647
+ publicKey: fields.granteePublicKey
648
+ }),
649
+ originHash: await originHash(fields.origin),
650
+ slot: GrantSlot(fields.slot)
651
+ });
652
+ const encoded = circuitScope({
653
+ grantee: {
654
+ scheme: "jubjub-schnorr",
655
+ publicKey: fields.granteePublicKey
656
+ },
657
+ operations: [],
658
+ color: TokenColor(fields.object.color),
659
+ recipient: fields.object.recipient,
660
+ maxCoinValue: fields.object.maxCoinValue,
661
+ perCallCap: "0",
662
+ cap: "0",
663
+ expiresAt: null,
664
+ ...fields.readPublicKey === void 0 ? {} : { readPublicKey: fields.readPublicKey }
665
+ });
666
+ return grantId === record.id && grantObjectCommit(ByteArray.fromHex(HexBytes(fields.scopeSalt)), encoded) === record.scope.objectCommit && HexBytes.fromByteArray(encoded.readPkHash) === record.scope.readPkHash && fields.readPublicKey !== void 0 === record.scope.read;
667
+ } catch {
668
+ return false;
669
+ }
670
+ };
671
+ /**
672
+ * The stored grants narrowed to the ones that still open what the chain
673
+ * holds under their id, or undefined once none is left. A grant the
674
+ * chain no longer holds is dropped, and so is one whose kept fields do
675
+ * not open its commitments: the entry an issue wrote before a call that
676
+ * never landed, once another client of the account issued over the same
677
+ * id, or an entry written before the object fields were kept, which
678
+ * cannot be checked. A revoked grant stays in the contract's map as a
679
+ * tombstone, so its slot and salt are kept until a revoke-all clears the
680
+ * map or a new grant overwrites the id.
681
+ */
682
+ const retainStoredGrants = async (stored, roster, address) => {
683
+ if (!stored) return void 0;
684
+ const retained = {};
685
+ for (const record of roster) {
686
+ const grant = stored[record.id];
687
+ if (!grant?.object) continue;
688
+ if (await opensOnChain(record, {
689
+ ...grant,
690
+ object: grant.object,
691
+ ...grant.view === void 0 ? {} : { readPublicKey: grant.view.readPublicKey }
692
+ }, address)) retained[record.id] = grant;
693
+ }
694
+ return Object.keys(retained).length > 0 ? retained : void 0;
695
+ };
696
+ /**
697
+ * The bookkeeping of grants this device issued, rebuilt from the grant
698
+ * records it opened in the inbox, for the grants the chain still holds:
699
+ * each grant whose parts open its on-chain commitments under this
700
+ * account and origin, with its read key and, when the newest view record
701
+ * holds the seal of the ledger's `enc_key`, that seal. A grant whose
702
+ * parts do not check out, or whose grant or object part is missing, is
703
+ * left out. Undefined when nothing is rebuilt.
704
+ */
705
+ const rebuildStoredGrants = async (parts, roster, scope) => {
706
+ const byGrant = partsByGrant(parts);
707
+ const rebuilt = {};
708
+ for (const record of roster) {
709
+ const held = byGrant.get(record.id);
710
+ if (!held?.grant || !held.object) continue;
711
+ const { grant, object, view } = held;
712
+ if (!await opensOnChain(record, {
713
+ ...grant,
714
+ origin: scope.origin,
715
+ object: object.object
716
+ }, scope.address)) continue;
717
+ const readPublicKey = grant.readPublicKey;
718
+ const isCurrent = view !== void 0 && scope.encKey.startsWith(view.encKeyPrefix);
719
+ rebuilt[record.id] = {
720
+ slot: grant.slot,
721
+ scopeSalt: grant.scopeSalt,
722
+ granteePublicKey: grant.granteePublicKey,
723
+ origin: scope.origin,
724
+ object: object.object,
725
+ ...readPublicKey === void 0 ? {} : { view: isCurrent ? {
726
+ readPublicKey,
727
+ seal: view.seal,
728
+ sealedEncKey: scope.encKey
729
+ } : { readPublicKey } }
730
+ };
731
+ }
732
+ return Object.keys(rebuilt).length > 0 ? rebuilt : void 0;
733
+ };
734
+ /**
735
+ * The record's bookkeeping with every rebuilt grant it lacks added: what
736
+ * the record holds is kept, so a record that survived is never
737
+ * overwritten by the chain's copy, except for a view whose seal is not of
738
+ * `encKey`, the ledger's current one, when the rebuilt view's seal is:
739
+ * the record write after a rotation can be lost while its seal reached
740
+ * the inbox. Run over the bookkeeping {@link retainStoredGrants} checked,
741
+ * a rebuilt grant takes the place of an entry that did not open.
742
+ */
743
+ const withRebuiltGrants = (stored, rebuilt, encKey) => {
744
+ if (!rebuilt) return stored;
745
+ const merged = {
746
+ ...rebuilt,
747
+ ...stored
748
+ };
749
+ for (const [id, fromChain] of Object.entries(rebuilt)) {
750
+ const held = stored?.[id];
751
+ if (held && held.view?.sealedEncKey !== encKey && fromChain.view?.sealedEncKey === encKey) merged[id] = {
752
+ ...held,
753
+ view: fromChain.view
754
+ };
755
+ }
756
+ return merged;
757
+ };
758
+ //#endregion
759
+ //#region ../../packages/module/passport-account/src/flows/create-account.ts
760
+ /** The initial device entry is inserted at epoch 0 with use counter 0. */
761
+ const INITIAL_DEVICE_EPOCH = DeviceEpoch(0n);
762
+ const INITIAL_USE_COUNTER_VALUE = "0";
763
+ /**
764
+ * The record of a creation left unfinished on this device, read inside
765
+ * the session once presence reported one. A record that opens without a
766
+ * pending deployment, or that this authoriser cannot open, refuses with
767
+ * AccountExistsError: it names an account that must not be created over.
768
+ * A record that carries only the marker of its creation, which a hosted
769
+ * signer shows no flow but the creation, cannot be resumed and is
770
+ * refused, as is one started under another lock choice than
771
+ * `lockAccount`, with CreationLockMismatchError naming the recorded one.
772
+ */
773
+ const pendingRecord = async (accountRecords, lockAccount) => {
774
+ const record = await accountRecords.read();
775
+ if (!record?.deployment) throw new AccountExistsError();
776
+ 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.`);
777
+ if (record.deployment.lockAccount !== lockAccount) throw lockChoiceMismatch(record.deployment.lockAccount);
778
+ return {
779
+ ...record,
780
+ deployment: record.deployment
781
+ };
782
+ };
783
+ /**
784
+ * Runs the deployment: a fresh one, which draws the account keys first,
785
+ * deploys with their `enc_key` and records the pending deployment, with
786
+ * this device's envelope and registration, as soon as the deploy lands
787
+ * so an interruption can be resumed; or the remaining waves of a
788
+ * recorded one, with the key entries it recorded.
789
+ */
790
+ const deploy = async ({ providers, lockAccount, pending, local, context: { seams, accountRecords } }) => {
791
+ const { passportAuthoriser, passportEncryptionKey, passportNetwork } = seams;
792
+ if (pending) return {
793
+ deployed: await resumeAccountDeployment({
794
+ providers,
795
+ authoriser: passportAuthoriser,
796
+ address: pending.address,
797
+ pending: pending.deployment
798
+ }),
799
+ pending: pending.deployment
800
+ };
801
+ const keys = await passportEncryptionKey.createKeys(passportNetwork.networkId, { devicePublicKey: local.devicePublicKey });
802
+ const keyEntries = [...keys.envelopes, ...keys.registrations].map((entry) => HexBytes.fromByteArray(entry));
803
+ const progress = {};
804
+ const deployed = await deployAccount({
805
+ providers,
806
+ authoriser: passportAuthoriser,
807
+ encPublicKey: keys.encKey,
808
+ lockAccount,
809
+ onDeployed: async (address, deployment) => {
810
+ progress.recorded = {
811
+ ...deployment,
812
+ keyEntries
813
+ };
814
+ await accountRecords.write({
815
+ address,
816
+ bindingVersion: accManifest.bindingVersion,
817
+ localUseCounter: INITIAL_USE_COUNTER_VALUE,
818
+ deployment: progress.recorded
819
+ });
820
+ }
821
+ });
822
+ if (!progress.recorded) throw new Error("The deployment landed without reporting its progress.");
823
+ return {
824
+ deployed,
825
+ pending: progress.recorded
826
+ };
827
+ };
828
+ /**
829
+ * Creates a Passport account: the passkey ceremony (implicit in the first
830
+ * authoriser use), the account keys drawn through the encryption key
831
+ * seam, the Account Custody Contract deployment in waves with sponsored
832
+ * fees, the initial device activation, the delivery of the account keys
833
+ * to this device's inbox, and the local persistence of the account
834
+ * record. Every step runs inside one key session, so the whole flow costs
835
+ * a single ceremony rather than one per authoriser call. The flow never
836
+ * holds the encryption secret: the seam draws it at random, seals it to
837
+ * this device's reader key and hands back only its public key, which the
838
+ * constructor takes as `enc_key`, with this device's envelope and its
839
+ * registration under a fresh roster key; once the device is active, both
840
+ * are appended to the inbox, one gated call each, so the account keys
841
+ * live on chain from the start.
842
+ *
843
+ * Progress is reported through `onProgress`: 'deploying' once the
844
+ * authoriser has produced the device key, 'proving' and 'sponsoring' from
845
+ * inside each transaction submission (a transaction proves first, then
846
+ * the sponsor balances it), 'activating' before the activation call and
847
+ * 'recording' before the key entries are appended. The record is written
848
+ * before the promise resolves, so a process that dies right after still
849
+ * recognises the account on next startup.
850
+ *
851
+ * The flow runs to completion once started: cancelling mid-deploy could
852
+ * orphan a contract the sponsor already paid for. The caller's flow gate
853
+ * is what keeps a second request from starting one. Should the process
854
+ * die between waves anyway, the record written when the deploy landed
855
+ * names the contract, what it still needs and the key entries, and the
856
+ * next create request on this device resumes from the operations the
857
+ * chain holds, activates once every wave is in and appends whichever key
858
+ * entry the inbox still lacks; a creation that died after its activation
859
+ * landed resumes from there. A resumed creation finishes exactly as it
860
+ * began, with the keys recorded then: a request under another lock choice
861
+ * than the recorded one is refused with CreationLockMismatchError, and a
862
+ * build pinning other verifier keys than the recorded deployment with
863
+ * CreationArtefactMismatchError, both before any wave. The session names
864
+ * the lock choice, so an authoriser that asks consent first shows it; a
865
+ * hosted signer refuses the same lock mismatch itself, and a resume
866
+ * started from another origin than the creation it resumes.
867
+ *
868
+ * A device already holding a completed account record refuses with
869
+ * AccountExistsError before the ceremony runs: the record is the only
870
+ * pointer to the deployed contract and there is no recovery path, so
871
+ * creating over it would orphan the existing account. Presence is read
872
+ * without opening the record, and it tells a completed record from an
873
+ * unfinished one, so a sealed record blocks the flow without costing a
874
+ * prompt and only a record the flow may resume is opened, inside the
875
+ * session, where it costs no extra prompt.
876
+ */
877
+ const createAccount = async ({ lockAccount }, context) => {
878
+ const { seams, accountRecords, onProgress } = context;
879
+ const presence = await accountRecords.presence();
880
+ if (presence === "complete") throw new AccountExistsError();
881
+ const { passportAuthoriser, passportNetwork, passportProver, passportSponsor } = seams;
882
+ return inKeySession(passportAuthoriser, {
883
+ kind: "create-account",
884
+ lockAccount
885
+ }, async () => {
886
+ const local = await readLocalDevice(seams);
887
+ const pending = presence === "pending" ? await pendingRecord(accountRecords, lockAccount) : void 0;
888
+ onProgress("deploying");
889
+ const { deployed, pending: deployment } = await deploy({
890
+ providers: await createMidnightProviders({
891
+ network: passportNetwork,
892
+ prover: passportProver,
893
+ sponsor: passportSponsor,
894
+ onStage: onProgress
895
+ }),
896
+ lockAccount,
897
+ pending,
898
+ local,
899
+ context
900
+ });
901
+ if (!deployed.isActivated) {
902
+ onProgress("activating");
903
+ await deployed.activate();
904
+ }
905
+ const account = {
906
+ address: deployed.address,
907
+ bindingVersion: accManifest.bindingVersion,
908
+ status: "ready",
909
+ networkId: passportNetwork.networkId
910
+ };
911
+ const startCounter = pending?.localUseCounter ?? INITIAL_USE_COUNTER_VALUE;
912
+ const { indexer, ledgerState } = await readAccountLedger(deployed.address, passportNetwork);
913
+ const held = new Set(inboxEntries(ledgerState).map((entry) => HexBytes.fromByteArray(entry)));
914
+ const chain = callChain({
915
+ account,
916
+ start: {
917
+ ledgerState,
918
+ localUseCounter: startCounter,
919
+ devices: [{
920
+ commitmentHex: await passportAuthoriser.deviceCommitment(AccAddress(deployed.address), INITIAL_DEVICE_EPOCH, UseCounter(BigInt(startCounter))),
921
+ isLocal: true
922
+ }]
923
+ },
924
+ indexer,
925
+ seams,
926
+ onProgress,
927
+ onAdvanced: async (localUseCounter) => accountRecords.write({
928
+ address: account.address,
929
+ bindingVersion: account.bindingVersion,
930
+ localUseCounter,
931
+ deployment
932
+ })
933
+ });
934
+ await chain.append("recording", deployment.keyEntries.filter((entry) => !held.has(entry)).map((entry) => ByteArray.fromHex(HexBytes(entry))));
935
+ const { localUseCounter, devices } = chain.position();
936
+ const counter = localUseCounter ?? startCounter;
937
+ await accountRecords.write(completedRecord(account, counter, {}));
938
+ return {
939
+ account,
940
+ devices,
941
+ localUseCounter: counter,
942
+ grants: [],
943
+ encKey: HexBytes.fromByteArray(ledgerState.enc_key)
944
+ };
945
+ });
946
+ };
947
+ //#endregion
948
+ //#region ../../packages/module/passport-account/src/acc/read-grants.ts
949
+ const NO_SLOTS = /* @__PURE__ */ new Map();
950
+ const NO_OPENINGS = /* @__PURE__ */ new Map();
951
+ /** The slot reported for a grant whose slot this client never learned. */
952
+ const UNKNOWN_SLOT = GrantSlot(0);
953
+ const toNumber = (field, value) => {
954
+ if (value > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error(`Grant field ${field} exceeds the safe integer range`);
955
+ return Number(value);
956
+ };
957
+ const toOperations = (scope) => {
958
+ const operations = [];
959
+ if (scope.op_withdraw_unshielded) operations.push("withdraw-unshielded");
960
+ if (scope.op_withdraw_shielded) operations.push("withdraw-shielded");
961
+ if (scope.op_withdraw_shielded_to_contract) operations.push("withdraw-shielded-to-contract");
962
+ return operations;
963
+ };
964
+ const toScope = (scope) => ({
965
+ operations: toOperations(scope),
966
+ read: scope.read,
967
+ objectCommit: HexBytes.fromByteArray(scope.object_commit),
968
+ perCallCap: scope.per_call_cap.toString(),
969
+ cap: scope.cap.toString(),
970
+ expiresAt: scope.expires_at === 0n ? null : toNumber("expires_at", scope.expires_at),
971
+ rpCommit: HexBytes.fromByteArray(scope.rp_commit),
972
+ readPkHash: HexBytes.fromByteArray(scope.read_pk_hash),
973
+ window: scope.window_len === 0n ? null : {
974
+ lengthSeconds: toNumber("window_len", scope.window_len),
975
+ cap: scope.window_cap.toString()
976
+ },
977
+ callerCommit: HexBytes.fromByteArray(scope.caller_commit)
978
+ });
979
+ const toRecord = (id, record, { slot, opening }) => ({
980
+ id,
981
+ slot,
982
+ active: record.active,
983
+ epoch: toNumber("epoch", record.epoch),
984
+ generation: toNumber("gen", record.gen),
985
+ issuedAt: toNumber("issued_at", record.issued_at),
986
+ scope: toScope(record.scope),
987
+ spentWindow: {
988
+ start: toNumber("window_start", record.window_start),
989
+ spent: record.window_spent.toString()
990
+ },
991
+ ...opening ? { opening } : {}
992
+ });
993
+ /**
994
+ * Decodes the account's grant roster from the ledger's grants map, in the
995
+ * map's order. Plain fields decode to the read model's numbers and
996
+ * decimal strings, commitments stay as hex, and an expiry or window of
997
+ * zero reads as none. A revoked grant stays in the map as an inactive
998
+ * tombstone and is listed as such. The contract stores no slot: the
999
+ * caller supplies the slots of the grants it issued, and a grant issued by
1000
+ * another client of the account reports slot 0. The openings the caller
1001
+ * holds are attached to their grants.
1002
+ */
1003
+ const readGrants = (ledgerState, slots = NO_SLOTS, openings = NO_OPENINGS) => Array.from(ledgerState.grants, ([rawId, record]) => {
1004
+ const id = GrantId(HexBytes.fromByteArray(rawId));
1005
+ return toRecord(id, record, {
1006
+ slot: slots.get(id) ?? UNKNOWN_SLOT,
1007
+ opening: openings.get(id)
1008
+ });
1009
+ });
1010
+ //#endregion
1011
+ //#region ../../packages/module/passport-account/src/flows/devices.ts
1012
+ /**
1013
+ * Contract aborts meaning the removal would leave the account without an
1014
+ * authorising device: the caller rule and the redundant count floor.
1015
+ */
1016
+ const LAST_DEVICE_ABORTS = ["failed assert: cannot remove the authorising device", "failed assert: cannot remove last device"];
1017
+ /**
1018
+ * Types a raw contract abort by the assert message the Account Custody
1019
+ * Contract raises; anything unrecognised passes through unchanged.
1020
+ * An unknown-entry abort on the remove circuit is attributed to the
1021
+ * removal target, not the caller: the circuit checks the caller first,
1022
+ * and the caller's entry was resolved against live ledger state inside
1023
+ * the same gate, so only a mid-flight roll of the caller's own entry
1024
+ * could produce it, while a stale target commitment always does.
1025
+ */
1026
+ const classifyContractAbort$1 = (error, circuit) => {
1027
+ const message = error instanceof Error ? error.message : String(error);
1028
+ if (LAST_DEVICE_ABORTS.some((abort) => message.includes(abort))) return new LastDeviceError(message);
1029
+ if (message.includes("failed assert: unknown device entry")) return circuit === "remove_device_with_jubjub" ? new RemovalTargetNotFoundError(message) : new NotAuthorisedError(message);
1030
+ if (isNotAuthorisedAbort(message)) return new NotAuthorisedError(message);
1031
+ return error;
1032
+ };
1033
+ /**
1034
+ * Whether any read grant of the account is live: its grantees hold the
1035
+ * live account secret, so a device handed the keys while one is live
1036
+ * gets a fresh secret first.
1037
+ */
1038
+ const hasLiveReadGrant = (ledgerState) => readGrants(ledgerState).some((record) => record.active && record.scope.read);
1039
+ /** The devices list with the enrolled commitment appended. */
1040
+ const withDevice = (devices, commitmentHex) => [...devices, {
1041
+ commitmentHex,
1042
+ isLocal: false
1043
+ }];
1044
+ /** The devices list with the revoked commitment dropped. */
1045
+ const withoutDevice = (devices, commitmentHex) => devices.filter((device) => device.commitmentHex !== commitmentHex);
1046
+ /** A device's entry as the call that targets it takes it. */
1047
+ const entryArgument = (commitmentHex) => ByteArray.fromHex(HexBytes(commitmentHex.toLowerCase()));
1048
+ /** The counter the chain left this device at, or the one it started from. */
1049
+ const chainCounter = (chain, input) => chain.position().localUseCounter ?? input.localUseCounter ?? "0";
1050
+ /**
1051
+ * Opens what a device flow runs on inside its key session: the grant
1052
+ * bookkeeping of the record, read where it costs no extra prompt so every
1053
+ * write keeps it, and a chain of gated calls from the ledger read ahead
1054
+ * of the session, each persisting the counter it advanced this device to
1055
+ * before the next call. The bookkeeping is checked against the ledger
1056
+ * first, so an entry that does not open the grant the chain holds under
1057
+ * its id, as one an issue wrote before a call that never landed, is
1058
+ * neither re-sealed to nor kept.
1059
+ */
1060
+ const openDeviceSession = async (input, { ledgerState, indexer }, { seams, accountRecords, onProgress }) => {
1061
+ const { account, devices, localUseCounter } = input;
1062
+ let grants = await retainStoredGrants((await accountRecords.read())?.grants, readGrants(ledgerState), account.address);
1063
+ const write = async (counter) => accountRecords.write(completedRecord(account, counter, { grants }));
1064
+ const chain = callChain({
1065
+ account,
1066
+ start: {
1067
+ ledgerState,
1068
+ localUseCounter,
1069
+ devices
1070
+ },
1071
+ indexer,
1072
+ seams,
1073
+ onProgress,
1074
+ onAdvanced: write
1075
+ });
1076
+ return {
1077
+ grants,
1078
+ chain,
1079
+ keep: async (next) => {
1080
+ grants = next;
1081
+ await write(chainCounter(chain, input));
1082
+ }
1083
+ };
1084
+ };
1085
+ /**
1086
+ * Enrols a new device key on the account. `commitmentHex` carries the new
1087
+ * device's entry commitment, derived out of band by the joining device;
1088
+ * the local device authorises `add_device_with_jubjub` through the
1089
+ * gated-call pattern. The roster gains the commitment on success only,
1090
+ * marked as remote, and the same result moves the local device's own row
1091
+ * to the successor entry the call left it on, so the roster and the use
1092
+ * counter never disagree about where this device stands.
1093
+ *
1094
+ * When `reader` names the joining device's reader and device keys, the
1095
+ * account keys are delivered to it in the same session: its device key
1096
+ * must derive the enrolled commitment at use counter 0 and its reader key
1097
+ * must be one the account seals to, both checked before any prompt. The
1098
+ * encryption key seals the live secret and the roster key to the reader
1099
+ * key, and the envelope and the device's registration under the roster
1100
+ * key are appended to the inbox, one gated call each, ahead of the
1101
+ * enrolment, so an enrolment that lands never leaves the device without
1102
+ * its keys; until it lands the registration names no live entry and a
1103
+ * rotation passes over it. While any read grant is live its grantees
1104
+ * hold the live secret, and one could seal it to the joining reader with
1105
+ * a roster key of its own ahead of the genuine envelope, so the account
1106
+ * keys are rotated first in the same session, with a fresh roster key,
1107
+ * as a removal rotates them, the joining device handed the new secret no
1108
+ * grantee has seen, and the live read grants this client holds the read
1109
+ * key of sealed it after; with no live read grant only enrolled devices
1110
+ * hold the secret, and the live keys are handed over as they are. The
1111
+ * joining device then signs in and reads the holdings with its own
1112
+ * passkey alone. Without `reader` the
1113
+ * device can authorise but reads nothing until some device delivers it
1114
+ * the keys.
1115
+ *
1116
+ * The advanced counter is persisted to the account record after every
1117
+ * call, before the roster is returned, so the next flow rescans from a
1118
+ * fresh anchor. A failure leaves the roster unreturned.
1119
+ *
1120
+ * A rejected authorisation (bad signature, malformed key, or a caller
1121
+ * entry the on-chain set no longer holds) rejects with NotAuthorisedError;
1122
+ * a record whose address holds no live contract rejects with
1123
+ * AccountContractMissingError; joining keys that do not match the entry
1124
+ * reject with DeviceReaderInvalidError. The transactions' proving and
1125
+ * sponsoring stages and 'recording' are reported through `onProgress`.
1126
+ */
1127
+ const addDevice = async ({ reader, ...input }, context) => {
1128
+ const { seams, onProgress } = context;
1129
+ const { account, commitmentHex } = input;
1130
+ const { networkId } = seams.passportNetwork;
1131
+ try {
1132
+ const ledger = await readAccountLedger(account.address, seams.passportNetwork);
1133
+ if (reader) assertJoiningDevice({
1134
+ address: account.address,
1135
+ epoch: ledger.ledgerState.device_epoch
1136
+ }, reader, commitmentHex);
1137
+ const view = reader && hasLiveReadGrant(ledger.ledgerState) ? viewCapability(seams.passportEncryptionKey) : void 0;
1138
+ const devices = view ? liveDeviceEntries(ledger.ledgerState) : void 0;
1139
+ return await inKeySession(seams.passportAuthoriser, {
1140
+ kind: "add-device",
1141
+ commitmentHex,
1142
+ account: AccAddress(account.address),
1143
+ ...reader ? { reader } : {},
1144
+ ...devices ? { devices } : {}
1145
+ }, async () => {
1146
+ const { grants, chain, keep } = await openDeviceSession(input, ledger, context);
1147
+ let rotated;
1148
+ if (reader) {
1149
+ const local = await readLocalDevice(seams);
1150
+ if (view && devices) rotated = await rotateEncKey({
1151
+ account,
1152
+ encryptionKey: seams.passportEncryptionKey,
1153
+ view,
1154
+ local,
1155
+ chain,
1156
+ indexer: ledger.indexer,
1157
+ networkId,
1158
+ onProgress,
1159
+ devices,
1160
+ rotateRoster: true
1161
+ });
1162
+ const delivery = await seams.passportEncryptionKey.sealToDevice(networkId, rotated?.keyring ?? readAccountInbox(account.address, chain.position().ledgerState, local.readerPublicKey).keyring, {
1163
+ ...reader,
1164
+ epoch: ledger.ledgerState.device_epoch,
1165
+ counter: 0n
1166
+ });
1167
+ await chain.append("recording", [delivery.envelope, delivery.registration]);
1168
+ if (view && rotated) {
1169
+ const views = await sealViews({
1170
+ view,
1171
+ networkId,
1172
+ keyring: rotated.keyring,
1173
+ account: AccAddress(account.address),
1174
+ targets: liveStoredReadGrants(grants, readGrants(chain.position().ledgerState))
1175
+ });
1176
+ await keep(withSealedViews(grants, views));
1177
+ await chain.append("recording", views.entries);
1178
+ }
1179
+ }
1180
+ await chain.submit({
1181
+ circuit: ADD_DEVICE_CIRCUIT,
1182
+ args: [entryArgument(commitmentHex)]
1183
+ });
1184
+ return {
1185
+ devices: withDevice(chain.position().devices, commitmentHex),
1186
+ localUseCounter: chainCounter(chain, input),
1187
+ ...rotated ? {
1188
+ encKey: HexBytes.fromByteArray(rotated.encKey),
1189
+ droppedDevices: rotated.dropped
1190
+ } : {}
1191
+ };
1192
+ });
1193
+ } catch (error) {
1194
+ throw classifyContractAbort$1(error, ADD_DEVICE_CIRCUIT);
1195
+ }
1196
+ };
1197
+ /**
1198
+ * Refuses a removal the contract would refuse before any prompt: a
1199
+ * target the on-chain device set does not hold, and the local device,
1200
+ * which cannot remove itself. The rotation a removal runs comes first,
1201
+ * so a removal refused only after it would have cost the target device
1202
+ * its keys for nothing.
1203
+ *
1204
+ * @throws {@link RemovalTargetNotFoundError} when the target is gone.
1205
+ * @throws {@link LastDeviceError} when the target is this device.
1206
+ */
1207
+ const assertRemovable = ({ devices, commitmentHex }, { ledgerState }) => {
1208
+ if (!ledgerState.devices.member(entryArgument(commitmentHex))) throw new RemovalTargetNotFoundError(`The account holds no device entry ${commitmentHex}.`);
1209
+ if (devices.find((device) => device.isLocal)?.commitmentHex.toLowerCase() === commitmentHex.toLowerCase()) throw new LastDeviceError("The device that authorises a removal cannot remove itself.");
1210
+ };
1211
+ /**
1212
+ * Revokes a device key from the account and rotates the account keys so
1213
+ * the removed device reads nothing newer. `commitmentHex` names the
1214
+ * target by its current entry commitment, which is the literal set
1215
+ * element the contract removes; the local device authorises
1216
+ * `remove_device_with_jubjub` through the gated-call pattern.
1217
+ *
1218
+ * Ahead of the removal, in the same session, the encryption key is
1219
+ * rotated with a fresh roster key and sealed only to this device and to
1220
+ * the registered devices whose device key derives a live entry other
1221
+ * than the target, which the key session names to the user before it
1222
+ * opens; the live holdings are re-encrypted towards it, and it is sealed
1223
+ * again to every live read grant this client holds the read key of, each
1224
+ * seal kept on chain in a grant record. The removed device's envelopes
1225
+ * stay in the inbox and keep opening the secrets they held, but none
1226
+ * opens the new one, and the registrations under the old roster key no
1227
+ * longer count, any it planted for itself included. A roster in which
1228
+ * two readers claim one live device, or one reader two, refuses with
1229
+ * DeviceRosterInvalidError rather than guess.
1230
+ *
1231
+ * The roster drops the commitment on success only, and the same result
1232
+ * moves the local device's own row to the successor entry the call left
1233
+ * it on. The advanced counter is persisted to the account record after
1234
+ * every call. The contract's own guards surface as typed errors: a
1235
+ * removal that would strand the account (the authorising device, or the
1236
+ * count floor) rejects with LastDeviceError, a rejected authorisation
1237
+ * (bad signature or malformed key) with NotAuthorisedError, and a target
1238
+ * entry that is no longer in the on-chain set with
1239
+ * RemovalTargetNotFoundError; the target's presence and the local device
1240
+ * are checked before any prompt, and an encryption key that cannot
1241
+ * rotate refuses with ReadUnavailableError before any prompt too. The
1242
+ * rotation's stages, 'recording' and the transactions' proving and
1243
+ * sponsoring are reported through `onProgress`.
1244
+ */
1245
+ const removeDevice = async (input, context) => {
1246
+ const { seams, onProgress } = context;
1247
+ const { account, commitmentHex } = input;
1248
+ const { networkId } = seams.passportNetwork;
1249
+ try {
1250
+ const view = viewCapability(seams.passportEncryptionKey);
1251
+ const ledger = await readAccountLedger(account.address, seams.passportNetwork);
1252
+ assertRemovable(input, ledger);
1253
+ const devices = liveDeviceEntries(ledger.ledgerState, commitmentHex);
1254
+ return await inKeySession(seams.passportAuthoriser, {
1255
+ kind: "remove-device",
1256
+ commitmentHex,
1257
+ account: AccAddress(account.address),
1258
+ devices
1259
+ }, async () => {
1260
+ const { grants, chain, keep } = await openDeviceSession(input, ledger, context);
1261
+ const rotated = await rotateEncKey({
1262
+ account,
1263
+ encryptionKey: seams.passportEncryptionKey,
1264
+ view,
1265
+ local: await readLocalDevice(seams),
1266
+ chain,
1267
+ indexer: ledger.indexer,
1268
+ networkId,
1269
+ onProgress,
1270
+ devices,
1271
+ rotateRoster: true,
1272
+ target: commitmentHex.toLowerCase()
1273
+ });
1274
+ const views = await sealViews({
1275
+ view,
1276
+ networkId,
1277
+ keyring: rotated.keyring,
1278
+ account: AccAddress(account.address),
1279
+ targets: liveStoredReadGrants(grants, readGrants(chain.position().ledgerState))
1280
+ });
1281
+ await keep(withSealedViews(grants, views));
1282
+ await chain.append("recording", views.entries);
1283
+ await chain.submit({
1284
+ circuit: REMOVE_DEVICE_CIRCUIT,
1285
+ args: [entryArgument(commitmentHex)]
1286
+ });
1287
+ return {
1288
+ devices: withoutDevice(chain.position().devices, commitmentHex),
1289
+ localUseCounter: chainCounter(chain, input),
1290
+ encKey: HexBytes.fromByteArray(rotated.encKey),
1291
+ droppedDevices: rotated.dropped
1292
+ };
1293
+ });
1294
+ } catch (error) {
1295
+ throw classifyContractAbort$1(error, REMOVE_DEVICE_CIRCUIT);
1296
+ }
1297
+ };
1298
+ //#endregion
1299
+ //#region ../../packages/module/passport-account/src/flows/grants.ts
1300
+ /** Contract abort naming a grant the grants map does not hold. */
1301
+ const UNKNOWN_GRANT_ABORT = "failed assert: unknown grant";
1302
+ /** Contract abort refusing a revoke over a grant already inactive. */
1303
+ const GRANT_NOT_LIVE_ABORT = "failed assert: grant not live";
1304
+ /** Contract abort refusing an issue over a grant id that is still active. */
1305
+ const GRANT_ACTIVE_ABORT = "failed assert: grant already active";
1306
+ /**
1307
+ * How long an accepted call is given to show in the indexer's view of the
1308
+ * grants map, which can lag the block the call landed in.
1309
+ */
1310
+ const ROSTER_POLL_ATTEMPTS = 30;
1311
+ const ROSTER_POLL_DELAY_MS = 1e3;
1312
+ const MILLISECONDS_PER_SECOND = 1e3;
1313
+ const sleep = async (ms) => new Promise((resolve) => {
1314
+ setTimeout(resolve, ms);
1315
+ });
1316
+ const isActive = (roster, grantId) => roster.some((record) => record.id === grantId && record.active);
1317
+ const activeCount = (roster) => roster.filter((record) => record.active).length;
1318
+ const nowSeconds = () => Math.floor(Date.now() / MILLISECONDS_PER_SECOND);
1319
+ /**
1320
+ * Types a raw contract abort by the assert message the Account Custody
1321
+ * Contract raises; anything unrecognised passes through unchanged. An
1322
+ * unknown-entry abort can only come from the caller check here, since no
1323
+ * grant circuit names a device entry.
1324
+ */
1325
+ const classifyContractAbort = (error) => {
1326
+ const message = error instanceof Error ? error.message : String(error);
1327
+ if (message.includes(UNKNOWN_GRANT_ABORT)) return new GrantNotFoundError(message);
1328
+ if (message.includes(GRANT_NOT_LIVE_ABORT)) return new GrantRevokedError(message);
1329
+ if (message.includes(GRANT_ACTIVE_ABORT)) return new GrantIssueRejectedError(message);
1330
+ if (message.includes("failed assert: unknown device entry") || isNotAuthorisedAbort(message)) return new NotAuthorisedError(message);
1331
+ return error;
1332
+ };
1333
+ /**
1334
+ * Refuses a device whose storage holds no account record before any
1335
+ * ceremony. The gate only runs a call over an account in state, so an
1336
+ * absent record means the storage was cleared underneath it.
1337
+ */
1338
+ const assertRecordPresent = async (accountRecords) => {
1339
+ if (await accountRecords.presence() === "absent") throw new AccountNotFoundError("No Passport account record is stored on this device.");
1340
+ };
1341
+ /**
1342
+ * The stored record of the account a grant call acts on, read inside the
1343
+ * session where it costs no extra prompt. Presence was checked ahead of
1344
+ * the session, so a missing record here vanished in between.
1345
+ */
1346
+ const storedRecord = async (accountRecords) => {
1347
+ const record = await accountRecords.read();
1348
+ if (!record) throw new AccountNotFoundError("No Passport account record is stored on this device.");
1349
+ return record;
1350
+ };
1351
+ /**
1352
+ * The ledger state once the indexer reflects the accepted call, with the
1353
+ * roster read from it. The submission settled in a block, but the
1354
+ * indexer can serve the state from before it for a moment, and a roster
1355
+ * read then would deny the call its own result.
1356
+ *
1357
+ * @throws {@link GrantRosterUnsettledError} when the indexer never
1358
+ * reflects the call within the polling window.
1359
+ */
1360
+ const awaitSettledRoster = async ({ indexer, address, slots, openings, isSettled }) => {
1361
+ for (let attempt = 0; attempt < ROSTER_POLL_ATTEMPTS; attempt++) {
1362
+ const ledgerState = await queryAccountLedger(indexer, address);
1363
+ const roster = readGrants(ledgerState, slots, openings);
1364
+ if (isSettled(roster)) return {
1365
+ ledgerState,
1366
+ roster
1367
+ };
1368
+ await sleep(ROSTER_POLL_DELAY_MS);
1369
+ }
1370
+ throw new GrantRosterUnsettledError(`The grant roster of ${address} never reflected the accepted call.`);
1371
+ };
1372
+ /**
1373
+ * Runs one gated grant call inside a single key session, so the use
1374
+ * counter rescan and the authorisation cost one ceremony rather than one
1375
+ * per authoriser call. A device whose storage holds no record is refused
1376
+ * ahead of the session. The record is read inside it, where it costs no
1377
+ * extra prompt, its grant bookkeeping narrowed to the grants whose kept
1378
+ * fields open what the ledger read before the call holds under their id,
1379
+ * so an entry an issue wrote before a call that never landed is neither
1380
+ * an opening nor a re-seal target. It is rewritten with the advanced
1381
+ * counter and the grant bookkeeping after every call, so a roster is
1382
+ * never published ahead of the record that anchors the next rescan and
1383
+ * names the slots. A call
1384
+ * that keeps its bookkeeping ahead of the submission writes it once more
1385
+ * before it, under the counter the record holds. A call that rotates the
1386
+ * encryption key does so in the same session ahead of its own circuit.
1387
+ * The other live read grants are sealed the new secret right after the
1388
+ * rotation lands, their seals kept in the record and on chain before the
1389
+ * call is submitted, so they keep reading whatever the call's outcome.
1390
+ * Once the roster shows an issued grant active, its bookkeeping is
1391
+ * appended to the inbox, sealed to this device alone, and the grantee of
1392
+ * a read grant is sealed the new secret, the seal kept the same way. The
1393
+ * wait for the indexer is reported as 'confirming' and the appends as
1394
+ * 'recording'. The contract's own aborts surface as typed errors;
1395
+ * anything else, and a failing on-chain status in particular, rejects
1396
+ * unchanged with the counter unmoved.
1397
+ */
1398
+ 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 } }) => {
1399
+ const { networkId } = seams.passportNetwork;
1400
+ const accountAddress = AccAddress(account.address);
1401
+ try {
1402
+ await assertRecordPresent(accountRecords);
1403
+ const rotationDevices = rotation ? liveDeviceEntries(ledgerState) : void 0;
1404
+ return await inKeySession(seams.passportAuthoriser, rotationDevices ? {
1405
+ ...flow,
1406
+ devices: rotationDevices
1407
+ } : flow, async () => {
1408
+ const record = await storedRecord(accountRecords);
1409
+ let stored = nextStoredGrants(await retainStoredGrants(record.grants, listed, account.address));
1410
+ const write = async (counter) => accountRecords.write(completedRecord(account, counter, { grants: stored }));
1411
+ if (writeBeforeCall) await write(record.localUseCounter);
1412
+ const chain = callChain({
1413
+ account,
1414
+ start: {
1415
+ ledgerState,
1416
+ localUseCounter,
1417
+ devices
1418
+ },
1419
+ indexer,
1420
+ seams,
1421
+ onProgress,
1422
+ onAdvanced: write
1423
+ });
1424
+ const counter = () => chain.position().localUseCounter ?? record.localUseCounter;
1425
+ const roster = (state) => readGrants(state, storedGrantSlots(stored), storedGrantOpenings(stored));
1426
+ let rotated;
1427
+ let unsealedGrantIds = [];
1428
+ let revocationPendingGrantIds = [];
1429
+ if (rotation) {
1430
+ rotated = await rotateEncKey({
1431
+ account,
1432
+ encryptionKey: seams.passportEncryptionKey,
1433
+ view: rotation,
1434
+ local: await readLocalDevice(seams),
1435
+ chain,
1436
+ indexer,
1437
+ networkId,
1438
+ onProgress,
1439
+ devices: rotationDevices ?? []
1440
+ });
1441
+ if (reseal) {
1442
+ const views = await sealViews({
1443
+ view: rotation,
1444
+ networkId,
1445
+ keyring: rotated.keyring,
1446
+ account: accountAddress,
1447
+ targets: liveStoredReadGrants(stored, roster(chain.position().ledgerState), reseal.except)
1448
+ });
1449
+ stored = withSealedViews(stored, views);
1450
+ unsealedGrantIds = views.unsealed;
1451
+ revocationPendingGrantIds = views.revocationPending;
1452
+ await write(counter());
1453
+ await chain.append("recording", views.entries);
1454
+ }
1455
+ }
1456
+ await chain.submit(call);
1457
+ onProgress("confirming");
1458
+ const settled = await awaitSettledRoster({
1459
+ indexer,
1460
+ address: account.address,
1461
+ slots: storedGrantSlots(stored),
1462
+ openings: storedGrantOpenings(stored),
1463
+ isSettled
1464
+ });
1465
+ let grants = settled.roster;
1466
+ if (issued) {
1467
+ const entries = await seams.passportEncryptionKey.sealGrant(networkId, accountAddress, issued.bookkeeping);
1468
+ if (rotation && rotated && issued.readPublicKey !== void 0) {
1469
+ const views = await sealViews({
1470
+ view: rotation,
1471
+ networkId,
1472
+ keyring: rotated.keyring,
1473
+ account: accountAddress,
1474
+ targets: [{
1475
+ grantId: issued.bookkeeping.grantId,
1476
+ readPublicKey: issued.readPublicKey
1477
+ }]
1478
+ });
1479
+ stored = withSealedViews(stored, views);
1480
+ unsealedGrantIds = [...unsealedGrantIds, ...views.unsealed];
1481
+ revocationPendingGrantIds = [...revocationPendingGrantIds, ...views.revocationPending];
1482
+ entries.push(...views.entries);
1483
+ await write(counter());
1484
+ grants = roster(settled.ledgerState);
1485
+ }
1486
+ await chain.append("recording", entries);
1487
+ }
1488
+ return {
1489
+ grants,
1490
+ devices: chain.position().devices,
1491
+ localUseCounter: counter(),
1492
+ ...rotated ? {
1493
+ encKey: HexBytes.fromByteArray(rotated.encKey),
1494
+ droppedDevices: rotated.dropped
1495
+ } : {},
1496
+ unsealedGrantIds,
1497
+ revocationPendingGrantIds
1498
+ };
1499
+ });
1500
+ } catch (error) {
1501
+ throw classifyContractAbort(error);
1502
+ }
1503
+ };
1504
+ /**
1505
+ * Issues a scoped grant to an agent key. The scope is checked against the
1506
+ * rules the contract asserts before any prompt: a shielded operation
1507
+ * needs read, a read grant needs a canonical read public key, a rolling
1508
+ * window is refused because the contract reserves its bounds, and a
1509
+ * grant beyond sixteen active ones is refused as Lace's limit. Each
1510
+ * refusal is a GrantScopeInvalidError or GrantLimitReachedError and costs
1511
+ * no ceremony; a host with no relying party id is refused with
1512
+ * GrantOriginMissingError before the chain is read, and a read grant
1513
+ * with ReadUnavailableError when the encryption key held here cannot
1514
+ * share the viewing secret. The grant id is derived from the account,
1515
+ * the grantee key, the hash of this host's relying party id and the
1516
+ * first slot free for that pair, and the scope salt is drawn fresh; the
1517
+ * local device then authorises `issue_grant_with_jubjub` over the id and
1518
+ * the salted scope digest, which binds the hash of the read key. The key
1519
+ * session is opened with the account, the slot, the salt and the digest
1520
+ * beside the id and the scope, so an authoriser that does not share this
1521
+ * flow's memory can re-derive both before it signs.
1522
+ *
1523
+ * A read grant delivers the account's viewing secret, and read is total:
1524
+ * the grantee sees every shielded holding, current and future, until the
1525
+ * key is rotated again. So the key is rotated first, in the same session:
1526
+ * a fresh random secret is sealed to every device, the live holdings are
1527
+ * re-encrypted towards it, its public key replaces `enc_key`, and only
1528
+ * then is the grant issued, so the grantee reads nothing of the
1529
+ * account's spent history. Right after the rotation lands the new secret
1530
+ * is sealed to every other live read grant this client holds the key of,
1531
+ * so they keep reading even should the issue fail, and once the grant
1532
+ * shows active it is sealed to the grantee's read key; the seals join
1533
+ * the bookkeeping, and the grantee's reaches it through the agent
1534
+ * policy, never the chain. A grantee seal the key seam refuses after the
1535
+ * grant landed leaves the grant active and unsealed, named among the
1536
+ * result's unsealed grants, rather than failing the issue.
1537
+ *
1538
+ * The slot and salt are kept in the account record, since the contract
1539
+ * stores neither, together with the object fields its commitment hides
1540
+ * (colour, recipient and coin bound) and the read key, which the grantee
1541
+ * needs to open it and which reach the roster as the grant's opening.
1542
+ * They are written before the call is submitted: a grant that lands
1543
+ * without its salt could never be opened for its grantee. A call that
1544
+ * never lands leaves a stale entry, which the next sign-in prunes against
1545
+ * the chain and a later issue over the same id overwrites. Once the
1546
+ * grant shows active the same bookkeeping, and each seal, is appended to
1547
+ * the inbox in grant records only this device opens, so a sign-in after
1548
+ * the record is lost rebuilds it. The roster is read back from chain
1549
+ * once the grant shows active in it.
1550
+ *
1551
+ * A grant id the contract still holds active rejects with
1552
+ * GrantIssueRejectedError, and a rejected authorisation with
1553
+ * NotAuthorisedError. The transaction's proving and sponsoring stages,
1554
+ * the rotation, the wait for the indexer and the appends are reported
1555
+ * through `onProgress`.
1556
+ */
1557
+ const issueGrant = async ({ scope, ...input }, context) => {
1558
+ const { rpId, seams } = context;
1559
+ if (rpId === void 0) throw new GrantOriginMissingError();
1560
+ const view = scope.read ? viewCapability(seams.passportEncryptionKey) : null;
1561
+ const { account } = input;
1562
+ const ledger = await readAccountLedger(account.address, seams.passportNetwork);
1563
+ const roster = readGrants(ledger.ledgerState);
1564
+ validateGrantScope(scope, {
1565
+ nowSeconds: nowSeconds(),
1566
+ grantCount: activeCount(roster)
1567
+ });
1568
+ const { grantId, slot, scopeSalt, scopeDigest, call } = prepareIssueGrant({
1569
+ contractAddress: ByteArray.fromHex(HexBytes(account.address)),
1570
+ scope,
1571
+ originHash: await originHash(rpId),
1572
+ roster
1573
+ });
1574
+ const readPublicKey = view ? scope.readPublicKey : void 0;
1575
+ const object = {
1576
+ color: scope.color,
1577
+ recipient: scope.recipient,
1578
+ maxCoinValue: scope.maxCoinValue
1579
+ };
1580
+ return {
1581
+ grantId,
1582
+ ...await runGrantCall({
1583
+ config: {
1584
+ call,
1585
+ flow: {
1586
+ kind: "issue-grant",
1587
+ grantId,
1588
+ scope,
1589
+ account: AccAddress(account.address),
1590
+ slot,
1591
+ scopeSalt,
1592
+ scopeDigest
1593
+ },
1594
+ nextStoredGrants: (stored) => withStoredGrant(stored, grantId, {
1595
+ slot,
1596
+ scopeSalt,
1597
+ granteePublicKey: scope.grantee.publicKey,
1598
+ origin: rpId,
1599
+ object,
1600
+ ...readPublicKey === void 0 ? {} : { view: { readPublicKey } }
1601
+ }),
1602
+ writeBeforeCall: true,
1603
+ isSettled: (settled) => isActive(settled, grantId),
1604
+ ...view && readPublicKey !== void 0 ? {
1605
+ rotation: view,
1606
+ reseal: { except: grantId }
1607
+ } : {},
1608
+ issued: {
1609
+ bookkeeping: {
1610
+ grantId,
1611
+ slot,
1612
+ scopeSalt,
1613
+ granteePublicKey: scope.grantee.publicKey,
1614
+ object,
1615
+ ...readPublicKey === void 0 ? {} : { readPublicKey }
1616
+ },
1617
+ ...readPublicKey === void 0 ? {} : { readPublicKey }
1618
+ }
1619
+ },
1620
+ input,
1621
+ ledger,
1622
+ roster,
1623
+ context
1624
+ })
1625
+ };
1626
+ };
1627
+ /**
1628
+ * The stored grants with the seal of one grant dropped, its read key
1629
+ * kept: a revoked read grant's seal holds a secret the rotation that
1630
+ * follows retires.
1631
+ */
1632
+ const withoutSeal = (stored, grantId) => {
1633
+ const view = stored?.[grantId]?.view;
1634
+ return stored && view ? withStoredGrantView(stored, grantId, { readPublicKey: view.readPublicKey }) : stored;
1635
+ };
1636
+ /**
1637
+ * Revokes one grant by its id. A grant the chain does not hold is refused
1638
+ * with GrantNotFoundError before any prompt, and one it lists inactive
1639
+ * with GrantRevokedError; the same errors type the contract's own aborts
1640
+ * should the grant vanish or be revoked elsewhere in between. The local
1641
+ * device authorises `revoke_grant_with_jubjub`. The grant stays in the
1642
+ * roster as an inactive tombstone, so its stored slot and salt are kept,
1643
+ * and the roster is read back from chain once the grant shows inactive.
1644
+ *
1645
+ * Revoking a read grant also rotates the encryption key, in the same
1646
+ * session ahead of the revocation: a fresh random secret is sealed to
1647
+ * every device and the live holdings are re-encrypted towards it, so the
1648
+ * revoked grantee keeps what it already read and nothing newer, and
1649
+ * right after the rotation lands the new secret is sealed again to every
1650
+ * other read grant this client holds the read key of, ahead of the
1651
+ * revocation. A read grant is refused with ReadUnavailableError before
1652
+ * any prompt when the encryption key held here cannot re-encrypt and
1653
+ * seal.
1654
+ */
1655
+ const revokeGrant = async ({ grantId, ...input }, context) => {
1656
+ const { account } = input;
1657
+ const ledger = await readAccountLedger(account.address, context.seams.passportNetwork);
1658
+ const roster = readGrants(ledger.ledgerState);
1659
+ const listed = roster.find((record) => record.id === grantId);
1660
+ if (!listed) throw new GrantNotFoundError(`The account at ${account.address} holds no grant ${grantId}.`);
1661
+ if (!listed.active) throw new GrantRevokedError(`The grant ${grantId} of the account at ${account.address} is already revoked.`);
1662
+ const rotation = listed.scope.read ? viewCapability(context.seams.passportEncryptionKey) : void 0;
1663
+ return runGrantCall({
1664
+ config: {
1665
+ call: prepareRevokeGrant(grantId),
1666
+ flow: {
1667
+ kind: "revoke-grant",
1668
+ grantId,
1669
+ account: AccAddress(account.address),
1670
+ ...rotation ? { read: true } : {}
1671
+ },
1672
+ nextStoredGrants: (stored) => rotation ? withoutSeal(stored, grantId) : stored,
1673
+ writeBeforeCall: false,
1674
+ isSettled: (settled) => !isActive(settled, grantId),
1675
+ ...rotation ? {
1676
+ rotation,
1677
+ reseal: { except: grantId }
1678
+ } : {}
1679
+ },
1680
+ input,
1681
+ ledger,
1682
+ roster,
1683
+ context
1684
+ });
1685
+ };
1686
+ /**
1687
+ * Revokes every grant the account has issued: the local device
1688
+ * authorises `revoke_all_grants_with_jubjub`, which clears the grants
1689
+ * map and advances the grant generation. The record's grant bookkeeping
1690
+ * is dropped with the map, and the roster is read back from chain once
1691
+ * it lists nothing, tombstones included. While any read grant is live,
1692
+ * the encryption key is rotated in the same session ahead of the call
1693
+ * and the live holdings are re-encrypted, so no revoked grantee reads
1694
+ * on; with every grant gone there is nobody to re-seal to. The rotation
1695
+ * is refused with ReadUnavailableError before any prompt when the
1696
+ * encryption key held here cannot re-encrypt.
1697
+ */
1698
+ const revokeAllGrants = async (input, context) => {
1699
+ const { account } = input;
1700
+ const ledger = await readAccountLedger(account.address, context.seams.passportNetwork);
1701
+ const roster = readGrants(ledger.ledgerState);
1702
+ const rotation = roster.some((record) => record.active && record.scope.read) ? viewCapability(context.seams.passportEncryptionKey) : void 0;
1703
+ return runGrantCall({
1704
+ config: {
1705
+ call: prepareRevokeAllGrants(),
1706
+ flow: {
1707
+ kind: "revoke-all-grants",
1708
+ account: AccAddress(account.address),
1709
+ ...rotation ? { read: true } : {}
1710
+ },
1711
+ nextStoredGrants: () => void 0,
1712
+ writeBeforeCall: false,
1713
+ isSettled: (settled) => settled.length === 0,
1714
+ ...rotation ? { rotation } : {}
1715
+ },
1716
+ input,
1717
+ ledger,
1718
+ roster,
1719
+ context
1720
+ });
1721
+ };
1722
+ //#endregion
1723
+ //#region ../../packages/module/passport-account/src/flows/holdings.ts
1724
+ /**
1725
+ * Reads the account's shielded holdings from public chain data: the
1726
+ * contract's inbox is walked with the account secrets this device's own
1727
+ * envelopes deliver, the one deriving the ledger's `enc_key` first and
1728
+ * the older ones after it, so a coin a racing sender or an interrupted
1729
+ * rotation left under an earlier key is still found, and each coin that
1730
+ * opens is kept only once its commitment is found among the outputs the
1731
+ * chain created for the account, with the tree index it took. The
1732
+ * envelopes are opened inside one key session, so the walk costs one
1733
+ * ceremony however many entries the inbox holds, and none at all when
1734
+ * it holds no coin to open; only the opened coin descriptions reach this
1735
+ * flow, never a secret. Nothing is written: no record, no chain call.
1736
+ *
1737
+ * A record whose address holds no live contract rejects with
1738
+ * AccountContractMissingError, and an inbox holding no envelope of this
1739
+ * device that opens the ledger's `enc_key` with EncryptionKeyMismatchError
1740
+ * rather than an inbox read as empty. An entry that fails to open or
1741
+ * describes a coin the chain never created is skipped and counted, never
1742
+ * an error; the account keys and grant bookkeeping the inbox also holds
1743
+ * are not coins and are not counted.
1744
+ *
1745
+ * Reports no intermediate stage: the ceremony and the indexer reads all
1746
+ * settle before there is anything to report.
1747
+ */
1748
+ const readShieldedHoldings = async ({ account }, { seams }) => {
1749
+ const { passportAuthoriser, passportEncryptionKey, passportNetwork } = seams;
1750
+ const indexer = createIndexerClient({
1751
+ indexerUrl: passportNetwork.indexerUrl,
1752
+ indexerWsUrl: passportNetwork.indexerWsUrl
1753
+ });
1754
+ const ledgerState = await queryAccountLedger(indexer, account.address);
1755
+ const { holdings, skippedEntries } = await walkInbox({
1756
+ address: account.address,
1757
+ ledgerState,
1758
+ indexer,
1759
+ openEntries: async (entries) => inKeySession(passportAuthoriser, { kind: "read-holdings" }, async () => {
1760
+ const { keyring } = readAccountInbox(account.address, ledgerState, await passportEncryptionKey.readerPublicKey(passportNetwork.networkId));
1761
+ return (await passportEncryptionKey.openInbox(passportNetwork.networkId, keyring, entries)).map((entry) => entry?.kind === "coin" ? entry.coin : null);
1762
+ })
1763
+ });
1764
+ return {
1765
+ holdings,
1766
+ skippedEntries
1767
+ };
1768
+ };
1769
+ //#endregion
1770
+ //#region ../../packages/module/passport-account/src/flows/sign-in.ts
1771
+ /**
1772
+ * The account's whole device roster, read from the on-chain device set.
1773
+ * Entries are opaque rolling commitments, so a remote entry carries no
1774
+ * identity beyond its commitment: only the local device can be named, by
1775
+ * the commitment its own rescan resolved.
1776
+ */
1777
+ const toDeviceRoster = (entries, localCommitmentHex) => Array.from(entries, (entry) => {
1778
+ const commitmentHex = HexBytes.fromByteArray(entry);
1779
+ return {
1780
+ commitmentHex,
1781
+ isLocal: commitmentHex === localCommitmentHex
1782
+ };
1783
+ });
1784
+ /**
1785
+ * Refuses a record whose creation is still unfinished on chain. A record
1786
+ * that carries a pending deployment names a contract that may still miss
1787
+ * verifier keys, its initial device or the entries that deliver the
1788
+ * account keys, which only the create flow can complete; but a creation
1789
+ * that appended its key entries and then died before its final record
1790
+ * write left a usable account behind, and the chain's boot flag and inbox
1791
+ * tell the two apart.
1792
+ */
1793
+ const assertCreationComplete = (record, isActivated, entries) => {
1794
+ const { deployment } = record;
1795
+ if (deployment === void 0) return;
1796
+ const held = new Set(entries.map((entry) => HexBytes.fromByteArray(entry)));
1797
+ if (!isActivated || deployment.keyEntries.some((entry) => !held.has(entry))) throw new AccountCreationPendingError(`The account at ${record.address} is deployed but not yet ready; resume the creation on this device, from the site that started it.`);
1798
+ };
1799
+ /** The grant record parts the Lace records of an inbox opened to. */
1800
+ const grantPartsOf = (opened) => opened.flatMap((entry) => entry?.kind === "grant" ? [entry.record] : []);
1801
+ /**
1802
+ * Recognises an existing Passport account on this device: the passkey
1803
+ * ceremony (implicit in the first authoriser use) proves the device key,
1804
+ * the persisted account record names the Account Custody Contract, the
1805
+ * indexer confirms the contract is live, this device's own envelopes in
1806
+ * the contract's inbox open a secret deriving the `enc_key` the contract
1807
+ * advertises, and the use-counter rescan restores this device's roster
1808
+ * position from the on-chain device set. The account secret is never
1809
+ * read from the record or derived from the passkey: it is random, and
1810
+ * this device reaches it only through an envelope sealed to its reader
1811
+ * key, so a device enrolled with its own passkey signs in without the
1812
+ * record of the device that created the account. The roster is rebuilt
1813
+ * from every entry in the device set, so a device enrolled elsewhere
1814
+ * survives a reload; only the local entry is named, the rest are opaque
1815
+ * commitments. The grant roster is read from the contract's grants map
1816
+ * the same way, with the slots and openings the record kept for the
1817
+ * grants this client issued; a grant the record lacks, as after the
1818
+ * record was lost, is rebuilt from the grant records this device keeps in
1819
+ * the inbox once they open its on-chain commitments under this host's
1820
+ * relying party id, its seal kept only while it holds the live secret.
1821
+ * A grant the record holds is kept only while its fields open the
1822
+ * commitments the chain holds under its id, so an entry an issue wrote
1823
+ * before a call that never landed gives way to the grant another client
1824
+ * of the account issued over the same id, and a rebuilt grant takes its
1825
+ * place when there is one. The live read grants whose kept seal does not
1826
+ * hold the live secret, as after an issue that failed before its seal,
1827
+ * are reported as unsealed.
1828
+ * The devices registered under the live roster key are kept in the
1829
+ * record as the roster the next rotation seals to. The whole chain runs
1830
+ * inside one key session, so it costs a single ceremony rather than one
1831
+ * per authoriser call.
1832
+ *
1833
+ * The healed counter is written back to the persisted record before this
1834
+ * resolves, so the next sign-in rescans from a fresh anchor instead of the
1835
+ * creation-time one. That write also completes a record a creation left
1836
+ * pending after its key entries landed, dropping the deployment and the
1837
+ * authority key it carries, and drops the bookkeeping of grants the chain
1838
+ * no longer holds or whose commitments it does not open. A missing record
1839
+ * raises AccountNotFoundError, a record whose address holds no live
1840
+ * contract raises AccountContractMissingError,
1841
+ * a record whose creation is unfinished on chain raises
1842
+ * AccountCreationPendingError, and an inbox holding no envelope of this
1843
+ * device that opens the live `enc_key`, as for a device never handed the
1844
+ * keys or removed since, raises EncryptionKeyMismatchError before the
1845
+ * rescan runs; rediscovering an account by name service when no record is
1846
+ * persisted is a separate flow and out of scope here.
1847
+ *
1848
+ * signIn reports no intermediate stage: the ceremony, the indexer read and
1849
+ * the rescan all settle before there is anything to report.
1850
+ */
1851
+ const signIn = async ({ seams, accountRecords, rpId }) => {
1852
+ const { passportAuthoriser, passportEncryptionKey, passportNetwork } = seams;
1853
+ return inKeySession(passportAuthoriser, { kind: "sign-in" }, async () => {
1854
+ const local = await readLocalDevice(seams);
1855
+ const record = await accountRecords.read();
1856
+ if (!record) throw new AccountNotFoundError("No Passport account record is stored on this device.");
1857
+ const state = await createIndexerClient({
1858
+ indexerUrl: passportNetwork.indexerUrl,
1859
+ indexerWsUrl: passportNetwork.indexerWsUrl
1860
+ }).queryContractState(record.address);
1861
+ if (!state) throw new AccountContractMissingError(`No live Account Custody Contract found at ${record.address}.`);
1862
+ const ledgerState = ledger(state.data);
1863
+ const { entries, keyring } = readAccountInbox(record.address, ledgerState, local.readerPublicKey);
1864
+ assertCreationComplete(record, ledgerState.booted, entries);
1865
+ if (keyring.envelopes.length === 0) throw new EncryptionKeyMismatchError(`The inbox of the account at ${record.address} holds no envelope for this device.`);
1866
+ const grantParts = grantPartsOf(await passportEncryptionKey.openInbox(passportNetwork.networkId, keyring, entries.filter(isLaceRecord)));
1867
+ const { useCounter, commitmentHex } = await resolveUseCounter({
1868
+ ledgerState,
1869
+ authoriser: passportAuthoriser,
1870
+ address: AccAddress(record.address),
1871
+ knownUseCounter: UseCounter(BigInt(record.localUseCounter))
1872
+ });
1873
+ const devices = toDeviceRoster(ledgerState.devices, commitmentHex);
1874
+ const encKey = HexBytes.fromByteArray(ledgerState.enc_key);
1875
+ const onChain = readGrants(ledgerState);
1876
+ const stored = withRebuiltGrants(await retainStoredGrants(record.grants, onChain, record.address), rpId === void 0 ? void 0 : await rebuildStoredGrants(grantParts, onChain, {
1877
+ address: record.address,
1878
+ origin: rpId,
1879
+ encKey
1880
+ }), encKey);
1881
+ const grants = readGrants(ledgerState, storedGrantSlots(stored), storedGrantOpenings(stored));
1882
+ const localUseCounter = useCounter.toString();
1883
+ await accountRecords.write(completedRecord(record, localUseCounter, { grants: stored }));
1884
+ return {
1885
+ account: {
1886
+ address: record.address,
1887
+ bindingVersion: record.bindingVersion,
1888
+ status: "ready",
1889
+ networkId: passportNetwork.networkId
1890
+ },
1891
+ devices,
1892
+ localUseCounter,
1893
+ grants,
1894
+ encKey,
1895
+ unsealedGrantIds: unsealedStoredReadGrants(stored, onChain, encKey)
1896
+ };
1897
+ });
1898
+ };
1899
+ //#endregion
1900
+ //#region ../../packages/module/passport-account/src/store/dependencies.ts
1901
+ /**
1902
+ * Runs a flow as a cold stream of its events. `run` is invoked once per
1903
+ * subscription with a progress sink that emits synchronously as the flow
1904
+ * reports; the resolution emits 'done' and completes, and a rejection
1905
+ * errors the stream. Unsubscribing only stops delivery: the running
1906
+ * promise is not cancelled, because a flow must run to completion once
1907
+ * started (abandoning it mid-deploy could orphan a sponsored contract).
1908
+ */
1909
+ const observeFlow = (run) => new Observable((subscriber) => {
1910
+ let isDelivering = true;
1911
+ run((stage) => {
1912
+ if (isDelivering) subscriber.next({
1913
+ type: "progress",
1914
+ stage
1915
+ });
1916
+ }).then((result) => {
1917
+ if (!isDelivering) return;
1918
+ subscriber.next({
1919
+ type: "done",
1920
+ result
1921
+ });
1922
+ subscriber.complete();
1923
+ }, (error) => {
1924
+ if (isDelivering) subscriber.error(error);
1925
+ });
1926
+ return () => {
1927
+ isDelivering = false;
1928
+ };
1929
+ });
1930
+ /**
1931
+ * The context a flow runs under, drawn from the side-effect dependency
1932
+ * bag: the five seams, the record store and the relying party id. An
1933
+ * injected store is used as is; otherwise the store is built over the
1934
+ * platform storage and keyed by the authoriser, so a record sealed by one
1935
+ * authoriser is only ever written through it. The sealed-record options
1936
+ * apply to that built store; signIn and the grant flows pass
1937
+ * `onSealedWithoutKey: 'error'` so a sealed record found without a
1938
+ * storage key surfaces as record-unreadable rather than as an absent
1939
+ * account.
1940
+ */
1941
+ const toFlowContext = ({ createKeyValueStorage, passportAuthoriser, passportEncryptionKey, passportNetwork, passportProver, passportSponsor }, onProgress, { store, rpId, ...recordOptions }) => ({
1942
+ seams: {
1943
+ passportAuthoriser,
1944
+ passportEncryptionKey,
1945
+ passportNetwork,
1946
+ passportProver,
1947
+ passportSponsor
1948
+ },
1949
+ accountRecords: toAccountRecords(store ?? createAccountRecordStore(createKeyValueStorage, passportAuthoriser.storageKey, recordOptions)),
1950
+ onProgress,
1951
+ rpId
1952
+ });
1953
+ /**
1954
+ * Maps the platform capabilities passed to `createPassportAccountModule`
1955
+ * onto the `passport-dependency` contract, and exposes the platform-neutral
1956
+ * flows observable-based. The flows are promise functions; wrapping them
1957
+ * here rather than in the side effects is what keeps promise handling out
1958
+ * of the side effects, so they compose the flows as plain streams and stay
1959
+ * testable on a virtual scheduler. The relying party id defaults to the
1960
+ * page's hostname, as the passkey ceremony's does.
1961
+ */
1962
+ const createDependencies = ({ authoriser, encryptionKey, sponsor, prover, network, accountRecordStore, rpId = globalThis.location?.hostname }) => {
1963
+ const host = {
1964
+ store: accountRecordStore,
1965
+ rpId
1966
+ };
1967
+ const recordRequired = {
1968
+ ...host,
1969
+ onSealedWithoutKey: "error"
1970
+ };
1971
+ return {
1972
+ passportAuthoriser: authoriser,
1973
+ passportEncryptionKey: encryptionKey,
1974
+ passportSponsor: sponsor,
1975
+ passportProver: prover,
1976
+ passportNetwork: network,
1977
+ passportFlows: {
1978
+ createAccount: (input, dependencies) => observeFlow(async (onProgress) => createAccount(input, toFlowContext(dependencies, onProgress, host))),
1979
+ signIn: (dependencies) => observeFlow(async (onProgress) => signIn(toFlowContext(dependencies, onProgress, recordRequired))),
1980
+ addDevice: (input, dependencies) => observeFlow(async (onProgress) => addDevice(input, toFlowContext(dependencies, onProgress, host))),
1981
+ removeDevice: (input, dependencies) => observeFlow(async (onProgress) => removeDevice(input, toFlowContext(dependencies, onProgress, host))),
1982
+ issueGrant: (input, dependencies) => observeFlow(async (onProgress) => issueGrant(input, toFlowContext(dependencies, onProgress, recordRequired))),
1983
+ revokeGrant: (input, dependencies) => observeFlow(async (onProgress) => revokeGrant(input, toFlowContext(dependencies, onProgress, recordRequired))),
1984
+ revokeAllGrants: (input, dependencies) => observeFlow(async (onProgress) => revokeAllGrants(input, toFlowContext(dependencies, onProgress, recordRequired))),
1985
+ readShieldedHoldings: (input, dependencies) => observeFlow(async (onProgress) => readShieldedHoldings(input, toFlowContext(dependencies, onProgress, host)))
1986
+ }
1987
+ };
1988
+ };
1989
+ //#endregion
1990
+ //#region ../../packages/module/passport-account/src/store/side-effects/flow-actions.ts
1991
+ /**
1992
+ * The flow error a failure reports: the error's own typed code when it
1993
+ * carries one, otherwise the flow's fallback code.
1994
+ */
1995
+ const toFlowError = (error, fallbackCode) => ({
1996
+ code: error instanceof Object && "code" in error && typeof error.code === "string" ? error.code : fallbackCode,
1997
+ message: error instanceof Error ? error.message : String(error)
1998
+ });
1999
+ /**
2000
+ * Maps a flow's events onto the passport flow state: each stage to
2001
+ * setFlow, the result to its publishing actions followed by
2002
+ * setFlow('ready'), and a failure to its `onFailed` actions followed by
2003
+ * a single setFlowError, so a flow always settles in 'ready' or 'error'.
2004
+ */
2005
+ const flowActions = (events$, { actions, fallbackCode, onDone, onFailed = [] }) => events$.pipe(mergeMap((event) => event.type === "progress" ? [actions.passport.setFlow(event.stage)] : [...onDone(event.result), actions.passport.setFlow("ready")]), catchError((error) => of(...onFailed, actions.passport.setFlowError(toFlowError(error, fallbackCode)))));
2006
+ //#endregion
2007
+ //#region ../../packages/module/passport-account/src/store/side-effects/passport-flows.ts
2008
+ /** Flow error code for creation failures that carry no typed code of their own. */
2009
+ const CREATE_ACCOUNT_FALLBACK_ERROR_CODE = "account-create-failed";
2010
+ /** Flow error code for sign-in failures that carry no typed code of their own. */
2011
+ const SIGN_IN_FALLBACK_ERROR_CODE = "account-sign-in-failed";
2012
+ /** Flow error codes for device call failures that carry no typed code of their own. */
2013
+ const ADD_DEVICE_FALLBACK_ERROR_CODE = "device-add-failed";
2014
+ const REMOVE_DEVICE_FALLBACK_ERROR_CODE = "device-remove-failed";
2015
+ /** Flow error codes for grant call failures that carry no typed code of their own. */
2016
+ const ISSUE_GRANT_FALLBACK_ERROR_CODE = "grant-issue-failed";
2017
+ const REVOKE_GRANT_FALLBACK_ERROR_CODE = "grant-revoke-failed";
2018
+ const REVOKE_ALL_GRANTS_FALLBACK_ERROR_CODE = "grant-revoke-all-failed";
2019
+ /** Flow error code for holdings reads that carry no typed code of their own. */
2020
+ const READ_HOLDINGS_FALLBACK_ERROR_CODE = "holdings-read-failed";
2021
+ /**
2022
+ * Flows an account call may start from. 'error' is a settled state, not
2023
+ * an operation in progress, so a call that failed can be retried without
2024
+ * a fresh sign-in ceremony; every other non-ready flow is an operation
2025
+ * left unfinished, whose account and use counter cannot be trusted yet.
2026
+ */
2027
+ const SETTLED_FLOWS = ["error", "ready"];
2028
+ /**
2029
+ * The actions that publish a roster a flow left behind: the devices, then
2030
+ * the counter the local row sits at. Both land ahead of 'ready', in the
2031
+ * order the flows have always published them.
2032
+ */
2033
+ const publishDeviceRoster = (actions) => ({ devices, localUseCounter }) => [actions.passport.setDevices(devices), actions.passport.setLocalUseCounter(localUseCounter)];
2034
+ /**
2035
+ * The actions that publish what a grant call left behind: the device
2036
+ * roster and counter it advanced, the `enc_key` a rotation it ran stepped
2037
+ * the encryption key to and the devices it left without the new key, the
2038
+ * grant roster, then the read grants the
2039
+ * call could not seal the viewing secret to, empty when it sealed all,
2040
+ * and those the signer refused because their revocation is pending.
2041
+ */
2042
+ const publishGrantRoster = (actions) => ({ grants, encKey, unsealedGrantIds, revocationPendingGrantIds, droppedDevices, ...roster }) => [
2043
+ ...publishDeviceRoster(actions)(roster),
2044
+ ...encKey === void 0 ? [] : [actions.passport.setEncKey(encKey)],
2045
+ ...droppedDevices === void 0 ? [] : [actions.passport.setDroppedDevices(droppedDevices)],
2046
+ actions.passport.setGrants(grants),
2047
+ actions.passport.setUnsealedGrantIds(unsealedGrantIds),
2048
+ actions.passport.setRevocationPendingGrantIds(revocationPendingGrantIds)
2049
+ ];
2050
+ /**
2051
+ * The actions that publish an issued grant: the rosters, then the id of
2052
+ * the grant, last so a consumer reacting to it finds the record in state.
2053
+ */
2054
+ const publishIssuedGrant = (actions) => ({ grantId, ...roster }) => [...publishGrantRoster(actions)(roster), actions.passport.setLastIssuedGrantId(grantId)];
2055
+ /**
2056
+ * The actions that publish an account a flow left ready: the account,
2057
+ * its device roster and counter, the `enc_key` its encryption key is
2058
+ * at, then its grant roster.
2059
+ */
2060
+ const publishAccountState = (actions) => ({ account, grants, encKey, ...roster }) => [
2061
+ actions.passport.setAccount(account),
2062
+ ...publishDeviceRoster(actions)(roster),
2063
+ actions.passport.setEncKey(encKey),
2064
+ actions.passport.setGrants(grants)
2065
+ ];
2066
+ /**
2067
+ * The actions that publish an account a sign-in recognised: the account
2068
+ * state, see {@link publishAccountState}, then the read grants whose kept
2069
+ * seal does not hold the live viewing secret, and no grants pending a
2070
+ * revocation, which only a grant call learns from the signer.
2071
+ */
2072
+ const publishRecognisedAccount = (actions) => ({ unsealedGrantIds, ...state }) => [
2073
+ ...publishAccountState(actions)(state),
2074
+ actions.passport.setUnsealedGrantIds(unsealedGrantIds),
2075
+ actions.passport.setRevocationPendingGrantIds([])
2076
+ ];
2077
+ /**
2078
+ * A device or grant call may rotate the account keys before it fails, so
2079
+ * the `enc_key` state holds may be the one rotated away: it is forgotten
2080
+ * until the next sign-in or rotation publishes it again, rather than let
2081
+ * a stale seal pass as current. The read grants an earlier call reported
2082
+ * unsealed or pending revocation were judged against that key, so they
2083
+ * are emptied with it.
2084
+ */
2085
+ const forgetKeyState = (actions) => [
2086
+ actions.passport.clearEncKey(),
2087
+ actions.passport.setUnsealedGrantIds([]),
2088
+ actions.passport.setRevocationPendingGrantIds([])
2089
+ ];
2090
+ /**
2091
+ * The createAccount flow behind the createAccount action, as the actions it
2092
+ * publishes: each reported stage as setFlow, then the created account, its
2093
+ * one-device roster, this device's counter and its empty grant roster,
2094
+ * then 'ready'. The flow itself persists the account record before it
2095
+ * resolves, so the account is never published ahead of the record that
2096
+ * recognises it.
2097
+ */
2098
+ const runCreateAccount = (input, dependencies) => {
2099
+ const { actions, passportFlows } = dependencies;
2100
+ return flowActions(passportFlows.createAccount(input, dependencies), {
2101
+ actions,
2102
+ fallbackCode: CREATE_ACCOUNT_FALLBACK_ERROR_CODE,
2103
+ onDone: publishAccountState(actions)
2104
+ });
2105
+ };
2106
+ /**
2107
+ * The signIn flow behind the signIn action, as the actions it publishes:
2108
+ * the recognised account, its whole device roster, this device's healed
2109
+ * use counter, the grant roster and its unsealed read grants, then
2110
+ * 'ready'. The flow itself persists the healed counter before it
2111
+ * resolves, so the record is never left behind the state it publishes.
2112
+ */
2113
+ const runSignIn = (dependencies) => {
2114
+ const { actions, passportFlows } = dependencies;
2115
+ return flowActions(passportFlows.signIn(dependencies), {
2116
+ actions,
2117
+ fallbackCode: SIGN_IN_FALLBACK_ERROR_CODE,
2118
+ onDone: publishRecognisedAccount(actions)
2119
+ });
2120
+ };
2121
+ /**
2122
+ * The actions that publish what a device call left behind: its roster and
2123
+ * counter, then, when it rotated the account keys, the `enc_key` it
2124
+ * stepped to and the devices it left without the new key.
2125
+ */
2126
+ const publishDeviceCall = (actions) => ({ encKey, droppedDevices, ...roster }) => [
2127
+ ...publishDeviceRoster(actions)(roster),
2128
+ ...encKey === void 0 ? [] : [actions.passport.setEncKey(encKey)],
2129
+ ...droppedDevices === void 0 ? [] : [actions.passport.setDroppedDevices(droppedDevices)]
2130
+ ];
2131
+ /**
2132
+ * The addDevice flow behind its action: what the call left behind, see
2133
+ * {@link publishDeviceCall}, then 'ready'. The joining device's keys
2134
+ * travel with the request when the action names them.
2135
+ */
2136
+ const addDeviceCall = {
2137
+ start: ({ commitmentHex, reader }, input, dependencies) => dependencies.passportFlows.addDevice({
2138
+ commitmentHex,
2139
+ ...reader ? { reader } : {},
2140
+ ...input
2141
+ }, dependencies),
2142
+ onDone: publishDeviceCall,
2143
+ fallbackCode: ADD_DEVICE_FALLBACK_ERROR_CODE,
2144
+ onFailed: forgetKeyState
2145
+ };
2146
+ /**
2147
+ * The removeDevice flow behind its action: what the call left behind, see
2148
+ * {@link publishDeviceCall}, then 'ready'.
2149
+ */
2150
+ const removeDeviceCall = {
2151
+ start: ({ commitmentHex }, input, dependencies) => dependencies.passportFlows.removeDevice({
2152
+ commitmentHex,
2153
+ ...input
2154
+ }, dependencies),
2155
+ onDone: publishDeviceCall,
2156
+ fallbackCode: REMOVE_DEVICE_FALLBACK_ERROR_CODE,
2157
+ onFailed: forgetKeyState
2158
+ };
2159
+ /**
2160
+ * The issueGrant flow behind its action: the rosters the call left
2161
+ * behind and the issued grant's id, then 'ready'.
2162
+ */
2163
+ const issueGrantCall = {
2164
+ start: (scope, input, dependencies) => dependencies.passportFlows.issueGrant({
2165
+ scope,
2166
+ ...input
2167
+ }, dependencies),
2168
+ onDone: publishIssuedGrant,
2169
+ fallbackCode: ISSUE_GRANT_FALLBACK_ERROR_CODE,
2170
+ onFailed: forgetKeyState
2171
+ };
2172
+ /** The revokeGrant flow behind its action: the rosters it left behind, then 'ready'. */
2173
+ const revokeGrantCall = {
2174
+ start: ({ grantId }, input, dependencies) => dependencies.passportFlows.revokeGrant({
2175
+ grantId,
2176
+ ...input
2177
+ }, dependencies),
2178
+ onDone: publishGrantRoster,
2179
+ fallbackCode: REVOKE_GRANT_FALLBACK_ERROR_CODE,
2180
+ onFailed: forgetKeyState
2181
+ };
2182
+ /** The revokeAllGrants flow behind its action: the rosters it left behind, then 'ready'. */
2183
+ const revokeAllGrantsCall = {
2184
+ start: (_payload, input, dependencies) => dependencies.passportFlows.revokeAllGrants(input, dependencies),
2185
+ onDone: publishGrantRoster,
2186
+ fallbackCode: REVOKE_ALL_GRANTS_FALLBACK_ERROR_CODE,
2187
+ onFailed: forgetKeyState
2188
+ };
2189
+ /**
2190
+ * The readShieldedHoldings flow behind its action: the holdings the walk
2191
+ * verified, then 'ready'. The skip count is the flow's to report, not the
2192
+ * store's to keep: a skipped entry is noise the inbox will carry forever.
2193
+ */
2194
+ const readShieldedHoldingsCall = {
2195
+ start: (_payload, input, dependencies) => dependencies.passportFlows.readShieldedHoldings(input, dependencies),
2196
+ onDone: (actions) => ({ holdings }) => [actions.passport.setShieldedHoldings(holdings)],
2197
+ fallbackCode: READ_HOLDINGS_FALLBACK_ERROR_CODE
2198
+ };
2199
+ /**
2200
+ * An account call behind its action, as the actions it publishes: what
2201
+ * the call left behind, then 'ready', or, when it fails, the actions
2202
+ * undoing what state may wrongly claim, then the error. The flow itself
2203
+ * persists the counter before it resolves.
2204
+ *
2205
+ * Requires an account and a settled flow: with no account in state, or
2206
+ * with an operation left unfinished, the flow is never called (so the
2207
+ * chain and the key session are never touched) and the call settles in
2208
+ * 'error' with 'no-account'. The refusal still runs behind the gate, so
2209
+ * it enters 'ceremony' first like any accepted request and clears the
2210
+ * error a previous flow left behind.
2211
+ */
2212
+ const runAccountCall = ({ start, onDone, fallbackCode, onFailed }, { payload, flow, account, devices, localUseCounter }, dependencies) => {
2213
+ const { actions } = dependencies;
2214
+ if (!account || !SETTLED_FLOWS.includes(flow)) return of(actions.passport.setFlowError(toFlowError(new NoAccountError(), fallbackCode)));
2215
+ return flowActions(start(payload, {
2216
+ account,
2217
+ devices,
2218
+ localUseCounter
2219
+ }, dependencies), {
2220
+ actions,
2221
+ fallbackCode,
2222
+ onDone: onDone(actions),
2223
+ ...onFailed ? { onFailed: onFailed(actions) } : {}
2224
+ });
2225
+ };
2226
+ /**
2227
+ * One start per dispatched account call action, each carrying the state
2228
+ * the call needs. State is sampled when the action arrives rather than
2229
+ * when the shared gate starts the call, so a call reads the account,
2230
+ * roster and counter the user acted on.
2231
+ */
2232
+ const accountCallStarts = ({ config, trigger$, stateObservables: { passport: { selectAccount$, selectDevices$, selectFlow$, selectLocalUseCounter$ } }, dependencies }) => trigger$.pipe(withLatestFrom(selectFlow$, selectAccount$, selectDevices$, selectLocalUseCounter$), map(([{ payload }, flow, account, devices, localUseCounter]) => () => runAccountCall(config, {
2233
+ payload,
2234
+ flow,
2235
+ account,
2236
+ devices,
2237
+ localUseCounter
2238
+ }, dependencies)));
2239
+ /**
2240
+ * Runs whichever passport flow was requested: {@link runCreateAccount}
2241
+ * behind the createAccount action, {@link runSignIn} behind signIn, and
2242
+ * {@link runAccountCall} behind addDevice, removeDevice, issueGrant,
2243
+ * revokeGrant, revokeAllGrants and readShieldedHoldings.
2244
+ *
2245
+ * Every flow owns the same account record, the same device key and the
2246
+ * same flow state, and each chain call resolves the account's use counter
2247
+ * and auth nonce, so they must never overlap: two calls resolving the same
2248
+ * counter both spend the same entry, one transaction is rejected, and the
2249
+ * loser reports its own failure over state the winner already moved on.
2250
+ * A holdings read spends no entry, but it opens the same key session and
2251
+ * reports through the same flow state, so it runs behind the same gate.
2252
+ * One exhaustMap over the merged starts, rather than one per action,
2253
+ * makes them mutually exclusive: a dispatch arriving while any flow is in
2254
+ * flight emits nothing at all, and the next one is only accepted once the
2255
+ * running flow settles. Dropping it silently is the stance exhaustMap
2256
+ * already took for a repeated dispatch of one action; a flow the user
2257
+ * never saw start is not a failure to report.
2258
+ *
2259
+ * Because only the gate knows which requests it accepted, it also owns
2260
+ * the entry into 'ceremony': the trigger reducers are pure, so a dropped
2261
+ * request cannot overwrite the running flow's state, and setFlow clears
2262
+ * the error a previous flow left behind. The emission is synchronous on
2263
+ * the accepted start, so a consumer sees the ceremony within the same
2264
+ * dispatch cycle as the trigger.
2265
+ *
2266
+ * The flow state cannot carry this guard the other way round: state
2267
+ * reaches a side effect before the action does, and an inner flow is
2268
+ * still subscribed while its own terminal setFlow('ready') is delivered,
2269
+ * so a reducer reading the flow back cannot tell a request the gate will
2270
+ * accept from one it will drop.
2271
+ */
2272
+ const runPassportFlow = ({ passport: { addDevice$, createAccount$, issueGrant$, readShieldedHoldings$, removeDevice$, revokeAllGrants$, revokeGrant$, signIn$ } }, stateObservables, dependencies) => merge(createAccount$.pipe(map(({ payload }) => () => runCreateAccount(payload, dependencies))), signIn$.pipe(map(() => () => runSignIn(dependencies))), accountCallStarts({
2273
+ config: addDeviceCall,
2274
+ trigger$: addDevice$,
2275
+ stateObservables,
2276
+ dependencies
2277
+ }), accountCallStarts({
2278
+ config: removeDeviceCall,
2279
+ trigger$: removeDevice$,
2280
+ stateObservables,
2281
+ dependencies
2282
+ }), accountCallStarts({
2283
+ config: issueGrantCall,
2284
+ trigger$: issueGrant$,
2285
+ stateObservables,
2286
+ dependencies
2287
+ }), accountCallStarts({
2288
+ config: revokeGrantCall,
2289
+ trigger$: revokeGrant$,
2290
+ stateObservables,
2291
+ dependencies
2292
+ }), accountCallStarts({
2293
+ config: revokeAllGrantsCall,
2294
+ trigger$: revokeAllGrants$,
2295
+ stateObservables,
2296
+ dependencies
2297
+ }), accountCallStarts({
2298
+ config: readShieldedHoldingsCall,
2299
+ trigger$: readShieldedHoldings$,
2300
+ stateObservables,
2301
+ dependencies
2302
+ })).pipe(exhaustMap((start) => concat(of(dependencies.actions.passport.setFlow("ceremony")), start())));
2303
+ //#endregion
2304
+ //#region ../../packages/module/passport-account/src/store/side-effects/restore-account.ts
2305
+ /** Whether a stored record names an activated account rather than a deployment in progress. */
2306
+ const isActivatedRecord = (record) => record !== void 0 && record.deployment === void 0;
2307
+ /**
2308
+ * The grant roster of the restored account, read from chain with the
2309
+ * slots the record kept. The read needs no ceremony, but it does need
2310
+ * the indexer: a contract it does not serve, or a read it fails, leaves
2311
+ * the roster empty rather than failing the restore, and the next sign-in
2312
+ * or grant call reads it again.
2313
+ */
2314
+ const restoreGrants = (record, { indexerUrl, indexerWsUrl }, actions) => defer(async () => createIndexerClient({
2315
+ indexerUrl,
2316
+ indexerWsUrl
2317
+ }).queryContractState(record.address)).pipe(filter((state) => state !== null && state !== void 0), map((state) => actions.passport.setGrants(readGrants(ledger(state.data), storedGrantSlots(record.grants)))), catchError(() => EMPTY));
2318
+ /**
2319
+ * Rehydrates the persisted account record into the store on startup, so a
2320
+ * returning user's account is recognised without a new passkey ceremony.
2321
+ * Only a record whose activation succeeded is restored, hence status
2322
+ * 'ready'; a record still carrying a pending deployment names no usable
2323
+ * account and is left for the create flow to resume, or for sign-in to
2324
+ * complete once the chain shows it activated. The device set is not
2325
+ * persisted and is rebuilt from chain by the device-management flows; the
2326
+ * grant roster is read from chain here, once the account is published,
2327
+ * so a consumer lists grants without a ceremony. A record sealed by a
2328
+ * passkey authoriser cannot be opened without a ceremony, and a ceremony
2329
+ * cannot run without a user gesture, so no storage key is passed here: a
2330
+ * sealed record reads as absent and the sign-in flow recognises the
2331
+ * account instead.
2332
+ */
2333
+ const restorePassportAccount = (_actionObservables, _stateObservables, { actions, createKeyValueStorage, passportNetwork }) => createAccountRecordStore(createKeyValueStorage).read().pipe(filter(isActivatedRecord), mergeMap((record) => concat(of(actions.passport.setAccount({
2334
+ address: record.address,
2335
+ bindingVersion: record.bindingVersion,
2336
+ status: "ready",
2337
+ networkId: passportNetwork.networkId
2338
+ }), actions.passport.setLocalUseCounter(record.localUseCounter)), restoreGrants(record, passportNetwork, actions))));
2339
+ //#endregion
2340
+ //#region ../../packages/module/passport-account/src/store/init.ts
2341
+ /**
2342
+ * Builds the passport-account module's store initializer for the given
2343
+ * platform capabilities: the mutually exclusive passport flows behind the
2344
+ * createAccount, signIn, addDevice, removeDevice, issueGrant, revokeGrant
2345
+ * and revokeAllGrants actions, and the restore of a previously created
2346
+ * account from its persisted record.
2347
+ */
2348
+ const createInit = (props) => () => ({
2349
+ sideEffects: [runPassportFlow, restorePassportAccount],
2350
+ sideEffectDependencies: createDependencies(props)
2351
+ });
2352
+ //#endregion
2353
+ export { createInit };