@tatchi-xyz/sdk 0.19.0 → 0.21.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 (243) hide show
  1. package/dist/cjs/core/EmailRecovery/index.js +25 -0
  2. package/dist/cjs/core/EmailRecovery/index.js.map +1 -1
  3. package/dist/cjs/core/TatchiPasskey/emailRecovery.js +135 -77
  4. package/dist/cjs/core/TatchiPasskey/emailRecovery.js.map +1 -1
  5. package/dist/cjs/core/TatchiPasskey/index.js +2 -1
  6. package/dist/cjs/core/TatchiPasskey/index.js.map +1 -1
  7. package/dist/cjs/core/TatchiPasskey/linkDevice.js +2 -1
  8. package/dist/cjs/core/TatchiPasskey/linkDevice.js.map +1 -1
  9. package/dist/cjs/core/TatchiPasskey/scanDevice.js +5 -3
  10. package/dist/cjs/core/TatchiPasskey/scanDevice.js.map +1 -1
  11. package/dist/cjs/core/WalletIframe/client/router.js +1 -1
  12. package/dist/cjs/core/WalletIframe/client/router.js.map +1 -1
  13. package/dist/cjs/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js +3 -4
  14. package/dist/cjs/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js.map +1 -1
  15. package/dist/cjs/core/defaultConfigs.js +3 -7
  16. package/dist/cjs/core/defaultConfigs.js.map +1 -1
  17. package/dist/cjs/core/nearCrypto.js +29 -5
  18. package/dist/cjs/core/nearCrypto.js.map +1 -1
  19. package/dist/cjs/core/rpcCalls.js +56 -26
  20. package/dist/cjs/core/rpcCalls.js.map +1 -1
  21. package/dist/cjs/core/types/emailRecovery.js +33 -0
  22. package/dist/cjs/core/types/emailRecovery.js.map +1 -0
  23. package/dist/cjs/index.js +4 -0
  24. package/dist/cjs/index.js.map +1 -1
  25. package/dist/cjs/react/components/AccountMenuButton/{LinkedDevicesModal-CSSowiHP.css → LinkedDevicesModal-BRtht0XI.css} +1 -1
  26. package/dist/{esm/react/components/AccountMenuButton/LinkedDevicesModal-CSSowiHP.css.map → cjs/react/components/AccountMenuButton/LinkedDevicesModal-BRtht0XI.css.map} +1 -1
  27. package/dist/cjs/react/components/AccountMenuButton/{ProfileDropdown-CEPMZ1gY.css → ProfileDropdown-BG_6hcim.css} +1 -1
  28. package/dist/{esm/react/components/AccountMenuButton/ProfileDropdown-CEPMZ1gY.css.map → cjs/react/components/AccountMenuButton/ProfileDropdown-BG_6hcim.css.map} +1 -1
  29. package/dist/cjs/react/components/AccountMenuButton/{Web3AuthProfileButton-DopOg7Xc.css → Web3AuthProfileButton-k8_FAYFq.css} +1 -1
  30. package/dist/cjs/react/components/AccountMenuButton/{Web3AuthProfileButton-DopOg7Xc.css.map → Web3AuthProfileButton-k8_FAYFq.css.map} +1 -1
  31. package/dist/cjs/react/components/AccountMenuButton/icons/{TouchIcon-BQWentvJ.css → TouchIcon-C-RcGfr5.css} +1 -1
  32. package/dist/cjs/react/components/AccountMenuButton/icons/{TouchIcon-BQWentvJ.css.map → TouchIcon-C-RcGfr5.css.map} +1 -1
  33. package/dist/cjs/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-DwrzWMYx.css → PasskeyAuthMenu-DKMiLeT9.css} +59 -4
  34. package/dist/cjs/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-DwrzWMYx.css.map → PasskeyAuthMenu-DKMiLeT9.css.map} +1 -1
  35. package/dist/cjs/react/components/PasskeyAuthMenu/adapters/tatchi.js +1 -0
  36. package/dist/cjs/react/components/PasskeyAuthMenu/adapters/tatchi.js.map +1 -1
  37. package/dist/cjs/react/components/PasskeyAuthMenu/client.js +30 -8
  38. package/dist/cjs/react/components/PasskeyAuthMenu/client.js.map +1 -1
  39. package/dist/cjs/react/components/PasskeyAuthMenu/controller/useSDKEvents.js +22 -0
  40. package/dist/cjs/react/components/PasskeyAuthMenu/controller/useSDKEvents.js.map +1 -0
  41. package/dist/cjs/react/components/PasskeyAuthMenu/ui/ContentSwitcher.js +17 -4
  42. package/dist/cjs/react/components/PasskeyAuthMenu/ui/ContentSwitcher.js.map +1 -1
  43. package/dist/cjs/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js +354 -154
  44. package/dist/cjs/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js.map +1 -1
  45. package/dist/cjs/react/components/{ShowQRCode-CCN4h6Uv.css → ShowQRCode-CB0UCQ_h.css} +1 -1
  46. package/dist/cjs/react/components/{ShowQRCode-CCN4h6Uv.css.map → ShowQRCode-CB0UCQ_h.css.map} +1 -1
  47. package/dist/cjs/react/context/useSDKFlowRuntime.js +183 -0
  48. package/dist/cjs/react/context/useSDKFlowRuntime.js.map +1 -0
  49. package/dist/cjs/react/context/useTatchiContextValue.js +24 -15
  50. package/dist/cjs/react/context/useTatchiContextValue.js.map +1 -1
  51. package/dist/cjs/react/context/useTatchiWithSdkFlow.js +96 -0
  52. package/dist/cjs/react/context/useTatchiWithSdkFlow.js.map +1 -0
  53. package/dist/cjs/react/sdk/src/core/EmailRecovery/index.js +26 -0
  54. package/dist/cjs/react/sdk/src/core/EmailRecovery/index.js.map +1 -1
  55. package/dist/cjs/react/sdk/src/core/TatchiPasskey/emailRecovery.js +135 -77
  56. package/dist/cjs/react/sdk/src/core/TatchiPasskey/emailRecovery.js.map +1 -1
  57. package/dist/cjs/react/sdk/src/core/TatchiPasskey/index.js +2 -1
  58. package/dist/cjs/react/sdk/src/core/TatchiPasskey/index.js.map +1 -1
  59. package/dist/cjs/react/sdk/src/core/TatchiPasskey/linkDevice.js +2 -1
  60. package/dist/cjs/react/sdk/src/core/TatchiPasskey/linkDevice.js.map +1 -1
  61. package/dist/cjs/react/sdk/src/core/TatchiPasskey/scanDevice.js +5 -3
  62. package/dist/cjs/react/sdk/src/core/TatchiPasskey/scanDevice.js.map +1 -1
  63. package/dist/cjs/react/sdk/src/core/WalletIframe/client/router.js +1 -1
  64. package/dist/cjs/react/sdk/src/core/WalletIframe/client/router.js.map +1 -1
  65. package/dist/cjs/react/sdk/src/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js +3 -4
  66. package/dist/cjs/react/sdk/src/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js.map +1 -1
  67. package/dist/cjs/react/sdk/src/core/defaultConfigs.js +3 -7
  68. package/dist/cjs/react/sdk/src/core/defaultConfigs.js.map +1 -1
  69. package/dist/cjs/react/sdk/src/core/nearCrypto.js +29 -5
  70. package/dist/cjs/react/sdk/src/core/nearCrypto.js.map +1 -1
  71. package/dist/cjs/react/sdk/src/core/rpcCalls.js +56 -26
  72. package/dist/cjs/react/sdk/src/core/rpcCalls.js.map +1 -1
  73. package/dist/cjs/react/sdk/src/core/types/emailRecovery.js +33 -0
  74. package/dist/cjs/react/sdk/src/core/types/emailRecovery.js.map +1 -0
  75. package/dist/cjs/server/email-recovery/emailParsers.js +2 -1
  76. package/dist/cjs/server/email-recovery/emailParsers.js.map +1 -1
  77. package/dist/cjs/server/email-recovery/index.js +6 -6
  78. package/dist/cjs/server/email-recovery/index.js.map +1 -1
  79. package/dist/cjs/server/email-recovery/rpcCalls.js +22 -3
  80. package/dist/cjs/server/email-recovery/rpcCalls.js.map +1 -1
  81. package/dist/cjs/server/router/cloudflare.js +8 -3
  82. package/dist/cjs/server/router/cloudflare.js.map +1 -1
  83. package/dist/cjs/server/router/express.js.map +1 -1
  84. package/dist/cjs/server/sdk/src/core/defaultConfigs.js +2 -4
  85. package/dist/cjs/server/sdk/src/core/defaultConfigs.js.map +1 -1
  86. package/dist/cjs/server/sdk/src/core/nearCrypto.js +26 -7
  87. package/dist/cjs/server/sdk/src/core/nearCrypto.js.map +1 -1
  88. package/dist/esm/core/EmailRecovery/index.js +25 -1
  89. package/dist/esm/core/EmailRecovery/index.js.map +1 -1
  90. package/dist/esm/core/TatchiPasskey/emailRecovery.js +136 -78
  91. package/dist/esm/core/TatchiPasskey/emailRecovery.js.map +1 -1
  92. package/dist/esm/core/TatchiPasskey/index.js +2 -1
  93. package/dist/esm/core/TatchiPasskey/index.js.map +1 -1
  94. package/dist/esm/core/TatchiPasskey/linkDevice.js +2 -1
  95. package/dist/esm/core/TatchiPasskey/linkDevice.js.map +1 -1
  96. package/dist/esm/core/TatchiPasskey/scanDevice.js +5 -3
  97. package/dist/esm/core/TatchiPasskey/scanDevice.js.map +1 -1
  98. package/dist/esm/core/WalletIframe/client/router.js +1 -1
  99. package/dist/esm/core/WalletIframe/client/router.js.map +1 -1
  100. package/dist/esm/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js +2 -3
  101. package/dist/esm/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js.map +1 -1
  102. package/dist/esm/core/defaultConfigs.js +3 -7
  103. package/dist/esm/core/defaultConfigs.js.map +1 -1
  104. package/dist/esm/core/nearCrypto.js +24 -6
  105. package/dist/esm/core/nearCrypto.js.map +1 -1
  106. package/dist/esm/core/rpcCalls.js +56 -26
  107. package/dist/esm/core/rpcCalls.js.map +1 -1
  108. package/dist/esm/core/types/emailRecovery.js +26 -0
  109. package/dist/esm/core/types/emailRecovery.js.map +1 -0
  110. package/dist/esm/index.js +3 -1
  111. package/dist/esm/index.js.map +1 -1
  112. package/dist/esm/react/components/AccountMenuButton/{LinkedDevicesModal-CSSowiHP.css → LinkedDevicesModal-BRtht0XI.css} +1 -1
  113. package/dist/{cjs/react/components/AccountMenuButton/LinkedDevicesModal-CSSowiHP.css.map → esm/react/components/AccountMenuButton/LinkedDevicesModal-BRtht0XI.css.map} +1 -1
  114. package/dist/esm/react/components/AccountMenuButton/{ProfileDropdown-CEPMZ1gY.css → ProfileDropdown-BG_6hcim.css} +1 -1
  115. package/dist/{cjs/react/components/AccountMenuButton/ProfileDropdown-CEPMZ1gY.css.map → esm/react/components/AccountMenuButton/ProfileDropdown-BG_6hcim.css.map} +1 -1
  116. package/dist/esm/react/components/AccountMenuButton/{Web3AuthProfileButton-DopOg7Xc.css → Web3AuthProfileButton-k8_FAYFq.css} +1 -1
  117. package/dist/esm/react/components/AccountMenuButton/{Web3AuthProfileButton-DopOg7Xc.css.map → Web3AuthProfileButton-k8_FAYFq.css.map} +1 -1
  118. package/dist/esm/react/components/AccountMenuButton/icons/{TouchIcon-BQWentvJ.css → TouchIcon-C-RcGfr5.css} +1 -1
  119. package/dist/esm/react/components/AccountMenuButton/icons/{TouchIcon-BQWentvJ.css.map → TouchIcon-C-RcGfr5.css.map} +1 -1
  120. package/dist/esm/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-DwrzWMYx.css → PasskeyAuthMenu-DKMiLeT9.css} +59 -4
  121. package/dist/esm/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-DwrzWMYx.css.map → PasskeyAuthMenu-DKMiLeT9.css.map} +1 -1
  122. package/dist/esm/react/components/PasskeyAuthMenu/adapters/tatchi.js +1 -0
  123. package/dist/esm/react/components/PasskeyAuthMenu/adapters/tatchi.js.map +1 -1
  124. package/dist/esm/react/components/PasskeyAuthMenu/client.js +30 -8
  125. package/dist/esm/react/components/PasskeyAuthMenu/client.js.map +1 -1
  126. package/dist/esm/react/components/PasskeyAuthMenu/controller/useSDKEvents.js +20 -0
  127. package/dist/esm/react/components/PasskeyAuthMenu/controller/useSDKEvents.js.map +1 -0
  128. package/dist/esm/react/components/PasskeyAuthMenu/ui/ContentSwitcher.js +17 -4
  129. package/dist/esm/react/components/PasskeyAuthMenu/ui/ContentSwitcher.js.map +1 -1
  130. package/dist/esm/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js +354 -154
  131. package/dist/esm/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js.map +1 -1
  132. package/dist/esm/react/components/{ShowQRCode-CCN4h6Uv.css → ShowQRCode-CB0UCQ_h.css} +1 -1
  133. package/dist/esm/react/components/{ShowQRCode-CCN4h6Uv.css.map → ShowQRCode-CB0UCQ_h.css.map} +1 -1
  134. package/dist/esm/react/context/useSDKFlowRuntime.js +181 -0
  135. package/dist/esm/react/context/useSDKFlowRuntime.js.map +1 -0
  136. package/dist/esm/react/context/useTatchiContextValue.js +25 -16
  137. package/dist/esm/react/context/useTatchiContextValue.js.map +1 -1
  138. package/dist/esm/react/context/useTatchiWithSdkFlow.js +94 -0
  139. package/dist/esm/react/context/useTatchiWithSdkFlow.js.map +1 -0
  140. package/dist/esm/react/sdk/src/core/EmailRecovery/index.js +25 -1
  141. package/dist/esm/react/sdk/src/core/EmailRecovery/index.js.map +1 -1
  142. package/dist/esm/react/sdk/src/core/TatchiPasskey/emailRecovery.js +136 -78
  143. package/dist/esm/react/sdk/src/core/TatchiPasskey/emailRecovery.js.map +1 -1
  144. package/dist/esm/react/sdk/src/core/TatchiPasskey/index.js +2 -1
  145. package/dist/esm/react/sdk/src/core/TatchiPasskey/index.js.map +1 -1
  146. package/dist/esm/react/sdk/src/core/TatchiPasskey/linkDevice.js +2 -1
  147. package/dist/esm/react/sdk/src/core/TatchiPasskey/linkDevice.js.map +1 -1
  148. package/dist/esm/react/sdk/src/core/TatchiPasskey/scanDevice.js +5 -3
  149. package/dist/esm/react/sdk/src/core/TatchiPasskey/scanDevice.js.map +1 -1
  150. package/dist/esm/react/sdk/src/core/WalletIframe/client/router.js +1 -1
  151. package/dist/esm/react/sdk/src/core/WalletIframe/client/router.js.map +1 -1
  152. package/dist/esm/react/sdk/src/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js +2 -3
  153. package/dist/esm/react/sdk/src/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js.map +1 -1
  154. package/dist/esm/react/sdk/src/core/defaultConfigs.js +3 -7
  155. package/dist/esm/react/sdk/src/core/defaultConfigs.js.map +1 -1
  156. package/dist/esm/react/sdk/src/core/nearCrypto.js +24 -6
  157. package/dist/esm/react/sdk/src/core/nearCrypto.js.map +1 -1
  158. package/dist/esm/react/sdk/src/core/rpcCalls.js +56 -26
  159. package/dist/esm/react/sdk/src/core/rpcCalls.js.map +1 -1
  160. package/dist/esm/react/sdk/src/core/types/emailRecovery.js +26 -0
  161. package/dist/esm/react/sdk/src/core/types/emailRecovery.js.map +1 -0
  162. package/dist/esm/react/styles/styles.css +58 -3
  163. package/dist/esm/sdk/{defaultConfigs-DpslkAQd.js → defaultConfigs-CfQDV-ya.js} +3 -7
  164. package/dist/esm/sdk/{getDeviceNumber-fXizNGQl.js → getDeviceNumber-BpernPnM.js} +4 -8
  165. package/dist/esm/sdk/getDeviceNumber-BpernPnM.js.map +1 -0
  166. package/dist/esm/sdk/offline-export-app.js +23 -6
  167. package/dist/esm/sdk/offline-export-app.js.map +1 -1
  168. package/dist/esm/sdk/{router-DuGYOd3G.js → router-BWtacLJg.js} +1 -1
  169. package/dist/esm/sdk/{rpcCalls-BQrJMTdg.js → rpcCalls-CYGJSCgm.js} +3 -3
  170. package/dist/esm/sdk/{rpcCalls-YVeUVMk2.js → rpcCalls-DZZSa-sk.js} +57 -27
  171. package/dist/esm/sdk/{transactions-bqaAwL4k.js → transactions-Cn9xTWlK.js} +2 -2
  172. package/dist/esm/sdk/{transactions-bqaAwL4k.js.map → transactions-Cn9xTWlK.js.map} +1 -1
  173. package/dist/esm/sdk/{transactions-BalIhtJ9.js → transactions-DfdwDQCn.js} +1 -1
  174. package/dist/esm/sdk/wallet-iframe-host.js +660 -590
  175. package/dist/esm/server/email-recovery/emailParsers.js +3 -1
  176. package/dist/esm/server/email-recovery/emailParsers.js.map +1 -1
  177. package/dist/esm/server/email-recovery/index.js +6 -6
  178. package/dist/esm/server/email-recovery/index.js.map +1 -1
  179. package/dist/esm/server/email-recovery/rpcCalls.js +22 -3
  180. package/dist/esm/server/email-recovery/rpcCalls.js.map +1 -1
  181. package/dist/esm/server/router/cloudflare.js +8 -3
  182. package/dist/esm/server/router/cloudflare.js.map +1 -1
  183. package/dist/esm/server/router/express.js.map +1 -1
  184. package/dist/esm/server/sdk/src/core/defaultConfigs.js +2 -4
  185. package/dist/esm/server/sdk/src/core/defaultConfigs.js.map +1 -1
  186. package/dist/esm/server/sdk/src/core/nearCrypto.js +26 -8
  187. package/dist/esm/server/sdk/src/core/nearCrypto.js.map +1 -1
  188. package/dist/esm/wasm_vrf_worker/pkg/wasm_vrf_worker_bg.wasm +0 -0
  189. package/dist/types/src/core/EmailRecovery/index.d.ts +8 -0
  190. package/dist/types/src/core/EmailRecovery/index.d.ts.map +1 -1
  191. package/dist/types/src/core/TatchiPasskey/emailRecovery.d.ts +8 -5
  192. package/dist/types/src/core/TatchiPasskey/emailRecovery.d.ts.map +1 -1
  193. package/dist/types/src/core/TatchiPasskey/index.d.ts +1 -1
  194. package/dist/types/src/core/TatchiPasskey/index.d.ts.map +1 -1
  195. package/dist/types/src/core/TatchiPasskey/scanDevice.d.ts.map +1 -1
  196. package/dist/types/src/core/WalletIframe/TatchiPasskeyIframe.d.ts +1 -1
  197. package/dist/types/src/core/WalletIframe/TatchiPasskeyIframe.d.ts.map +1 -1
  198. package/dist/types/src/core/WalletIframe/client/router.d.ts +1 -1
  199. package/dist/types/src/core/WalletIframe/client/router.d.ts.map +1 -1
  200. package/dist/types/src/core/WalletIframe/shared/messages.d.ts +1 -1
  201. package/dist/types/src/core/WalletIframe/shared/messages.d.ts.map +1 -1
  202. package/dist/types/src/core/WebAuthnManager/SignerWorkerManager/handlers/validation.d.ts +2 -1
  203. package/dist/types/src/core/WebAuthnManager/SignerWorkerManager/handlers/validation.d.ts.map +1 -1
  204. package/dist/types/src/core/defaultConfigs.d.ts.map +1 -1
  205. package/dist/types/src/core/nearCrypto.d.ts +14 -0
  206. package/dist/types/src/core/nearCrypto.d.ts.map +1 -1
  207. package/dist/types/src/core/rpcCalls.d.ts +11 -8
  208. package/dist/types/src/core/rpcCalls.d.ts.map +1 -1
  209. package/dist/types/src/core/types/emailRecovery.d.ts +10 -0
  210. package/dist/types/src/core/types/emailRecovery.d.ts.map +1 -0
  211. package/dist/types/src/core/types/index.d.ts +1 -0
  212. package/dist/types/src/core/types/index.d.ts.map +1 -1
  213. package/dist/types/src/core/types/tatchi.d.ts +0 -4
  214. package/dist/types/src/core/types/tatchi.d.ts.map +1 -1
  215. package/dist/types/src/index.d.ts +1 -0
  216. package/dist/types/src/index.d.ts.map +1 -1
  217. package/dist/types/src/react/components/PasskeyAuthMenu/adapters/tatchi.d.ts +2 -0
  218. package/dist/types/src/react/components/PasskeyAuthMenu/adapters/tatchi.d.ts.map +1 -1
  219. package/dist/types/src/react/components/PasskeyAuthMenu/client.d.ts.map +1 -1
  220. package/dist/types/src/react/components/PasskeyAuthMenu/controller/useSDKEvents.d.ts +10 -0
  221. package/dist/types/src/react/components/PasskeyAuthMenu/controller/useSDKEvents.d.ts.map +1 -0
  222. package/dist/types/src/react/components/PasskeyAuthMenu/types.d.ts +8 -3
  223. package/dist/types/src/react/components/PasskeyAuthMenu/types.d.ts.map +1 -1
  224. package/dist/types/src/react/components/PasskeyAuthMenu/ui/ContentSwitcher.d.ts +2 -0
  225. package/dist/types/src/react/components/PasskeyAuthMenu/ui/ContentSwitcher.d.ts.map +1 -1
  226. package/dist/types/src/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.d.ts +1 -1
  227. package/dist/types/src/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.d.ts.map +1 -1
  228. package/dist/types/src/react/context/useSDKFlowRuntime.d.ts +10 -0
  229. package/dist/types/src/react/context/useSDKFlowRuntime.d.ts.map +1 -0
  230. package/dist/types/src/react/context/useTatchiContextValue.d.ts.map +1 -1
  231. package/dist/types/src/react/context/useTatchiWithSdkFlow.d.ts +9 -0
  232. package/dist/types/src/react/context/useTatchiWithSdkFlow.d.ts.map +1 -0
  233. package/dist/types/src/react/types.d.ts +31 -0
  234. package/dist/types/src/react/types.d.ts.map +1 -1
  235. package/dist/types/src/server/email-recovery/emailParsers.d.ts.map +1 -1
  236. package/dist/types/src/server/email-recovery/index.d.ts +5 -6
  237. package/dist/types/src/server/email-recovery/index.d.ts.map +1 -1
  238. package/dist/types/src/server/email-recovery/rpcCalls.d.ts +1 -0
  239. package/dist/types/src/server/email-recovery/rpcCalls.d.ts.map +1 -1
  240. package/dist/types/src/server/router/cloudflare-adaptor.d.ts.map +1 -1
  241. package/dist/workers/wasm_vrf_worker_bg.wasm +0 -0
  242. package/package.json +1 -1
  243. package/dist/esm/sdk/getDeviceNumber-fXizNGQl.js.map +0 -1
@@ -1,3 +1,5 @@
1
+ import { ensureEd25519Prefix } from "../sdk/src/core/nearCrypto.js";
2
+
1
3
  //#region src/server/email-recovery/emailParsers.ts
2
4
  let EmailRecoveryModeHint = /* @__PURE__ */ function(EmailRecoveryModeHint$1) {
3
5
  EmailRecoveryModeHint$1["ZkEmail"] = "zk-email";
@@ -81,7 +83,7 @@ function parseRecoverSubjectBindings(rawEmail) {
81
83
  return {
82
84
  requestId,
83
85
  accountId,
84
- newPublicKey
86
+ newPublicKey: ensureEd25519Prefix(newPublicKey)
85
87
  };
86
88
  }
87
89
 
@@ -1 +1 @@
1
- {"version":3,"file":"emailParsers.js","names":["headerLines: string[]"],"sources":["../../../../src/server/email-recovery/emailParsers.ts"],"sourcesContent":["import type { EmailRecoveryMode } from './types';\nimport { normalizeForwardableEmailPayload, parseAccountIdFromSubject } from './zkEmail';\n\nexport enum EmailRecoveryModeHint {\n ZkEmail = 'zk-email',\n TeeEncrypted = 'tee-encrypted',\n OnchainPublic = 'onchain-public',\n}\n\nexport function normalizeRecoveryMode(raw: string | undefined | null): EmailRecoveryMode | null {\n if (!raw) return null;\n const value = raw.trim().toLowerCase();\n if (value === EmailRecoveryModeHint.ZkEmail) return 'zk-email';\n if (value === EmailRecoveryModeHint.TeeEncrypted) return 'tee-encrypted';\n if (value === EmailRecoveryModeHint.OnchainPublic) return 'onchain-public';\n return null;\n}\n\nexport function extractRecoveryModeFromBody(emailBlob?: string): EmailRecoveryMode | null {\n if (!emailBlob) return null;\n\n const lines = emailBlob.split(/\\r?\\n/);\n const bodyStartIndex = lines.findIndex(line => line.trim() === '');\n if (bodyStartIndex === -1) return null;\n\n const bodyLines = lines.slice(bodyStartIndex + 1);\n const firstNonEmptyBodyLine = bodyLines.find(line => line.trim() !== '');\n if (!firstNonEmptyBodyLine) return null;\n\n const candidate = firstNonEmptyBodyLine.trim();\n const normalized = normalizeRecoveryMode(candidate);\n if (normalized) return normalized;\n\n const lower = candidate.toLowerCase();\n if (lower.includes(EmailRecoveryModeHint.ZkEmail)) return 'zk-email';\n if (lower.includes(EmailRecoveryModeHint.TeeEncrypted)) return 'tee-encrypted';\n if (lower.includes(EmailRecoveryModeHint.OnchainPublic)) return 'onchain-public';\n\n return null;\n}\n\ntype HeaderValue = string | string[] | undefined;\ntype HeadersLike = Headers | Record<string, HeaderValue> | undefined;\n\nexport type RecoverEmailParseResult =\n | { ok: true; accountId: string; emailBlob: string; explicitMode?: string }\n | { ok: false; status: number; code: string; message: string };\n\nfunction getHeader(headers: HeadersLike, name: string): string | undefined {\n if (!headers) return undefined;\n\n const maybeHeaders = headers as any;\n if (typeof maybeHeaders.get === 'function') {\n const v = maybeHeaders.get(name);\n return (typeof v === 'string') ? v : undefined;\n }\n\n const record = headers as Record<string, HeaderValue>;\n const v = record[name.toLowerCase()] ?? record[name];\n if (Array.isArray(v)) return (typeof v[0] === 'string') ? v[0] : undefined;\n return (typeof v === 'string') ? v : undefined;\n}\n\nfunction parseExplicitMode(body: unknown, headers?: HeadersLike): string | undefined {\n const modeFromBody =\n (typeof (body as any)?.explicitMode === 'string' ? String((body as any).explicitMode) : '') ||\n (typeof (body as any)?.explicit_mode === 'string' ? String((body as any).explicit_mode) : '');\n const modeFromHeader = getHeader(headers, 'x-email-recovery-mode') || getHeader(headers, 'x-recovery-mode') || '';\n const raw = (modeFromBody || modeFromHeader).trim();\n return raw ? raw : undefined;\n}\n\nexport function parseRecoverEmailRequest(body: unknown, opts: { headers?: HeadersLike } = {}): RecoverEmailParseResult {\n const explicitMode = parseExplicitMode(body, opts.headers);\n\n const normalized = normalizeForwardableEmailPayload(body);\n if (!normalized.ok) {\n return { ok: false, status: 400, code: normalized.code, message: normalized.message };\n }\n\n const payload = normalized.payload;\n const emailBlob = payload.raw || '';\n const emailHeaders = payload.headers || {};\n\n const subjectHeader = emailHeaders['subject'];\n const parsedAccountId = parseAccountIdFromSubject(subjectHeader || emailBlob);\n const headerAccountId = String(emailHeaders['x-near-account-id'] || emailHeaders['x-account-id'] || '').trim();\n const accountId = (parsedAccountId || headerAccountId || '').trim();\n\n if (!accountId) {\n return { ok: false, status: 400, code: 'missing_account', message: 'x-near-account-id header is required' };\n }\n if (!emailBlob) {\n return { ok: false, status: 400, code: 'missing_email', message: 'raw email blob is required' };\n }\n\n return { ok: true, accountId, emailBlob, explicitMode };\n}\n\nconst EMAIL_ADDRESS_REGEX =\n /([a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*)/;\n\nexport function canonicalizeEmail(input: string): string {\n const raw = String(input || '').trim();\n if (!raw) return '';\n\n // Handle cases where a full header line is passed in (e.g. \"From: ...\").\n const withoutHeaderName = raw.replace(/^[a-z0-9-]+\\s*:\\s*/i, '').trim();\n\n // Prefer the common \"Name <email@domain>\" format when present, but still\n // validate/extract the actual address via regex.\n const angleMatch = withoutHeaderName.match(/<([^>]+)>/);\n const candidates = [\n angleMatch?.[1],\n withoutHeaderName,\n ].filter((v): v is string => typeof v === 'string' && v.length > 0);\n\n for (const candidate of candidates) {\n const cleaned = candidate.replace(/^mailto:\\s*/i, '');\n const match = cleaned.match(EMAIL_ADDRESS_REGEX);\n if (match?.[1]) {\n return match[1].trim().toLowerCase();\n }\n }\n\n return withoutHeaderName.toLowerCase();\n}\n\nexport function parseHeaderValue(rawEmail: string, name: string): string | undefined {\n try {\n const raw = String(rawEmail || '');\n if (!raw) return undefined;\n\n const lines = raw.split(/\\r?\\n/);\n const headerLines: string[] = [];\n\n // Only consider the header section (until the first blank line).\n for (const line of lines) {\n if (line.trim() === '') break;\n\n // RFC822 header folding: lines starting with whitespace continue previous header.\n if (/^\\s/.test(line) && headerLines.length > 0) {\n headerLines[headerLines.length - 1] += ` ${line.trim()}`;\n continue;\n }\n\n headerLines.push(line);\n }\n\n const headerName = name.trim();\n if (!headerName) return undefined;\n\n const re = new RegExp(`^${headerName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\\\s*:`, 'i');\n const found = headerLines.find((l) => re.test(l));\n if (!found) return undefined;\n\n const idx = found.indexOf(':');\n const value = idx >= 0 ? found.slice(idx + 1).trim() : '';\n return value || undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function parseRecoverSubjectBindings(\n rawEmail: string\n): { requestId: string; accountId: string; newPublicKey: string } | null {\n // Accept either a full RFC822 email or a bare Subject value.\n let subjectText = (parseHeaderValue(rawEmail, 'subject') || String(rawEmail || '')).trim();\n if (!subjectText) return null;\n\n // Strip common reply/forward prefixes.\n subjectText = subjectText.replace(/^(re|fwd):\\s*/i, '').trim();\n if (!subjectText) return null;\n\n // Strict format:\n // \"recover-<request_id> <accountId> ed25519:<pk>\"\n const match = subjectText.match(\n /^recover-([A-Za-z0-9]{6})\\s+([^\\s]+)\\s+ed25519:([^\\s]+)\\s*$/i\n );\n if (!match) return null;\n\n const [, requestId, accountId, newPublicKey] = match;\n return { requestId, accountId, newPublicKey };\n}\n"],"mappings":";AAGA,IAAY,0EAAL;AACL;AACA;AACA;;;AAGF,SAAgB,sBAAsB,KAA0D;AAC9F,KAAI,CAAC,IAAK,QAAO;CACjB,MAAM,QAAQ,IAAI,OAAO;AACzB,KAAI,UAAU,sBAAsB,QAAS,QAAO;AACpD,KAAI,UAAU,sBAAsB,aAAc,QAAO;AACzD,KAAI,UAAU,sBAAsB,cAAe,QAAO;AAC1D,QAAO;;AAGT,SAAgB,4BAA4B,WAA8C;AACxF,KAAI,CAAC,UAAW,QAAO;CAEvB,MAAM,QAAQ,UAAU,MAAM;CAC9B,MAAM,iBAAiB,MAAM,WAAU,SAAQ,KAAK,WAAW;AAC/D,KAAI,mBAAmB,GAAI,QAAO;CAElC,MAAM,YAAY,MAAM,MAAM,iBAAiB;CAC/C,MAAM,wBAAwB,UAAU,MAAK,SAAQ,KAAK,WAAW;AACrE,KAAI,CAAC,sBAAuB,QAAO;CAEnC,MAAM,YAAY,sBAAsB;CACxC,MAAM,aAAa,sBAAsB;AACzC,KAAI,WAAY,QAAO;CAEvB,MAAM,QAAQ,UAAU;AACxB,KAAI,MAAM,SAAS,sBAAsB,SAAU,QAAO;AAC1D,KAAI,MAAM,SAAS,sBAAsB,cAAe,QAAO;AAC/D,KAAI,MAAM,SAAS,sBAAsB,eAAgB,QAAO;AAEhE,QAAO;;AA6DT,MAAM,sBACJ;AAEF,SAAgB,kBAAkB,OAAuB;CACvD,MAAM,MAAM,OAAO,SAAS,IAAI;AAChC,KAAI,CAAC,IAAK,QAAO;CAGjB,MAAM,oBAAoB,IAAI,QAAQ,uBAAuB,IAAI;CAIjE,MAAM,aAAa,kBAAkB,MAAM;CAC3C,MAAM,aAAa,CACjB,aAAa,IACb,mBACA,QAAQ,MAAmB,OAAO,MAAM,YAAY,EAAE,SAAS;AAEjE,MAAK,MAAM,aAAa,YAAY;EAClC,MAAM,UAAU,UAAU,QAAQ,gBAAgB;EAClD,MAAM,QAAQ,QAAQ,MAAM;AAC5B,MAAI,QAAQ,GACV,QAAO,MAAM,GAAG,OAAO;;AAI3B,QAAO,kBAAkB;;AAG3B,SAAgB,iBAAiB,UAAkB,MAAkC;AACnF,KAAI;EACF,MAAM,MAAM,OAAO,YAAY;AAC/B,MAAI,CAAC,IAAK,QAAO;EAEjB,MAAM,QAAQ,IAAI,MAAM;EACxB,MAAMA,cAAwB;AAG9B,OAAK,MAAM,QAAQ,OAAO;AACxB,OAAI,KAAK,WAAW,GAAI;AAGxB,OAAI,MAAM,KAAK,SAAS,YAAY,SAAS,GAAG;AAC9C,gBAAY,YAAY,SAAS,MAAM,IAAI,KAAK;AAChD;;AAGF,eAAY,KAAK;;EAGnB,MAAM,aAAa,KAAK;AACxB,MAAI,CAAC,WAAY,QAAO;EAExB,MAAM,KAAK,IAAI,OAAO,IAAI,WAAW,QAAQ,uBAAuB,QAAQ,QAAQ;EACpF,MAAM,QAAQ,YAAY,MAAM,MAAM,GAAG,KAAK;AAC9C,MAAI,CAAC,MAAO,QAAO;EAEnB,MAAM,MAAM,MAAM,QAAQ;EAC1B,MAAM,QAAQ,OAAO,IAAI,MAAM,MAAM,MAAM,GAAG,SAAS;AACvD,SAAO,SAAS;SACV;AACN,SAAO;;;AAIX,SAAgB,4BACd,UACuE;CAEvE,IAAI,eAAe,iBAAiB,UAAU,cAAc,OAAO,YAAY,KAAK;AACpF,KAAI,CAAC,YAAa,QAAO;AAGzB,eAAc,YAAY,QAAQ,kBAAkB,IAAI;AACxD,KAAI,CAAC,YAAa,QAAO;CAIzB,MAAM,QAAQ,YAAY,MACxB;AAEF,KAAI,CAAC,MAAO,QAAO;CAEnB,MAAM,GAAG,WAAW,WAAW,gBAAgB;AAC/C,QAAO;EAAE;EAAW;EAAW"}
1
+ {"version":3,"file":"emailParsers.js","names":["headerLines: string[]"],"sources":["../../../../src/server/email-recovery/emailParsers.ts"],"sourcesContent":["import type { EmailRecoveryMode } from './types';\nimport { normalizeForwardableEmailPayload, parseAccountIdFromSubject } from './zkEmail';\nimport { ensureEd25519Prefix } from '../../core/nearCrypto';\n\nexport enum EmailRecoveryModeHint {\n ZkEmail = 'zk-email',\n TeeEncrypted = 'tee-encrypted',\n OnchainPublic = 'onchain-public',\n}\n\nexport function normalizeRecoveryMode(raw: string | undefined | null): EmailRecoveryMode | null {\n if (!raw) return null;\n const value = raw.trim().toLowerCase();\n if (value === EmailRecoveryModeHint.ZkEmail) return 'zk-email';\n if (value === EmailRecoveryModeHint.TeeEncrypted) return 'tee-encrypted';\n if (value === EmailRecoveryModeHint.OnchainPublic) return 'onchain-public';\n return null;\n}\n\nexport function extractRecoveryModeFromBody(emailBlob?: string): EmailRecoveryMode | null {\n if (!emailBlob) return null;\n\n const lines = emailBlob.split(/\\r?\\n/);\n const bodyStartIndex = lines.findIndex(line => line.trim() === '');\n if (bodyStartIndex === -1) return null;\n\n const bodyLines = lines.slice(bodyStartIndex + 1);\n const firstNonEmptyBodyLine = bodyLines.find(line => line.trim() !== '');\n if (!firstNonEmptyBodyLine) return null;\n\n const candidate = firstNonEmptyBodyLine.trim();\n const normalized = normalizeRecoveryMode(candidate);\n if (normalized) return normalized;\n\n const lower = candidate.toLowerCase();\n if (lower.includes(EmailRecoveryModeHint.ZkEmail)) return 'zk-email';\n if (lower.includes(EmailRecoveryModeHint.TeeEncrypted)) return 'tee-encrypted';\n if (lower.includes(EmailRecoveryModeHint.OnchainPublic)) return 'onchain-public';\n\n return null;\n}\n\ntype HeaderValue = string | string[] | undefined;\ntype HeadersLike = Headers | Record<string, HeaderValue> | undefined;\n\nexport type RecoverEmailParseResult =\n | { ok: true; accountId: string; emailBlob: string; explicitMode?: string }\n | { ok: false; status: number; code: string; message: string };\n\nfunction getHeader(headers: HeadersLike, name: string): string | undefined {\n if (!headers) return undefined;\n\n const maybeHeaders = headers as any;\n if (typeof maybeHeaders.get === 'function') {\n const v = maybeHeaders.get(name);\n return (typeof v === 'string') ? v : undefined;\n }\n\n const record = headers as Record<string, HeaderValue>;\n const v = record[name.toLowerCase()] ?? record[name];\n if (Array.isArray(v)) return (typeof v[0] === 'string') ? v[0] : undefined;\n return (typeof v === 'string') ? v : undefined;\n}\n\nfunction parseExplicitMode(body: unknown, headers?: HeadersLike): string | undefined {\n const modeFromBody =\n (typeof (body as any)?.explicitMode === 'string' ? String((body as any).explicitMode) : '') ||\n (typeof (body as any)?.explicit_mode === 'string' ? String((body as any).explicit_mode) : '');\n const modeFromHeader = getHeader(headers, 'x-email-recovery-mode') || getHeader(headers, 'x-recovery-mode') || '';\n const raw = (modeFromBody || modeFromHeader).trim();\n return raw ? raw : undefined;\n}\n\nexport function parseRecoverEmailRequest(body: unknown, opts: { headers?: HeadersLike } = {}): RecoverEmailParseResult {\n const explicitMode = parseExplicitMode(body, opts.headers);\n\n const normalized = normalizeForwardableEmailPayload(body);\n if (!normalized.ok) {\n return { ok: false, status: 400, code: normalized.code, message: normalized.message };\n }\n\n const payload = normalized.payload;\n const emailBlob = payload.raw || '';\n const emailHeaders = payload.headers || {};\n\n const subjectHeader = emailHeaders['subject'];\n const parsedAccountId = parseAccountIdFromSubject(subjectHeader || emailBlob);\n const headerAccountId = String(emailHeaders['x-near-account-id'] || emailHeaders['x-account-id'] || '').trim();\n const accountId = (parsedAccountId || headerAccountId || '').trim();\n\n if (!accountId) {\n return { ok: false, status: 400, code: 'missing_account', message: 'x-near-account-id header is required' };\n }\n if (!emailBlob) {\n return { ok: false, status: 400, code: 'missing_email', message: 'raw email blob is required' };\n }\n\n return { ok: true, accountId, emailBlob, explicitMode };\n}\n\nconst EMAIL_ADDRESS_REGEX =\n /([a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*)/;\n\nexport function canonicalizeEmail(input: string): string {\n const raw = String(input || '').trim();\n if (!raw) return '';\n\n // Handle cases where a full header line is passed in (e.g. \"From: ...\").\n const withoutHeaderName = raw.replace(/^[a-z0-9-]+\\s*:\\s*/i, '').trim();\n\n // Prefer the common \"Name <email@domain>\" format when present, but still\n // validate/extract the actual address via regex.\n const angleMatch = withoutHeaderName.match(/<([^>]+)>/);\n const candidates = [\n angleMatch?.[1],\n withoutHeaderName,\n ].filter((v): v is string => typeof v === 'string' && v.length > 0);\n\n for (const candidate of candidates) {\n const cleaned = candidate.replace(/^mailto:\\s*/i, '');\n const match = cleaned.match(EMAIL_ADDRESS_REGEX);\n if (match?.[1]) {\n return match[1].trim().toLowerCase();\n }\n }\n\n return withoutHeaderName.toLowerCase();\n}\n\nexport function parseHeaderValue(rawEmail: string, name: string): string | undefined {\n try {\n const raw = String(rawEmail || '');\n if (!raw) return undefined;\n\n const lines = raw.split(/\\r?\\n/);\n const headerLines: string[] = [];\n\n // Only consider the header section (until the first blank line).\n for (const line of lines) {\n if (line.trim() === '') break;\n\n // RFC822 header folding: lines starting with whitespace continue previous header.\n if (/^\\s/.test(line) && headerLines.length > 0) {\n headerLines[headerLines.length - 1] += ` ${line.trim()}`;\n continue;\n }\n\n headerLines.push(line);\n }\n\n const headerName = name.trim();\n if (!headerName) return undefined;\n\n const re = new RegExp(`^${headerName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\\\s*:`, 'i');\n const found = headerLines.find((l) => re.test(l));\n if (!found) return undefined;\n\n const idx = found.indexOf(':');\n const value = idx >= 0 ? found.slice(idx + 1).trim() : '';\n return value || undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function parseRecoverSubjectBindings(\n rawEmail: string\n): { requestId: string; accountId: string; newPublicKey: string } | null {\n // Accept either a full RFC822 email or a bare Subject value.\n let subjectText = (parseHeaderValue(rawEmail, 'subject') || String(rawEmail || '')).trim();\n if (!subjectText) return null;\n\n // Strip common reply/forward prefixes.\n subjectText = subjectText.replace(/^(re|fwd):\\s*/i, '').trim();\n if (!subjectText) return null;\n\n // Strict format:\n // \"recover-<request_id> <accountId> ed25519:<pk>\"\n const match = subjectText.match(\n /^recover-([A-Za-z0-9]{6})\\s+([^\\s]+)\\s+ed25519:([^\\s]+)\\s*$/i\n );\n if (!match) return null;\n\n const [, requestId, accountId, newPublicKey] = match;\n return { requestId, accountId, newPublicKey: ensureEd25519Prefix(newPublicKey) };\n}\n"],"mappings":";;;AAIA,IAAY,0EAAL;AACL;AACA;AACA;;;AAGF,SAAgB,sBAAsB,KAA0D;AAC9F,KAAI,CAAC,IAAK,QAAO;CACjB,MAAM,QAAQ,IAAI,OAAO;AACzB,KAAI,UAAU,sBAAsB,QAAS,QAAO;AACpD,KAAI,UAAU,sBAAsB,aAAc,QAAO;AACzD,KAAI,UAAU,sBAAsB,cAAe,QAAO;AAC1D,QAAO;;AAGT,SAAgB,4BAA4B,WAA8C;AACxF,KAAI,CAAC,UAAW,QAAO;CAEvB,MAAM,QAAQ,UAAU,MAAM;CAC9B,MAAM,iBAAiB,MAAM,WAAU,SAAQ,KAAK,WAAW;AAC/D,KAAI,mBAAmB,GAAI,QAAO;CAElC,MAAM,YAAY,MAAM,MAAM,iBAAiB;CAC/C,MAAM,wBAAwB,UAAU,MAAK,SAAQ,KAAK,WAAW;AACrE,KAAI,CAAC,sBAAuB,QAAO;CAEnC,MAAM,YAAY,sBAAsB;CACxC,MAAM,aAAa,sBAAsB;AACzC,KAAI,WAAY,QAAO;CAEvB,MAAM,QAAQ,UAAU;AACxB,KAAI,MAAM,SAAS,sBAAsB,SAAU,QAAO;AAC1D,KAAI,MAAM,SAAS,sBAAsB,cAAe,QAAO;AAC/D,KAAI,MAAM,SAAS,sBAAsB,eAAgB,QAAO;AAEhE,QAAO;;AA6DT,MAAM,sBACJ;AAEF,SAAgB,kBAAkB,OAAuB;CACvD,MAAM,MAAM,OAAO,SAAS,IAAI;AAChC,KAAI,CAAC,IAAK,QAAO;CAGjB,MAAM,oBAAoB,IAAI,QAAQ,uBAAuB,IAAI;CAIjE,MAAM,aAAa,kBAAkB,MAAM;CAC3C,MAAM,aAAa,CACjB,aAAa,IACb,mBACA,QAAQ,MAAmB,OAAO,MAAM,YAAY,EAAE,SAAS;AAEjE,MAAK,MAAM,aAAa,YAAY;EAClC,MAAM,UAAU,UAAU,QAAQ,gBAAgB;EAClD,MAAM,QAAQ,QAAQ,MAAM;AAC5B,MAAI,QAAQ,GACV,QAAO,MAAM,GAAG,OAAO;;AAI3B,QAAO,kBAAkB;;AAG3B,SAAgB,iBAAiB,UAAkB,MAAkC;AACnF,KAAI;EACF,MAAM,MAAM,OAAO,YAAY;AAC/B,MAAI,CAAC,IAAK,QAAO;EAEjB,MAAM,QAAQ,IAAI,MAAM;EACxB,MAAMA,cAAwB;AAG9B,OAAK,MAAM,QAAQ,OAAO;AACxB,OAAI,KAAK,WAAW,GAAI;AAGxB,OAAI,MAAM,KAAK,SAAS,YAAY,SAAS,GAAG;AAC9C,gBAAY,YAAY,SAAS,MAAM,IAAI,KAAK;AAChD;;AAGF,eAAY,KAAK;;EAGnB,MAAM,aAAa,KAAK;AACxB,MAAI,CAAC,WAAY,QAAO;EAExB,MAAM,KAAK,IAAI,OAAO,IAAI,WAAW,QAAQ,uBAAuB,QAAQ,QAAQ;EACpF,MAAM,QAAQ,YAAY,MAAM,MAAM,GAAG,KAAK;AAC9C,MAAI,CAAC,MAAO,QAAO;EAEnB,MAAM,MAAM,MAAM,QAAQ;EAC1B,MAAM,QAAQ,OAAO,IAAI,MAAM,MAAM,MAAM,GAAG,SAAS;AACvD,SAAO,SAAS;SACV;AACN,SAAO;;;AAIX,SAAgB,4BACd,UACuE;CAEvE,IAAI,eAAe,iBAAiB,UAAU,cAAc,OAAO,YAAY,KAAK;AACpF,KAAI,CAAC,YAAa,QAAO;AAGzB,eAAc,YAAY,QAAQ,kBAAkB,IAAI;AACxD,KAAI,CAAC,YAAa,QAAO;CAIzB,MAAM,QAAQ,YAAY,MACxB;AAEF,KAAI,CAAC,MAAO,QAAO;CAEnB,MAAM,GAAG,WAAW,WAAW,gBAAgB;AAC/C,QAAO;EAAE;EAAW;EAAW,cAAc,oBAAoB"}
@@ -16,8 +16,8 @@ import { decryptEmailForOutlayerTestOnly, deriveTestX25519KeypairFromSeed } from
16
16
  * - Encrypting raw RFC822 emails with encryptEmailForOutlayer, binding an AEAD context
17
17
  * `{ account_id, network_id, payer_account_id }`,
18
18
  * - Calling the per-account EmailRecoverer contract with:
19
- * - `verify_encrypted_email_and_recover(encrypted_email_blob, aead_context, expected_hashed_email, expected_new_public_key)` for DKIM/TEE,
20
- * - `verify_zkemail_and_recover` for zk-email recovery,
19
+ * - `verify_encrypted_email_and_recover(encrypted_email_blob, aead_context, expected_hashed_email, expected_new_public_key, request_id)` for DKIM/TEE,
20
+ * - `verify_zkemail_and_recover(..., request_id)` for zk-email recovery,
21
21
  * - Performing legacy plaintext on-chain verification via `verify_email_onchain_and_recover`
22
22
  * for backwards compatibility only.
23
23
  */
@@ -149,10 +149,9 @@ var EmailRecoveryService = class {
149
149
  * - Calls the per-account EmailRecoverer contract's
150
150
  * `verify_encrypted_email_and_recover` entrypoint on the user's account.
151
151
  *
152
- * The per-account EmailRecoverer then delegates to the global
153
- * EmailDKIMVerifier (TEE path), which stores a VerificationResult keyed by
154
- * request_id. The frontend polls EmailDKIMVerifier::get_verification_result(request_id)
155
- * to observe success/failure.
152
+ * The per-account EmailRecoverer records a pollable attempt keyed by
153
+ * `request_id` (parsed from the email Subject) so the frontend can observe
154
+ * success/failure by polling `EmailRecoverer.get_recovery_attempt(request_id)`.
156
155
  */
157
156
  async verifyEncryptedEmailAndRecover(request) {
158
157
  const accountId = (request.accountId || "").trim();
@@ -334,6 +333,7 @@ var EmailRecoveryService = class {
334
333
  public_inputs: proofResult.publicInputs,
335
334
  account_id: bindings.accountId,
336
335
  new_public_key: bindings.newPublicKey,
336
+ request_id: bindings.requestId,
337
337
  from_email: bindings.fromEmail,
338
338
  timestamp: bindings.timestamp
339
339
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["e: unknown","e: any","error: any"],"sources":["../../../../src/server/email-recovery/index.ts"],"sourcesContent":["import type { ActionArgsWasm } from '../../core/types/actions';\nimport { ActionType, validateActionArgsWasm } from '../../core/types/actions';\nimport { parseContractExecutionError } from '../core/errors';\nimport { generateZkEmailProofFromPayload, type ZkEmailProverClientOptions } from './zkEmail';\nimport { extractRecoveryModeFromBody, normalizeRecoveryMode } from './emailParsers';\nimport { encryptEmailForOutlayer } from './emailEncryptor';\nimport { buildEncryptedEmailRecoveryActions, buildOnchainEmailRecoveryActions, buildZkEmailRecoveryActions, sendEmailRecoveryTransaction, getOutlayerEncryptionPublicKey } from './rpcCalls';\nimport { ZkEmailProverClient } from './zkEmail/proverClient';\nimport { mapZkEmailRecoveryError, prepareZkEmailRecovery } from './zkEmail/recovery';\nimport { normalizeLogger, type NormalizedLogger } from '../core/logger';\nimport type {\n EmailRecoveryDispatchRequest,\n EmailRecoveryMode,\n EmailRecoveryRequest,\n EmailRecoveryResult,\n EmailRecoveryServiceDeps,\n} from './types';\n\nexport * from './emailEncryptor';\nexport * from './zkEmail';\nexport * from './zkEmail/recovery';\nexport * from './testHelpers';\nexport * from './types';\n\n/**\n * EmailRecoveryService encapsulates email recovery logic for the relayer.\n *\n * It currently orchestrates:\n * - Fetching and caching the Outlayer X25519 public key from the global EmailDKIMVerifier,\n * - Encrypting raw RFC822 emails with encryptEmailForOutlayer, binding an AEAD context\n * `{ account_id, network_id, payer_account_id }`,\n * - Calling the per-account EmailRecoverer contract with:\n * - `verify_encrypted_email_and_recover(encrypted_email_blob, aead_context, expected_hashed_email, expected_new_public_key)` for DKIM/TEE,\n * - `verify_zkemail_and_recover` for zk-email recovery,\n * - Performing legacy plaintext on-chain verification via `verify_email_onchain_and_recover`\n * for backwards compatibility only.\n */\nexport class EmailRecoveryService {\n private readonly deps: EmailRecoveryServiceDeps;\n private readonly logger: NormalizedLogger;\n private cachedOutlayerPk: Uint8Array | null = null;\n private zkEmailProverClient: ZkEmailProverClient | null = null;\n private zkEmailProverClientKey: string | null = null;\n\n constructor(deps: EmailRecoveryServiceDeps) {\n this.deps = deps;\n this.logger = normalizeLogger(deps.logger);\n }\n\n /**\n * Lightweight view of zk-email prover wiring for health/readiness endpoints.\n * This does not perform any network calls.\n */\n getZkEmailProverBaseUrl(): string | null {\n const baseUrl = String(this.deps.zkEmailProver?.baseUrl || '').trim().replace(/\\/+$/, '');\n return baseUrl ? baseUrl : null;\n }\n\n /**\n * Readiness check for zk-email prover.\n *\n * Returns `healthy: null` when zk-email prover is not configured.\n * Does not log; callers (routers) may decide how/when to log.\n */\n async checkZkEmailProverHealth(): Promise<{\n configured: boolean;\n baseUrl: string | null;\n healthy: boolean | null;\n errorCode?: string;\n message?: string;\n proverCauseCode?: string;\n proverCauseMessage?: string;\n }> {\n const baseUrl = this.getZkEmailProverBaseUrl();\n const opts = this.deps.zkEmailProver;\n if (!baseUrl || !opts) {\n return { configured: false, baseUrl: null, healthy: null };\n }\n\n try {\n const client = this.getZkEmailProverClient({ ...opts, baseUrl });\n await client.healthz();\n return { configured: true, baseUrl, healthy: true };\n } catch (e: unknown) {\n const mapped = mapZkEmailRecoveryError(e);\n return {\n configured: true,\n baseUrl,\n healthy: false,\n errorCode: mapped.errorCode,\n message: mapped.message,\n proverCauseCode: mapped.proverCauseCode,\n proverCauseMessage: mapped.proverCauseMessage,\n };\n }\n }\n\n private getZkEmailProverClient(opts: ZkEmailProverClientOptions): ZkEmailProverClient {\n const baseUrl = String(opts.baseUrl || '').replace(/\\/+$/, '');\n const timeoutMs = opts.timeoutMs ?? 60_000;\n const healthCheck = opts.healthCheck;\n const key = `${baseUrl}|${timeoutMs}|${healthCheck?.enabled ?? 'default'}|${healthCheck?.ttlMs ?? 'default'}|${healthCheck?.timeoutMs ?? 'default'}`;\n\n if (this.zkEmailProverClient && this.zkEmailProverClientKey === key) {\n return this.zkEmailProverClient;\n }\n\n const client = new ZkEmailProverClient(opts);\n this.zkEmailProverClient = client;\n this.zkEmailProverClientKey = key;\n return client;\n }\n\n private async getOutlayerEmailDkimPublicKey(): Promise<Uint8Array> {\n if (this.cachedOutlayerPk) {\n return this.cachedOutlayerPk;\n }\n const pk = await getOutlayerEncryptionPublicKey(this.deps);\n this.cachedOutlayerPk = pk;\n return pk;\n }\n\n /**\n * Determine recovery mode (zk-email | encrypted | onchain-public) from:\n * - explicit override (for programmatic callers),\n * - body markers inside the raw email,\n * falling back to tee-private for backwards compatibility.\n */\n private determineRecoveryMode(input: {\n explicitMode?: string;\n emailBlob?: string;\n }): EmailRecoveryMode {\n return (\n normalizeRecoveryMode(input.explicitMode) ??\n extractRecoveryModeFromBody(input.emailBlob) ??\n 'tee-encrypted'\n );\n }\n\n /**\n * Top-level dispatcher for email recovery modes.\n *\n * Usage from HTTP routes:\n * - Pass the full raw RFC822 email as `emailBlob` (including headers + body).\n * - Optionally include an explicit `explicitMode` override (`'zk-email' | 'tee-encrypted' | 'onchain-public'`).\n * - Otherwise, the first non-empty body line is parsed as a mode hint:\n * - `\"zk-email\"` → zk-email prover + per-account `verify_zkemail_and_recover`.\n * - `\"tee-encrypted\"` (or legacy `\"encrypted\"`) → per-account EmailRecoverer encrypted path (`verify_encrypted_email_and_recover`).\n * - `\"onchain-public\"` → currently routed to the same per-account encrypted path for backwards compatibility.\n * - If no hint is found, the mode defaults to `'tee-encrypted'`.\n */\n async requestEmailRecovery(request: EmailRecoveryDispatchRequest): Promise<EmailRecoveryResult> {\n const mode = this.determineRecoveryMode({\n explicitMode: request.explicitMode,\n emailBlob: request.emailBlob,\n });\n this.logger.debug('[email-recovery] requestEmailRecovery mode selected', {\n mode,\n accountId: request.accountId,\n });\n\n switch (mode) {\n case 'tee-encrypted':\n return this.verifyEncryptedEmailAndRecover({\n accountId: request.accountId,\n emailBlob: request.emailBlob,\n });\n case 'zk-email':\n return this.verifyZkemailAndRecover({\n accountId: request.accountId,\n emailBlob: request.emailBlob,\n });\n case 'onchain-public':\n // Use the same encrypted/TEE path via per-account EmailRecoverer.\n return this.verifyEncryptedEmailAndRecover({\n accountId: request.accountId,\n emailBlob: request.emailBlob,\n });\n default:\n // Fallback to the TEE-encrypted path for forwards compatibility.\n return this.verifyEncryptedEmailAndRecover({\n accountId: request.accountId,\n emailBlob: request.emailBlob,\n });\n }\n }\n\n /**\n * Helper for encrypted DKIM-based email recovery:\n * - Encrypts the raw email blob for the Outlayer worker.\n * - Calls the per-account EmailRecoverer contract's\n * `verify_encrypted_email_and_recover` entrypoint on the user's account.\n *\n * The per-account EmailRecoverer then delegates to the global\n * EmailDKIMVerifier (TEE path), which stores a VerificationResult keyed by\n * request_id. The frontend polls EmailDKIMVerifier::get_verification_result(request_id)\n * to observe success/failure.\n */\n\t async verifyEncryptedEmailAndRecover(request: EmailRecoveryRequest): Promise<EmailRecoveryResult> {\n\t const accountId = (request.accountId || '').trim();\n\t const emailBlob = request.emailBlob;\n\n\t if (!accountId) {\n\t const errMsg = 'accountId is required';\n\t return { success: false, error: errMsg, message: errMsg };\n\t }\n\t if (!emailBlob || typeof emailBlob !== 'string') {\n\t const errMsg = 'emailBlob (raw email) is required';\n\t return { success: false, error: errMsg, message: errMsg };\n\t }\n\n\t const { ensureSignerAndRelayerAccount } = this.deps;\n\n\t try {\n\t await ensureSignerAndRelayerAccount();\n\t } catch (e: any) {\n\t const msg = e?.message || 'Failed to initialize relayer account';\n\t return { success: false, error: msg, message: msg };\n\t }\n\n\t\t const recipientPk = await this.getOutlayerEmailDkimPublicKey();\n\t\t this.logger.debug('[email-recovery] encrypted using Outlayer public key', {\n\t\t accountId,\n\t\t outlayerPkLen: recipientPk.length,\n\t\t });\n\n\t const { actions, receiverId } = await buildEncryptedEmailRecoveryActions(this.deps, {\n\t accountId,\n\t emailBlob,\n\t recipientPk,\n\t encrypt: async ({ emailRaw, aeadContext, recipientPk: pk }) => {\n\t const { envelope } = await encryptEmailForOutlayer({\n\t emailRaw,\n\t aeadContext,\n\t recipientPk: pk,\n\t });\n\n\t\t this.logger.debug('[email-recovery] encrypted email envelope metadata', {\n\t\t accountId,\n\t\t aeadContextLen: aeadContext.length,\n\t\t envelope: {\n\t\t version: envelope.version,\n\t\t ephemeral_pub_len: envelope.ephemeral_pub?.length ?? 0,\n\t\t nonce_len: envelope.nonce?.length ?? 0,\n\t\t ciphertext_len: envelope.ciphertext?.length ?? 0,\n\t\t },\n\t\t });\n\n\t return { envelope };\n\t },\n\t });\n\n\t return sendEmailRecoveryTransaction(this.deps, {\n\t receiverId,\n\t actions,\n\t label: `Encrypted email verification requested for ${accountId}`,\n\t });\n\t }\n\n /**\n * Legacy helper for plaintext/on-chain DKIM email verification + account recovery.\n * This path is deprecated in favor of the encrypted TEE path via\n * `verifyEncryptedEmailAndRecover` and is no longer used by\n * `requestEmailRecovery`.\n */\n\t async verifyEmailOnchainAndRecover(request: EmailRecoveryRequest): Promise<EmailRecoveryResult> {\n const accountId = (request.accountId || '').trim();\n const emailBlob = request.emailBlob;\n\n if (!accountId) {\n let errMsg = 'accountId is required';\n return { success: false, error: errMsg, message: errMsg };\n }\n if (!emailBlob || typeof emailBlob !== 'string') {\n let errMsg = 'emailBlob (raw email) is required';\n return { success: false, error: errMsg, message: errMsg };\n }\n\n\t const { ensureSignerAndRelayerAccount } = this.deps;\n\n try {\n await ensureSignerAndRelayerAccount();\n } catch (e: any) {\n const msg = e?.message || 'Failed to initialize relayer account';\n return { success: false, error: msg, message: msg };\n }\n\n\t const { actions, receiverId } = await buildOnchainEmailRecoveryActions(this.deps, {\n\t accountId,\n\t emailBlob,\n\t });\n\n\t return sendEmailRecoveryTransaction(this.deps, {\n\t receiverId,\n\t actions,\n\t label: `On-chain email verification requested for ${accountId}`,\n\t });\n }\n\n /**\n * Helper for zk-email recovery:\n * - Calls external zk-email prover with the raw email blob to obtain (proof, publicInputs).\n * - Extracts subject/header bindings (account_id, new_public_key, from_email, timestamp).\n * - Calls the per-account EmailRecoverer contract with verify_zkemail_and_recover.\n */\n\t async verifyZkemailAndRecover(request: EmailRecoveryRequest): Promise<EmailRecoveryResult> {\n\t const accountId = (request.accountId || '').trim();\n\t const emailBlob = request.emailBlob;\n\n if (!accountId) {\n return {\n success: false,\n error: 'zkemail_missing_account_id',\n message: 'accountId is required',\n };\n }\n if (!emailBlob || typeof emailBlob !== 'string') {\n return {\n success: false,\n error: 'zkemail_missing_email_blob',\n message: 'emailBlob (raw email) is required',\n };\n }\n\n\t const { ensureSignerAndRelayerAccount, zkEmailProver } = this.deps;\n\n if (!zkEmailProver || !zkEmailProver.baseUrl) {\n this.logger.warn('[email-recovery] zk-email missing prover configuration', { accountId });\n return {\n success: false,\n error: 'zkemail_prover_not_configured',\n message: 'zk-email prover configuration is missing',\n };\n }\n\n const prepared = prepareZkEmailRecovery(emailBlob, accountId);\n if (!prepared.ok) {\n const log = {\n accountId,\n requestId: prepared.requestId,\n proverBaseUrl: zkEmailProver.baseUrl,\n errorCode: prepared.errorCode,\n errorMessage: prepared.message,\n accountIdSubject: prepared.subjectAccountId,\n };\n if (prepared.errorCode === 'zkemail_account_mismatch') {\n this.logger.warn('[email-recovery] zk-email account mismatch', log);\n } else {\n this.logger.warn('[email-recovery] zk-email recovery rejected', log);\n }\n return { success: false, error: prepared.errorCode, message: prepared.message };\n }\n\n const { payload, bindings } = prepared.prepared;\n\n try {\n await ensureSignerAndRelayerAccount();\n } catch (e: any) {\n const msg = e?.message || 'Failed to initialize relayer account';\n this.logger.error('[email-recovery] zk-email ensureSignerAndRelayerAccount failed', {\n accountId,\n requestId: bindings.requestId,\n error: msg,\n });\n return { success: false, error: 'zkemail_relayer_init_failed', message: msg };\n }\n\n\t try {\n\t const proverClient = this.getZkEmailProverClient(zkEmailProver);\n\t const proofResult = await generateZkEmailProofFromPayload(payload, proverClient);\n\n\t const contractArgs = {\n\t proof: proofResult.proof,\n\t public_inputs: proofResult.publicInputs,\n\t account_id: bindings.accountId,\n\t new_public_key: bindings.newPublicKey,\n\t from_email: bindings.fromEmail,\n\t timestamp: bindings.timestamp,\n\t };\n\n\t const { actions, receiverId } = await buildZkEmailRecoveryActions(this.deps, {\n\t accountId,\n\t contractArgs,\n\t });\n\n\t return sendEmailRecoveryTransaction(this.deps, {\n\t receiverId,\n\t actions,\n\t label: `ZK-email recovery requested for ${accountId}`,\n\t });\n\t\t } catch (error: any) {\n const mapped = mapZkEmailRecoveryError(error);\n\n\t\t this.logger.error('[email-recovery] zk-email recovery error', {\n\t\t accountId,\n\t requestId: bindings.requestId,\n\t\t errorCode: mapped.errorCode,\n\t\t errorMessage: mapped.message,\n\t proverBaseUrl: zkEmailProver.baseUrl,\n\t proverCauseCode: mapped.proverCauseCode,\n\t proverCauseMessage: mapped.proverCauseMessage,\n\t\t });\n\n\t return {\n\t success: false,\n\t error: mapped.errorCode,\n\t message: mapped.message,\n\t };\n\t }\n\t }\n\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["e: unknown","e: any","error: any"],"sources":["../../../../src/server/email-recovery/index.ts"],"sourcesContent":["import type { ActionArgsWasm } from '../../core/types/actions';\nimport { ActionType, validateActionArgsWasm } from '../../core/types/actions';\nimport { parseContractExecutionError } from '../core/errors';\nimport { generateZkEmailProofFromPayload, type ZkEmailProverClientOptions } from './zkEmail';\nimport { extractRecoveryModeFromBody, normalizeRecoveryMode } from './emailParsers';\nimport { encryptEmailForOutlayer } from './emailEncryptor';\nimport { buildEncryptedEmailRecoveryActions, buildOnchainEmailRecoveryActions, buildZkEmailRecoveryActions, sendEmailRecoveryTransaction, getOutlayerEncryptionPublicKey } from './rpcCalls';\nimport { ZkEmailProverClient } from './zkEmail/proverClient';\nimport { mapZkEmailRecoveryError, prepareZkEmailRecovery } from './zkEmail/recovery';\nimport { normalizeLogger, type NormalizedLogger } from '../core/logger';\nimport type {\n EmailRecoveryDispatchRequest,\n EmailRecoveryMode,\n EmailRecoveryRequest,\n EmailRecoveryResult,\n EmailRecoveryServiceDeps,\n} from './types';\n\nexport * from './emailEncryptor';\nexport * from './zkEmail';\nexport * from './zkEmail/recovery';\nexport * from './testHelpers';\nexport * from './types';\n\n/**\n * EmailRecoveryService encapsulates email recovery logic for the relayer.\n *\n * It currently orchestrates:\n * - Fetching and caching the Outlayer X25519 public key from the global EmailDKIMVerifier,\n * - Encrypting raw RFC822 emails with encryptEmailForOutlayer, binding an AEAD context\n * `{ account_id, network_id, payer_account_id }`,\n * - Calling the per-account EmailRecoverer contract with:\n * - `verify_encrypted_email_and_recover(encrypted_email_blob, aead_context, expected_hashed_email, expected_new_public_key, request_id)` for DKIM/TEE,\n * - `verify_zkemail_and_recover(..., request_id)` for zk-email recovery,\n * - Performing legacy plaintext on-chain verification via `verify_email_onchain_and_recover`\n * for backwards compatibility only.\n */\nexport class EmailRecoveryService {\n private readonly deps: EmailRecoveryServiceDeps;\n private readonly logger: NormalizedLogger;\n private cachedOutlayerPk: Uint8Array | null = null;\n private zkEmailProverClient: ZkEmailProverClient | null = null;\n private zkEmailProverClientKey: string | null = null;\n\n constructor(deps: EmailRecoveryServiceDeps) {\n this.deps = deps;\n this.logger = normalizeLogger(deps.logger);\n }\n\n /**\n * Lightweight view of zk-email prover wiring for health/readiness endpoints.\n * This does not perform any network calls.\n */\n getZkEmailProverBaseUrl(): string | null {\n const baseUrl = String(this.deps.zkEmailProver?.baseUrl || '').trim().replace(/\\/+$/, '');\n return baseUrl ? baseUrl : null;\n }\n\n /**\n * Readiness check for zk-email prover.\n *\n * Returns `healthy: null` when zk-email prover is not configured.\n * Does not log; callers (routers) may decide how/when to log.\n */\n async checkZkEmailProverHealth(): Promise<{\n configured: boolean;\n baseUrl: string | null;\n healthy: boolean | null;\n errorCode?: string;\n message?: string;\n proverCauseCode?: string;\n proverCauseMessage?: string;\n }> {\n const baseUrl = this.getZkEmailProverBaseUrl();\n const opts = this.deps.zkEmailProver;\n if (!baseUrl || !opts) {\n return { configured: false, baseUrl: null, healthy: null };\n }\n\n try {\n const client = this.getZkEmailProverClient({ ...opts, baseUrl });\n await client.healthz();\n return { configured: true, baseUrl, healthy: true };\n } catch (e: unknown) {\n const mapped = mapZkEmailRecoveryError(e);\n return {\n configured: true,\n baseUrl,\n healthy: false,\n errorCode: mapped.errorCode,\n message: mapped.message,\n proverCauseCode: mapped.proverCauseCode,\n proverCauseMessage: mapped.proverCauseMessage,\n };\n }\n }\n\n private getZkEmailProverClient(opts: ZkEmailProverClientOptions): ZkEmailProverClient {\n const baseUrl = String(opts.baseUrl || '').replace(/\\/+$/, '');\n const timeoutMs = opts.timeoutMs ?? 60_000;\n const healthCheck = opts.healthCheck;\n const key = `${baseUrl}|${timeoutMs}|${healthCheck?.enabled ?? 'default'}|${healthCheck?.ttlMs ?? 'default'}|${healthCheck?.timeoutMs ?? 'default'}`;\n\n if (this.zkEmailProverClient && this.zkEmailProverClientKey === key) {\n return this.zkEmailProverClient;\n }\n\n const client = new ZkEmailProverClient(opts);\n this.zkEmailProverClient = client;\n this.zkEmailProverClientKey = key;\n return client;\n }\n\n private async getOutlayerEmailDkimPublicKey(): Promise<Uint8Array> {\n if (this.cachedOutlayerPk) {\n return this.cachedOutlayerPk;\n }\n const pk = await getOutlayerEncryptionPublicKey(this.deps);\n this.cachedOutlayerPk = pk;\n return pk;\n }\n\n /**\n * Determine recovery mode (zk-email | encrypted | onchain-public) from:\n * - explicit override (for programmatic callers),\n * - body markers inside the raw email,\n * falling back to tee-private for backwards compatibility.\n */\n private determineRecoveryMode(input: {\n explicitMode?: string;\n emailBlob?: string;\n }): EmailRecoveryMode {\n return (\n normalizeRecoveryMode(input.explicitMode) ??\n extractRecoveryModeFromBody(input.emailBlob) ??\n 'tee-encrypted'\n );\n }\n\n /**\n * Top-level dispatcher for email recovery modes.\n *\n * Usage from HTTP routes:\n * - Pass the full raw RFC822 email as `emailBlob` (including headers + body).\n * - Optionally include an explicit `explicitMode` override (`'zk-email' | 'tee-encrypted' | 'onchain-public'`).\n * - Otherwise, the first non-empty body line is parsed as a mode hint:\n * - `\"zk-email\"` → zk-email prover + per-account `verify_zkemail_and_recover`.\n * - `\"tee-encrypted\"` (or legacy `\"encrypted\"`) → per-account EmailRecoverer encrypted path (`verify_encrypted_email_and_recover`).\n * - `\"onchain-public\"` → currently routed to the same per-account encrypted path for backwards compatibility.\n * - If no hint is found, the mode defaults to `'tee-encrypted'`.\n */\n async requestEmailRecovery(request: EmailRecoveryDispatchRequest): Promise<EmailRecoveryResult> {\n const mode = this.determineRecoveryMode({\n explicitMode: request.explicitMode,\n emailBlob: request.emailBlob,\n });\n this.logger.debug('[email-recovery] requestEmailRecovery mode selected', {\n mode,\n accountId: request.accountId,\n });\n\n switch (mode) {\n case 'tee-encrypted':\n return this.verifyEncryptedEmailAndRecover({\n accountId: request.accountId,\n emailBlob: request.emailBlob,\n });\n case 'zk-email':\n return this.verifyZkemailAndRecover({\n accountId: request.accountId,\n emailBlob: request.emailBlob,\n });\n case 'onchain-public':\n // Use the same encrypted/TEE path via per-account EmailRecoverer.\n return this.verifyEncryptedEmailAndRecover({\n accountId: request.accountId,\n emailBlob: request.emailBlob,\n });\n default:\n // Fallback to the TEE-encrypted path for forwards compatibility.\n return this.verifyEncryptedEmailAndRecover({\n accountId: request.accountId,\n emailBlob: request.emailBlob,\n });\n }\n }\n\n /**\n * Helper for encrypted DKIM-based email recovery:\n * - Encrypts the raw email blob for the Outlayer worker.\n * - Calls the per-account EmailRecoverer contract's\n * `verify_encrypted_email_and_recover` entrypoint on the user's account.\n *\n * The per-account EmailRecoverer records a pollable attempt keyed by\n * `request_id` (parsed from the email Subject) so the frontend can observe\n * success/failure by polling `EmailRecoverer.get_recovery_attempt(request_id)`.\n */\n\t async verifyEncryptedEmailAndRecover(request: EmailRecoveryRequest): Promise<EmailRecoveryResult> {\n\t const accountId = (request.accountId || '').trim();\n\t const emailBlob = request.emailBlob;\n\n\t if (!accountId) {\n\t const errMsg = 'accountId is required';\n\t return { success: false, error: errMsg, message: errMsg };\n\t }\n\t if (!emailBlob || typeof emailBlob !== 'string') {\n\t const errMsg = 'emailBlob (raw email) is required';\n\t return { success: false, error: errMsg, message: errMsg };\n\t }\n\n\t const { ensureSignerAndRelayerAccount } = this.deps;\n\n\t try {\n\t await ensureSignerAndRelayerAccount();\n\t } catch (e: any) {\n\t const msg = e?.message || 'Failed to initialize relayer account';\n\t return { success: false, error: msg, message: msg };\n\t }\n\n\t\t const recipientPk = await this.getOutlayerEmailDkimPublicKey();\n\t\t this.logger.debug('[email-recovery] encrypted using Outlayer public key', {\n\t\t accountId,\n\t\t outlayerPkLen: recipientPk.length,\n\t\t });\n\n\t const { actions, receiverId } = await buildEncryptedEmailRecoveryActions(this.deps, {\n\t accountId,\n\t emailBlob,\n\t recipientPk,\n\t encrypt: async ({ emailRaw, aeadContext, recipientPk: pk }) => {\n\t const { envelope } = await encryptEmailForOutlayer({\n\t emailRaw,\n\t aeadContext,\n\t recipientPk: pk,\n\t });\n\n\t\t this.logger.debug('[email-recovery] encrypted email envelope metadata', {\n\t\t accountId,\n\t\t aeadContextLen: aeadContext.length,\n\t\t envelope: {\n\t\t version: envelope.version,\n\t\t ephemeral_pub_len: envelope.ephemeral_pub?.length ?? 0,\n\t\t nonce_len: envelope.nonce?.length ?? 0,\n\t\t ciphertext_len: envelope.ciphertext?.length ?? 0,\n\t\t },\n\t\t });\n\n\t return { envelope };\n\t },\n\t });\n\n\t return sendEmailRecoveryTransaction(this.deps, {\n\t receiverId,\n\t actions,\n\t label: `Encrypted email verification requested for ${accountId}`,\n\t });\n\t }\n\n /**\n * Legacy helper for plaintext/on-chain DKIM email verification + account recovery.\n * This path is deprecated in favor of the encrypted TEE path via\n * `verifyEncryptedEmailAndRecover` and is no longer used by\n * `requestEmailRecovery`.\n */\n\t async verifyEmailOnchainAndRecover(request: EmailRecoveryRequest): Promise<EmailRecoveryResult> {\n const accountId = (request.accountId || '').trim();\n const emailBlob = request.emailBlob;\n\n if (!accountId) {\n let errMsg = 'accountId is required';\n return { success: false, error: errMsg, message: errMsg };\n }\n if (!emailBlob || typeof emailBlob !== 'string') {\n let errMsg = 'emailBlob (raw email) is required';\n return { success: false, error: errMsg, message: errMsg };\n }\n\n\t const { ensureSignerAndRelayerAccount } = this.deps;\n\n try {\n await ensureSignerAndRelayerAccount();\n } catch (e: any) {\n const msg = e?.message || 'Failed to initialize relayer account';\n return { success: false, error: msg, message: msg };\n }\n\n\t const { actions, receiverId } = await buildOnchainEmailRecoveryActions(this.deps, {\n\t accountId,\n\t emailBlob,\n\t });\n\n\t return sendEmailRecoveryTransaction(this.deps, {\n\t receiverId,\n\t actions,\n\t label: `On-chain email verification requested for ${accountId}`,\n\t });\n }\n\n /**\n * Helper for zk-email recovery:\n * - Calls external zk-email prover with the raw email blob to obtain (proof, publicInputs).\n * - Extracts subject/header bindings (account_id, new_public_key, from_email, timestamp).\n * - Calls the per-account EmailRecoverer contract with verify_zkemail_and_recover.\n */\n\t async verifyZkemailAndRecover(request: EmailRecoveryRequest): Promise<EmailRecoveryResult> {\n\t const accountId = (request.accountId || '').trim();\n\t const emailBlob = request.emailBlob;\n\n if (!accountId) {\n return {\n success: false,\n error: 'zkemail_missing_account_id',\n message: 'accountId is required',\n };\n }\n if (!emailBlob || typeof emailBlob !== 'string') {\n return {\n success: false,\n error: 'zkemail_missing_email_blob',\n message: 'emailBlob (raw email) is required',\n };\n }\n\n\t const { ensureSignerAndRelayerAccount, zkEmailProver } = this.deps;\n\n if (!zkEmailProver || !zkEmailProver.baseUrl) {\n this.logger.warn('[email-recovery] zk-email missing prover configuration', { accountId });\n return {\n success: false,\n error: 'zkemail_prover_not_configured',\n message: 'zk-email prover configuration is missing',\n };\n }\n\n const prepared = prepareZkEmailRecovery(emailBlob, accountId);\n if (!prepared.ok) {\n const log = {\n accountId,\n requestId: prepared.requestId,\n proverBaseUrl: zkEmailProver.baseUrl,\n errorCode: prepared.errorCode,\n errorMessage: prepared.message,\n accountIdSubject: prepared.subjectAccountId,\n };\n if (prepared.errorCode === 'zkemail_account_mismatch') {\n this.logger.warn('[email-recovery] zk-email account mismatch', log);\n } else {\n this.logger.warn('[email-recovery] zk-email recovery rejected', log);\n }\n return { success: false, error: prepared.errorCode, message: prepared.message };\n }\n\n const { payload, bindings } = prepared.prepared;\n\n try {\n await ensureSignerAndRelayerAccount();\n } catch (e: any) {\n const msg = e?.message || 'Failed to initialize relayer account';\n this.logger.error('[email-recovery] zk-email ensureSignerAndRelayerAccount failed', {\n accountId,\n requestId: bindings.requestId,\n error: msg,\n });\n return { success: false, error: 'zkemail_relayer_init_failed', message: msg };\n }\n\n\t try {\n\t const proverClient = this.getZkEmailProverClient(zkEmailProver);\n\t const proofResult = await generateZkEmailProofFromPayload(payload, proverClient);\n\n\t\t const contractArgs = {\n\t\t proof: proofResult.proof,\n\t\t public_inputs: proofResult.publicInputs,\n\t\t account_id: bindings.accountId,\n\t\t new_public_key: bindings.newPublicKey,\n\t\t request_id: bindings.requestId,\n\t\t from_email: bindings.fromEmail,\n\t\t timestamp: bindings.timestamp,\n\t\t };\n\n\t const { actions, receiverId } = await buildZkEmailRecoveryActions(this.deps, {\n\t accountId,\n\t contractArgs,\n\t });\n\n\t return sendEmailRecoveryTransaction(this.deps, {\n\t receiverId,\n\t actions,\n\t label: `ZK-email recovery requested for ${accountId}`,\n\t });\n\t\t } catch (error: any) {\n const mapped = mapZkEmailRecoveryError(error);\n\n\t\t this.logger.error('[email-recovery] zk-email recovery error', {\n\t\t accountId,\n\t requestId: bindings.requestId,\n\t\t errorCode: mapped.errorCode,\n\t\t errorMessage: mapped.message,\n\t proverBaseUrl: zkEmailProver.baseUrl,\n\t proverCauseCode: mapped.proverCauseCode,\n\t proverCauseMessage: mapped.proverCauseMessage,\n\t\t });\n\n\t return {\n\t success: false,\n\t error: mapped.errorCode,\n\t message: mapped.message,\n\t };\n\t }\n\t }\n\n}\n"],"mappings":"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"}
@@ -4,6 +4,18 @@ import { parseHeaderValue, parseRecoverSubjectBindings } from "./emailParsers.js
4
4
  import { hashRecoveryEmailForAccount } from "./emailEncryptor.js";
5
5
 
6
6
  //#region src/server/email-recovery/rpcCalls.ts
7
+ function normalizeSingleLine(input) {
8
+ return String(input || "").replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
9
+ }
10
+ function formatEmailRecoveryTxError(error, receiverId) {
11
+ const kind = typeof error?.kind === "string" ? String(error.kind) : "";
12
+ const short = typeof error?.short === "string" ? String(error.short) : "";
13
+ const msg = normalizeSingleLine(error?.message || String(error || ""));
14
+ if (kind === "AccountDoesNotExist" || /AccountDoesNotExist/i.test(short) || /AccountDoesNotExist/i.test(msg) || /account does not exist/i.test(msg)) return `Account "${receiverId}" does not exist`;
15
+ if (/Invalid(Account|Receiver)Id/i.test(kind) || /Invalid(Account|Receiver)Id/i.test(short) || /Invalid(Account|Receiver)Id/i.test(msg)) return `Invalid NEAR account ID "${receiverId}"`;
16
+ if (short && short !== "TxExecutionError" && short !== "RPC error") return `Transaction failed (${short})`;
17
+ return msg || "Unknown email recovery error";
18
+ }
7
19
  async function getOutlayerEncryptionPublicKey(deps) {
8
20
  const { nearClient, emailDkimVerifierContract } = deps;
9
21
  const result = await nearClient.view({
@@ -48,7 +60,8 @@ async function buildEncryptedEmailRecoveryActions(deps, input) {
48
60
  encrypted_email_blob: envelope,
49
61
  aead_context: aeadContext,
50
62
  expected_hashed_email: expectedHashedEmail,
51
- expected_new_public_key: bindings.newPublicKey
63
+ expected_new_public_key: bindings.newPublicKey,
64
+ request_id: bindings.requestId
52
65
  };
53
66
  const actions = [{
54
67
  action_type: ActionType.FunctionCall,
@@ -80,10 +93,16 @@ async function buildZkEmailRecoveryActions(deps, input) {
80
93
  }
81
94
  async function buildOnchainEmailRecoveryActions(_deps, input) {
82
95
  const { accountId, emailBlob } = input;
96
+ const bindings = parseRecoverSubjectBindings(emailBlob);
97
+ if (!bindings) throw new Error("On-chain email recovery requires Subject: recover-<request_id> <accountId> ed25519:<new_public_key>");
98
+ if (bindings.accountId !== accountId) throw new Error(`On-chain email recovery subject accountId mismatch (expected "${accountId}", got "${bindings.accountId}")`);
83
99
  const actions = [{
84
100
  action_type: ActionType.FunctionCall,
85
101
  method_name: "verify_email_onchain_and_recover",
86
- args: JSON.stringify({ email_blob: emailBlob }),
102
+ args: JSON.stringify({
103
+ email_blob: emailBlob,
104
+ request_id: bindings.requestId
105
+ }),
87
106
  gas: "300000000000000",
88
107
  deposit: "10000000000000000000000"
89
108
  }];
@@ -121,7 +140,7 @@ async function sendEmailRecoveryTransaction(deps, args) {
121
140
  message: label
122
141
  };
123
142
  } catch (error) {
124
- const msg = error?.message || "Unknown email recovery error";
143
+ const msg = formatEmailRecoveryTxError(error, receiverId);
125
144
  return {
126
145
  success: false,
127
146
  error: msg,
@@ -1 +1 @@
1
- {"version":3,"file":"rpcCalls.js","names":["bytes: Uint8Array","aeadContext: EmailEncryptionContext","actions: ActionArgsWasm[]","error: any"],"sources":["../../../../src/server/email-recovery/rpcCalls.ts"],"sourcesContent":["import type { ActionArgsWasm } from '../../core/types/actions';\nimport { ActionType, validateActionArgsWasm } from '../../core/types/actions';\nimport { parseContractExecutionError } from '../core/errors';\nimport { hashRecoveryEmailForAccount, type EmailEncryptionContext } from './emailEncryptor';\nimport { parseHeaderValue, parseRecoverSubjectBindings } from './emailParsers';\nimport type { EmailRecoveryResult, EmailRecoveryServiceDeps, EmailRecoveryRequest } from './types';\n\nexport async function getOutlayerEncryptionPublicKey(\n deps: Pick<EmailRecoveryServiceDeps, 'nearClient' | 'emailDkimVerifierContract'>,\n): Promise<Uint8Array> {\n const { nearClient, emailDkimVerifierContract } = deps;\n\n const result = await nearClient.view<{}, unknown>({\n account: emailDkimVerifierContract,\n method: 'get_outlayer_encryption_public_key',\n args: {},\n });\n\n if (typeof result !== 'string' || !result) {\n throw new Error('Outlayer encryption public key is not configured on EmailDkimVerifier');\n }\n\n let bytes: Uint8Array;\n try {\n const decoded = typeof Buffer !== 'undefined'\n ? Buffer.from(result, 'base64')\n : Uint8Array.from(atob(result), c => c.charCodeAt(0));\n bytes = decoded instanceof Uint8Array ? decoded : new Uint8Array(decoded);\n } catch (e) {\n throw new Error(`Failed to decode Outlayer email DKIM public key: ${(e as Error).message}`);\n }\n\n if (bytes.length !== 32) {\n throw new Error(`Outlayer email DKIM public key must be 32 bytes, got ${bytes.length}`);\n }\n\n return bytes;\n}\n\nexport async function buildEncryptedEmailRecoveryActions(\n deps: EmailRecoveryServiceDeps,\n input: {\n accountId: string;\n emailBlob: string;\n recipientPk: Uint8Array;\n encrypt: (args: {\n emailRaw: string;\n aeadContext: EmailEncryptionContext;\n recipientPk: Uint8Array;\n }) => Promise<{ envelope: { version: number; ephemeral_pub: string; nonce: string; ciphertext: string } }>;\n },\n): Promise<{ actions: ActionArgsWasm[]; receiverId: string }> {\n const {\n relayerAccountId,\n networkId,\n } = deps;\n const { accountId, emailBlob, recipientPk, encrypt } = input;\n\n const aeadContext: EmailEncryptionContext = {\n account_id: accountId,\n network_id: networkId,\n payer_account_id: relayerAccountId,\n };\n\n const { envelope } = await encrypt({\n emailRaw: emailBlob,\n aeadContext,\n recipientPk,\n });\n\n const bindings = parseRecoverSubjectBindings(emailBlob);\n if (!bindings) {\n throw new Error('Encrypted email recovery requires Subject: recover-<request_id> <accountId> ed25519:<new_public_key>');\n }\n if (bindings.accountId !== accountId) {\n throw new Error(`Encrypted email recovery subject accountId mismatch (expected \"${accountId}\", got \"${bindings.accountId}\")`);\n }\n\n const fromHeader = parseHeaderValue(emailBlob, 'from');\n if (!fromHeader) {\n throw new Error('Encrypted email recovery requires a From: header');\n }\n const expectedHashedEmail = hashRecoveryEmailForAccount({ recoveryEmail: fromHeader, accountId });\n\n const contractArgs = {\n encrypted_email_blob: envelope,\n aead_context: aeadContext,\n expected_hashed_email: expectedHashedEmail,\n expected_new_public_key: bindings.newPublicKey,\n };\n\n const actions: ActionArgsWasm[] = [\n {\n action_type: ActionType.FunctionCall,\n method_name: 'verify_encrypted_email_and_recover',\n args: JSON.stringify(contractArgs),\n gas: '300000000000000',\n deposit: '10000000000000000000000',\n },\n ];\n actions.forEach(validateActionArgsWasm);\n\n return {\n actions,\n receiverId: accountId,\n };\n}\n\nexport async function buildZkEmailRecoveryActions(\n deps: EmailRecoveryServiceDeps,\n input: {\n accountId: string;\n contractArgs: {\n proof: unknown;\n public_inputs: string[];\n account_id: string;\n new_public_key: string;\n from_email: string;\n timestamp: string;\n };\n },\n): Promise<{ actions: ActionArgsWasm[]; receiverId: string }> {\n const { accountId, contractArgs } = input;\n\n const actions: ActionArgsWasm[] = [\n {\n action_type: ActionType.FunctionCall,\n method_name: 'verify_zkemail_and_recover',\n args: JSON.stringify(contractArgs),\n gas: '300000000000000',\n deposit: '10000000000000000000000',\n },\n ];\n actions.forEach(validateActionArgsWasm);\n\n return {\n actions,\n receiverId: accountId,\n };\n}\n\nexport async function buildOnchainEmailRecoveryActions(\n _deps: EmailRecoveryServiceDeps,\n input: { accountId: string; emailBlob: string },\n): Promise<{ actions: ActionArgsWasm[]; receiverId: string }> {\n const { accountId, emailBlob } = input;\n\n const actions: ActionArgsWasm[] = [\n {\n action_type: ActionType.FunctionCall,\n method_name: 'verify_email_onchain_and_recover',\n args: JSON.stringify({\n email_blob: emailBlob,\n }),\n gas: '300000000000000',\n deposit: '10000000000000000000000',\n },\n ];\n actions.forEach(validateActionArgsWasm);\n\n return {\n actions,\n receiverId: accountId,\n };\n}\n\nexport async function sendEmailRecoveryTransaction(\n deps: EmailRecoveryServiceDeps,\n args: {\n receiverId: string;\n actions: ActionArgsWasm[];\n label: string;\n },\n): Promise<EmailRecoveryResult> {\n const {\n relayerAccountId,\n relayerPrivateKey,\n nearClient,\n queueTransaction,\n fetchTxContext,\n signWithPrivateKey,\n getRelayerPublicKey,\n } = deps;\n\n const { receiverId, actions, label } = args;\n\n return queueTransaction(async () => {\n try {\n const relayerPublicKey = getRelayerPublicKey();\n const { nextNonce, blockHash } = await fetchTxContext(relayerAccountId, relayerPublicKey);\n\n const signed = await signWithPrivateKey({\n nearPrivateKey: relayerPrivateKey,\n signerAccountId: relayerAccountId,\n receiverId,\n nonce: nextNonce,\n blockHash,\n actions,\n });\n\n const result = await nearClient.sendTransaction(signed);\n\n const contractError = parseContractExecutionError(result, receiverId);\n if (contractError) {\n return {\n success: false,\n error: contractError,\n message: contractError,\n };\n }\n\n return {\n success: true,\n transactionHash: result.transaction.hash,\n message: label,\n };\n } catch (error: any) {\n const msg = error?.message || 'Unknown email recovery error';\n return {\n success: false,\n error: msg,\n message: msg,\n };\n }\n }, args.label);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"rpcCalls.js","names":["bytes: Uint8Array","aeadContext: EmailEncryptionContext","actions: ActionArgsWasm[]","error: any"],"sources":["../../../../src/server/email-recovery/rpcCalls.ts"],"sourcesContent":["import type { ActionArgsWasm } from '../../core/types/actions';\nimport { ActionType, validateActionArgsWasm } from '../../core/types/actions';\nimport { parseContractExecutionError } from '../core/errors';\nimport { hashRecoveryEmailForAccount, type EmailEncryptionContext } from './emailEncryptor';\nimport { parseHeaderValue, parseRecoverSubjectBindings } from './emailParsers';\nimport type { EmailRecoveryResult, EmailRecoveryServiceDeps, EmailRecoveryRequest } from './types';\n\nfunction normalizeSingleLine(input: string): string {\n return String(input || '')\n .replace(/[\\r\\n]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction formatEmailRecoveryTxError(error: unknown, receiverId: string): string {\n const kind = typeof (error as any)?.kind === 'string' ? String((error as any).kind) : '';\n const short = typeof (error as any)?.short === 'string' ? String((error as any).short) : '';\n const msg = normalizeSingleLine((error as any)?.message || String(error || ''));\n\n // Non-existent target account (common when the Subject includes a typo / unknown account).\n if (\n kind === 'AccountDoesNotExist' ||\n /AccountDoesNotExist/i.test(short) ||\n /AccountDoesNotExist/i.test(msg) ||\n /account does not exist/i.test(msg)\n ) {\n return `Account \"${receiverId}\" does not exist`;\n }\n\n // Invalid / malformed account id.\n if (\n /Invalid(Account|Receiver)Id/i.test(kind) ||\n /Invalid(Account|Receiver)Id/i.test(short) ||\n /Invalid(Account|Receiver)Id/i.test(msg)\n ) {\n return `Invalid NEAR account ID \"${receiverId}\"`;\n }\n\n // Prefer concise NearRpcError \"short\" where available.\n if (short && short !== 'TxExecutionError' && short !== 'RPC error') {\n return `Transaction failed (${short})`;\n }\n\n return msg || 'Unknown email recovery error';\n}\n\nexport async function getOutlayerEncryptionPublicKey(\n deps: Pick<EmailRecoveryServiceDeps, 'nearClient' | 'emailDkimVerifierContract'>,\n): Promise<Uint8Array> {\n const { nearClient, emailDkimVerifierContract } = deps;\n\n const result = await nearClient.view<{}, unknown>({\n account: emailDkimVerifierContract,\n method: 'get_outlayer_encryption_public_key',\n args: {},\n });\n\n if (typeof result !== 'string' || !result) {\n throw new Error('Outlayer encryption public key is not configured on EmailDkimVerifier');\n }\n\n let bytes: Uint8Array;\n try {\n const decoded = typeof Buffer !== 'undefined'\n ? Buffer.from(result, 'base64')\n : Uint8Array.from(atob(result), c => c.charCodeAt(0));\n bytes = decoded instanceof Uint8Array ? decoded : new Uint8Array(decoded);\n } catch (e) {\n throw new Error(`Failed to decode Outlayer email DKIM public key: ${(e as Error).message}`);\n }\n\n if (bytes.length !== 32) {\n throw new Error(`Outlayer email DKIM public key must be 32 bytes, got ${bytes.length}`);\n }\n\n return bytes;\n}\n\nexport async function buildEncryptedEmailRecoveryActions(\n deps: EmailRecoveryServiceDeps,\n input: {\n accountId: string;\n emailBlob: string;\n recipientPk: Uint8Array;\n encrypt: (args: {\n emailRaw: string;\n aeadContext: EmailEncryptionContext;\n recipientPk: Uint8Array;\n }) => Promise<{ envelope: { version: number; ephemeral_pub: string; nonce: string; ciphertext: string } }>;\n },\n): Promise<{ actions: ActionArgsWasm[]; receiverId: string }> {\n const {\n relayerAccountId,\n networkId,\n } = deps;\n const { accountId, emailBlob, recipientPk, encrypt } = input;\n\n const aeadContext: EmailEncryptionContext = {\n account_id: accountId,\n network_id: networkId,\n payer_account_id: relayerAccountId,\n };\n\n const { envelope } = await encrypt({\n emailRaw: emailBlob,\n aeadContext,\n recipientPk,\n });\n\n const bindings = parseRecoverSubjectBindings(emailBlob);\n if (!bindings) {\n throw new Error('Encrypted email recovery requires Subject: recover-<request_id> <accountId> ed25519:<new_public_key>');\n }\n if (bindings.accountId !== accountId) {\n throw new Error(`Encrypted email recovery subject accountId mismatch (expected \"${accountId}\", got \"${bindings.accountId}\")`);\n }\n\n const fromHeader = parseHeaderValue(emailBlob, 'from');\n if (!fromHeader) {\n throw new Error('Encrypted email recovery requires a From: header');\n }\n const expectedHashedEmail = hashRecoveryEmailForAccount({ recoveryEmail: fromHeader, accountId });\n\n const contractArgs = {\n encrypted_email_blob: envelope,\n aead_context: aeadContext,\n expected_hashed_email: expectedHashedEmail,\n expected_new_public_key: bindings.newPublicKey,\n request_id: bindings.requestId,\n };\n\n const actions: ActionArgsWasm[] = [\n {\n action_type: ActionType.FunctionCall,\n method_name: 'verify_encrypted_email_and_recover',\n args: JSON.stringify(contractArgs),\n gas: '300000000000000',\n deposit: '10000000000000000000000',\n },\n ];\n actions.forEach(validateActionArgsWasm);\n\n return {\n actions,\n receiverId: accountId,\n };\n}\n\nexport async function buildZkEmailRecoveryActions(\n deps: EmailRecoveryServiceDeps,\n input: {\n accountId: string;\n contractArgs: {\n proof: unknown;\n public_inputs: string[];\n account_id: string;\n new_public_key: string;\n request_id: string;\n from_email: string;\n timestamp: string;\n };\n },\n): Promise<{ actions: ActionArgsWasm[]; receiverId: string }> {\n const { accountId, contractArgs } = input;\n\n const actions: ActionArgsWasm[] = [\n {\n action_type: ActionType.FunctionCall,\n method_name: 'verify_zkemail_and_recover',\n args: JSON.stringify(contractArgs),\n gas: '300000000000000',\n deposit: '10000000000000000000000',\n },\n ];\n actions.forEach(validateActionArgsWasm);\n\n return {\n actions,\n receiverId: accountId,\n };\n}\n\nexport async function buildOnchainEmailRecoveryActions(\n _deps: EmailRecoveryServiceDeps,\n input: { accountId: string; emailBlob: string },\n): Promise<{ actions: ActionArgsWasm[]; receiverId: string }> {\n const { accountId, emailBlob } = input;\n\n const bindings = parseRecoverSubjectBindings(emailBlob);\n if (!bindings) {\n throw new Error('On-chain email recovery requires Subject: recover-<request_id> <accountId> ed25519:<new_public_key>');\n }\n if (bindings.accountId !== accountId) {\n throw new Error(`On-chain email recovery subject accountId mismatch (expected \"${accountId}\", got \"${bindings.accountId}\")`);\n }\n\n const actions: ActionArgsWasm[] = [\n {\n action_type: ActionType.FunctionCall,\n method_name: 'verify_email_onchain_and_recover',\n args: JSON.stringify({\n email_blob: emailBlob,\n request_id: bindings.requestId,\n }),\n gas: '300000000000000',\n deposit: '10000000000000000000000',\n },\n ];\n actions.forEach(validateActionArgsWasm);\n\n return {\n actions,\n receiverId: accountId,\n };\n}\n\nexport async function sendEmailRecoveryTransaction(\n deps: EmailRecoveryServiceDeps,\n args: {\n receiverId: string;\n actions: ActionArgsWasm[];\n label: string;\n },\n): Promise<EmailRecoveryResult> {\n const {\n relayerAccountId,\n relayerPrivateKey,\n nearClient,\n queueTransaction,\n fetchTxContext,\n signWithPrivateKey,\n getRelayerPublicKey,\n } = deps;\n\n const { receiverId, actions, label } = args;\n\n return queueTransaction(async () => {\n try {\n const relayerPublicKey = getRelayerPublicKey();\n const { nextNonce, blockHash } = await fetchTxContext(relayerAccountId, relayerPublicKey);\n\n const signed = await signWithPrivateKey({\n nearPrivateKey: relayerPrivateKey,\n signerAccountId: relayerAccountId,\n receiverId,\n nonce: nextNonce,\n blockHash,\n actions,\n });\n\n const result = await nearClient.sendTransaction(signed);\n\n const contractError = parseContractExecutionError(result, receiverId);\n if (contractError) {\n return {\n success: false,\n error: contractError,\n message: contractError,\n };\n }\n\n return {\n success: true,\n transactionHash: result.transaction.hash,\n message: label,\n };\n } catch (error: any) {\n const msg = formatEmailRecoveryTxError(error, receiverId);\n return {\n success: false,\n error: msg,\n message: msg,\n };\n }\n }, args.label);\n}\n"],"mappings":";;;;;;AAOA,SAAS,oBAAoB,OAAuB;AAClD,QAAO,OAAO,SAAS,IACpB,QAAQ,YAAY,KACpB,QAAQ,QAAQ,KAChB;;AAGL,SAAS,2BAA2B,OAAgB,YAA4B;CAC9E,MAAM,OAAO,OAAQ,OAAe,SAAS,WAAW,OAAQ,MAAc,QAAQ;CACtF,MAAM,QAAQ,OAAQ,OAAe,UAAU,WAAW,OAAQ,MAAc,SAAS;CACzF,MAAM,MAAM,oBAAqB,OAAe,WAAW,OAAO,SAAS;AAG3E,KACE,SAAS,yBACT,uBAAuB,KAAK,UAC5B,uBAAuB,KAAK,QAC5B,0BAA0B,KAAK,KAE/B,QAAO,YAAY,WAAW;AAIhC,KACE,+BAA+B,KAAK,SACpC,+BAA+B,KAAK,UACpC,+BAA+B,KAAK,KAEpC,QAAO,4BAA4B,WAAW;AAIhD,KAAI,SAAS,UAAU,sBAAsB,UAAU,YACrD,QAAO,uBAAuB,MAAM;AAGtC,QAAO,OAAO;;AAGhB,eAAsB,+BACpB,MACqB;CACrB,MAAM,EAAE,YAAY,8BAA8B;CAElD,MAAM,SAAS,MAAM,WAAW,KAAkB;EAChD,SAAS;EACT,QAAQ;EACR,MAAM;;AAGR,KAAI,OAAO,WAAW,YAAY,CAAC,OACjC,OAAM,IAAI,MAAM;CAGlB,IAAIA;AACJ,KAAI;EACF,MAAM,UAAU,OAAO,WAAW,cAC9B,OAAO,KAAK,QAAQ,YACpB,WAAW,KAAK,KAAK,UAAS,MAAK,EAAE,WAAW;AACpD,UAAQ,mBAAmB,aAAa,UAAU,IAAI,WAAW;UAC1D,GAAG;AACV,QAAM,IAAI,MAAM,oDAAqD,EAAY;;AAGnF,KAAI,MAAM,WAAW,GACnB,OAAM,IAAI,MAAM,wDAAwD,MAAM;AAGhF,QAAO;;AAGT,eAAsB,mCACpB,MACA,OAU4D;CAC5D,MAAM,EACJ,kBACA,cACE;CACJ,MAAM,EAAE,WAAW,WAAW,aAAa,YAAY;CAEvD,MAAMC,cAAsC;EAC1C,YAAY;EACZ,YAAY;EACZ,kBAAkB;;CAGpB,MAAM,EAAE,aAAa,MAAM,QAAQ;EACjC,UAAU;EACV;EACA;;CAGF,MAAM,WAAW,4BAA4B;AAC7C,KAAI,CAAC,SACH,OAAM,IAAI,MAAM;AAElB,KAAI,SAAS,cAAc,UACzB,OAAM,IAAI,MAAM,kEAAkE,UAAU,UAAU,SAAS,UAAU;CAG3H,MAAM,aAAa,iBAAiB,WAAW;AAC/C,KAAI,CAAC,WACH,OAAM,IAAI,MAAM;CAElB,MAAM,sBAAsB,4BAA4B;EAAE,eAAe;EAAY;;CAErF,MAAM,eAAe;EACnB,sBAAsB;EACtB,cAAc;EACd,uBAAuB;EACvB,yBAAyB,SAAS;EAClC,YAAY,SAAS;;CAGvB,MAAMC,UAA4B,CAChC;EACE,aAAa,WAAW;EACxB,aAAa;EACb,MAAM,KAAK,UAAU;EACrB,KAAK;EACL,SAAS;;AAGb,SAAQ,QAAQ;AAEhB,QAAO;EACL;EACA,YAAY;;;AAIhB,eAAsB,4BACpB,MACA,OAY4D;CAC5D,MAAM,EAAE,WAAW,iBAAiB;CAEpC,MAAMA,UAA4B,CAChC;EACE,aAAa,WAAW;EACxB,aAAa;EACb,MAAM,KAAK,UAAU;EACrB,KAAK;EACL,SAAS;;AAGb,SAAQ,QAAQ;AAEhB,QAAO;EACL;EACA,YAAY;;;AAIhB,eAAsB,iCACpB,OACA,OAC4D;CAC5D,MAAM,EAAE,WAAW,cAAc;CAEjC,MAAM,WAAW,4BAA4B;AAC7C,KAAI,CAAC,SACH,OAAM,IAAI,MAAM;AAElB,KAAI,SAAS,cAAc,UACzB,OAAM,IAAI,MAAM,iEAAiE,UAAU,UAAU,SAAS,UAAU;CAG1H,MAAMA,UAA4B,CAChC;EACE,aAAa,WAAW;EACxB,aAAa;EACb,MAAM,KAAK,UAAU;GACnB,YAAY;GACZ,YAAY,SAAS;;EAEvB,KAAK;EACL,SAAS;;AAGb,SAAQ,QAAQ;AAEhB,QAAO;EACL;EACA,YAAY;;;AAIhB,eAAsB,6BACpB,MACA,MAK8B;CAC9B,MAAM,EACJ,kBACA,mBACA,YACA,kBACA,gBACA,oBACA,wBACE;CAEJ,MAAM,EAAE,YAAY,SAAS,UAAU;AAEvC,QAAO,iBAAiB,YAAY;AAClC,MAAI;GACF,MAAM,mBAAmB;GACzB,MAAM,EAAE,WAAW,cAAc,MAAM,eAAe,kBAAkB;GAExE,MAAM,SAAS,MAAM,mBAAmB;IACtC,gBAAgB;IAChB,iBAAiB;IACjB;IACA,OAAO;IACP;IACA;;GAGF,MAAM,SAAS,MAAM,WAAW,gBAAgB;GAEhD,MAAM,gBAAgB,4BAA4B,QAAQ;AAC1D,OAAI,cACF,QAAO;IACL,SAAS;IACT,OAAO;IACP,SAAS;;AAIb,UAAO;IACL,SAAS;IACT,iBAAiB,OAAO,YAAY;IACpC,SAAS;;WAEJC,OAAY;GACnB,MAAM,MAAM,2BAA2B,OAAO;AAC9C,UAAO;IACL,SAAS;IACT,OAAO;IACP,SAAS;;;IAGZ,KAAK"}
@@ -334,6 +334,9 @@ function normalizeEmailAddress(input) {
334
334
  if (angleStart !== -1 && angleEnd > angleStart) return trimmed.slice(angleStart + 1, angleEnd).trim().toLowerCase();
335
335
  return trimmed.toLowerCase();
336
336
  }
337
+ function normalizeRejectReason(input) {
338
+ return String(input || "").replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
339
+ }
337
340
  function toLowercaseHeaderRecord(input) {
338
341
  const out = {};
339
342
  if (!input) return out;
@@ -409,7 +412,7 @@ function createCloudflareEmailHandler(service, opts = {}) {
409
412
  }
410
413
  if (!service.emailRecovery) {
411
414
  logger.warn("[email] rejecting: EmailRecoveryService not configured");
412
- message.setReject("Recovery relayer rejected email: email recovery service unavailable");
415
+ message.setReject("Email recovery relayer rejected email: email recovery service unavailable");
413
416
  return;
414
417
  }
415
418
  const result = await service.emailRecovery.requestEmailRecovery({
@@ -420,9 +423,11 @@ function createCloudflareEmailHandler(service, opts = {}) {
420
423
  if (!result?.success) {
421
424
  logger.warn("[email] recovery failed", {
422
425
  accountId: parsed.accountId,
423
- error: result?.error || "unknown"
426
+ error: result?.error || "unknown",
427
+ message: result?.message
424
428
  });
425
- message.setReject(`Email recovery relayer rejected email: ${result?.error || "recovery failed"}`);
429
+ const reason = normalizeRejectReason(result?.message || result?.error || "recovery failed");
430
+ message.setReject(`Email recovery relayer rejected email: ${reason}`);
426
431
  return;
427
432
  }
428
433
  logger.info("[email] recovery submitted", { accountId: parsed.accountId });