@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.
- package/dist/cjs/core/EmailRecovery/index.js +25 -0
- package/dist/cjs/core/EmailRecovery/index.js.map +1 -1
- package/dist/cjs/core/TatchiPasskey/emailRecovery.js +135 -77
- package/dist/cjs/core/TatchiPasskey/emailRecovery.js.map +1 -1
- package/dist/cjs/core/TatchiPasskey/index.js +2 -1
- package/dist/cjs/core/TatchiPasskey/index.js.map +1 -1
- package/dist/cjs/core/TatchiPasskey/linkDevice.js +2 -1
- package/dist/cjs/core/TatchiPasskey/linkDevice.js.map +1 -1
- package/dist/cjs/core/TatchiPasskey/scanDevice.js +5 -3
- package/dist/cjs/core/TatchiPasskey/scanDevice.js.map +1 -1
- package/dist/cjs/core/WalletIframe/client/router.js +1 -1
- package/dist/cjs/core/WalletIframe/client/router.js.map +1 -1
- package/dist/cjs/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js +3 -4
- package/dist/cjs/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js.map +1 -1
- package/dist/cjs/core/defaultConfigs.js +3 -7
- package/dist/cjs/core/defaultConfigs.js.map +1 -1
- package/dist/cjs/core/nearCrypto.js +29 -5
- package/dist/cjs/core/nearCrypto.js.map +1 -1
- package/dist/cjs/core/rpcCalls.js +56 -26
- package/dist/cjs/core/rpcCalls.js.map +1 -1
- package/dist/cjs/core/types/emailRecovery.js +33 -0
- package/dist/cjs/core/types/emailRecovery.js.map +1 -0
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/react/components/AccountMenuButton/{LinkedDevicesModal-CSSowiHP.css → LinkedDevicesModal-BRtht0XI.css} +1 -1
- package/dist/{esm/react/components/AccountMenuButton/LinkedDevicesModal-CSSowiHP.css.map → cjs/react/components/AccountMenuButton/LinkedDevicesModal-BRtht0XI.css.map} +1 -1
- package/dist/cjs/react/components/AccountMenuButton/{ProfileDropdown-CEPMZ1gY.css → ProfileDropdown-BG_6hcim.css} +1 -1
- package/dist/{esm/react/components/AccountMenuButton/ProfileDropdown-CEPMZ1gY.css.map → cjs/react/components/AccountMenuButton/ProfileDropdown-BG_6hcim.css.map} +1 -1
- package/dist/cjs/react/components/AccountMenuButton/{Web3AuthProfileButton-DopOg7Xc.css → Web3AuthProfileButton-k8_FAYFq.css} +1 -1
- package/dist/cjs/react/components/AccountMenuButton/{Web3AuthProfileButton-DopOg7Xc.css.map → Web3AuthProfileButton-k8_FAYFq.css.map} +1 -1
- package/dist/cjs/react/components/AccountMenuButton/icons/{TouchIcon-BQWentvJ.css → TouchIcon-C-RcGfr5.css} +1 -1
- package/dist/cjs/react/components/AccountMenuButton/icons/{TouchIcon-BQWentvJ.css.map → TouchIcon-C-RcGfr5.css.map} +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-DwrzWMYx.css → PasskeyAuthMenu-DKMiLeT9.css} +59 -4
- package/dist/cjs/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-DwrzWMYx.css.map → PasskeyAuthMenu-DKMiLeT9.css.map} +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/adapters/tatchi.js +1 -0
- package/dist/cjs/react/components/PasskeyAuthMenu/adapters/tatchi.js.map +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/client.js +30 -8
- package/dist/cjs/react/components/PasskeyAuthMenu/client.js.map +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/controller/useSDKEvents.js +22 -0
- package/dist/cjs/react/components/PasskeyAuthMenu/controller/useSDKEvents.js.map +1 -0
- package/dist/cjs/react/components/PasskeyAuthMenu/ui/ContentSwitcher.js +17 -4
- package/dist/cjs/react/components/PasskeyAuthMenu/ui/ContentSwitcher.js.map +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js +354 -154
- package/dist/cjs/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js.map +1 -1
- package/dist/cjs/react/components/{ShowQRCode-CCN4h6Uv.css → ShowQRCode-CB0UCQ_h.css} +1 -1
- package/dist/cjs/react/components/{ShowQRCode-CCN4h6Uv.css.map → ShowQRCode-CB0UCQ_h.css.map} +1 -1
- package/dist/cjs/react/context/useSDKFlowRuntime.js +183 -0
- package/dist/cjs/react/context/useSDKFlowRuntime.js.map +1 -0
- package/dist/cjs/react/context/useTatchiContextValue.js +24 -15
- package/dist/cjs/react/context/useTatchiContextValue.js.map +1 -1
- package/dist/cjs/react/context/useTatchiWithSdkFlow.js +96 -0
- package/dist/cjs/react/context/useTatchiWithSdkFlow.js.map +1 -0
- package/dist/cjs/react/sdk/src/core/EmailRecovery/index.js +26 -0
- package/dist/cjs/react/sdk/src/core/EmailRecovery/index.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/emailRecovery.js +135 -77
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/emailRecovery.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/index.js +2 -1
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/index.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/linkDevice.js +2 -1
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/linkDevice.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/scanDevice.js +5 -3
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/scanDevice.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/WalletIframe/client/router.js +1 -1
- package/dist/cjs/react/sdk/src/core/WalletIframe/client/router.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js +3 -4
- package/dist/cjs/react/sdk/src/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/defaultConfigs.js +3 -7
- package/dist/cjs/react/sdk/src/core/defaultConfigs.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/nearCrypto.js +29 -5
- package/dist/cjs/react/sdk/src/core/nearCrypto.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/rpcCalls.js +56 -26
- package/dist/cjs/react/sdk/src/core/rpcCalls.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/types/emailRecovery.js +33 -0
- package/dist/cjs/react/sdk/src/core/types/emailRecovery.js.map +1 -0
- package/dist/cjs/server/email-recovery/emailParsers.js +2 -1
- package/dist/cjs/server/email-recovery/emailParsers.js.map +1 -1
- package/dist/cjs/server/email-recovery/index.js +6 -6
- package/dist/cjs/server/email-recovery/index.js.map +1 -1
- package/dist/cjs/server/email-recovery/rpcCalls.js +22 -3
- package/dist/cjs/server/email-recovery/rpcCalls.js.map +1 -1
- package/dist/cjs/server/router/cloudflare.js +8 -3
- package/dist/cjs/server/router/cloudflare.js.map +1 -1
- package/dist/cjs/server/router/express.js.map +1 -1
- package/dist/cjs/server/sdk/src/core/defaultConfigs.js +2 -4
- package/dist/cjs/server/sdk/src/core/defaultConfigs.js.map +1 -1
- package/dist/cjs/server/sdk/src/core/nearCrypto.js +26 -7
- package/dist/cjs/server/sdk/src/core/nearCrypto.js.map +1 -1
- package/dist/esm/core/EmailRecovery/index.js +25 -1
- package/dist/esm/core/EmailRecovery/index.js.map +1 -1
- package/dist/esm/core/TatchiPasskey/emailRecovery.js +136 -78
- package/dist/esm/core/TatchiPasskey/emailRecovery.js.map +1 -1
- package/dist/esm/core/TatchiPasskey/index.js +2 -1
- package/dist/esm/core/TatchiPasskey/index.js.map +1 -1
- package/dist/esm/core/TatchiPasskey/linkDevice.js +2 -1
- package/dist/esm/core/TatchiPasskey/linkDevice.js.map +1 -1
- package/dist/esm/core/TatchiPasskey/scanDevice.js +5 -3
- package/dist/esm/core/TatchiPasskey/scanDevice.js.map +1 -1
- package/dist/esm/core/WalletIframe/client/router.js +1 -1
- package/dist/esm/core/WalletIframe/client/router.js.map +1 -1
- package/dist/esm/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js +2 -3
- package/dist/esm/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js.map +1 -1
- package/dist/esm/core/defaultConfigs.js +3 -7
- package/dist/esm/core/defaultConfigs.js.map +1 -1
- package/dist/esm/core/nearCrypto.js +24 -6
- package/dist/esm/core/nearCrypto.js.map +1 -1
- package/dist/esm/core/rpcCalls.js +56 -26
- package/dist/esm/core/rpcCalls.js.map +1 -1
- package/dist/esm/core/types/emailRecovery.js +26 -0
- package/dist/esm/core/types/emailRecovery.js.map +1 -0
- package/dist/esm/index.js +3 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/react/components/AccountMenuButton/{LinkedDevicesModal-CSSowiHP.css → LinkedDevicesModal-BRtht0XI.css} +1 -1
- package/dist/{cjs/react/components/AccountMenuButton/LinkedDevicesModal-CSSowiHP.css.map → esm/react/components/AccountMenuButton/LinkedDevicesModal-BRtht0XI.css.map} +1 -1
- package/dist/esm/react/components/AccountMenuButton/{ProfileDropdown-CEPMZ1gY.css → ProfileDropdown-BG_6hcim.css} +1 -1
- package/dist/{cjs/react/components/AccountMenuButton/ProfileDropdown-CEPMZ1gY.css.map → esm/react/components/AccountMenuButton/ProfileDropdown-BG_6hcim.css.map} +1 -1
- package/dist/esm/react/components/AccountMenuButton/{Web3AuthProfileButton-DopOg7Xc.css → Web3AuthProfileButton-k8_FAYFq.css} +1 -1
- package/dist/esm/react/components/AccountMenuButton/{Web3AuthProfileButton-DopOg7Xc.css.map → Web3AuthProfileButton-k8_FAYFq.css.map} +1 -1
- package/dist/esm/react/components/AccountMenuButton/icons/{TouchIcon-BQWentvJ.css → TouchIcon-C-RcGfr5.css} +1 -1
- package/dist/esm/react/components/AccountMenuButton/icons/{TouchIcon-BQWentvJ.css.map → TouchIcon-C-RcGfr5.css.map} +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-DwrzWMYx.css → PasskeyAuthMenu-DKMiLeT9.css} +59 -4
- package/dist/esm/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-DwrzWMYx.css.map → PasskeyAuthMenu-DKMiLeT9.css.map} +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/adapters/tatchi.js +1 -0
- package/dist/esm/react/components/PasskeyAuthMenu/adapters/tatchi.js.map +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/client.js +30 -8
- package/dist/esm/react/components/PasskeyAuthMenu/client.js.map +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/controller/useSDKEvents.js +20 -0
- package/dist/esm/react/components/PasskeyAuthMenu/controller/useSDKEvents.js.map +1 -0
- package/dist/esm/react/components/PasskeyAuthMenu/ui/ContentSwitcher.js +17 -4
- package/dist/esm/react/components/PasskeyAuthMenu/ui/ContentSwitcher.js.map +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js +354 -154
- package/dist/esm/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js.map +1 -1
- package/dist/esm/react/components/{ShowQRCode-CCN4h6Uv.css → ShowQRCode-CB0UCQ_h.css} +1 -1
- package/dist/esm/react/components/{ShowQRCode-CCN4h6Uv.css.map → ShowQRCode-CB0UCQ_h.css.map} +1 -1
- package/dist/esm/react/context/useSDKFlowRuntime.js +181 -0
- package/dist/esm/react/context/useSDKFlowRuntime.js.map +1 -0
- package/dist/esm/react/context/useTatchiContextValue.js +25 -16
- package/dist/esm/react/context/useTatchiContextValue.js.map +1 -1
- package/dist/esm/react/context/useTatchiWithSdkFlow.js +94 -0
- package/dist/esm/react/context/useTatchiWithSdkFlow.js.map +1 -0
- package/dist/esm/react/sdk/src/core/EmailRecovery/index.js +25 -1
- package/dist/esm/react/sdk/src/core/EmailRecovery/index.js.map +1 -1
- package/dist/esm/react/sdk/src/core/TatchiPasskey/emailRecovery.js +136 -78
- package/dist/esm/react/sdk/src/core/TatchiPasskey/emailRecovery.js.map +1 -1
- package/dist/esm/react/sdk/src/core/TatchiPasskey/index.js +2 -1
- package/dist/esm/react/sdk/src/core/TatchiPasskey/index.js.map +1 -1
- package/dist/esm/react/sdk/src/core/TatchiPasskey/linkDevice.js +2 -1
- package/dist/esm/react/sdk/src/core/TatchiPasskey/linkDevice.js.map +1 -1
- package/dist/esm/react/sdk/src/core/TatchiPasskey/scanDevice.js +5 -3
- package/dist/esm/react/sdk/src/core/TatchiPasskey/scanDevice.js.map +1 -1
- package/dist/esm/react/sdk/src/core/WalletIframe/client/router.js +1 -1
- package/dist/esm/react/sdk/src/core/WalletIframe/client/router.js.map +1 -1
- package/dist/esm/react/sdk/src/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js +2 -3
- package/dist/esm/react/sdk/src/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js.map +1 -1
- package/dist/esm/react/sdk/src/core/defaultConfigs.js +3 -7
- package/dist/esm/react/sdk/src/core/defaultConfigs.js.map +1 -1
- package/dist/esm/react/sdk/src/core/nearCrypto.js +24 -6
- package/dist/esm/react/sdk/src/core/nearCrypto.js.map +1 -1
- package/dist/esm/react/sdk/src/core/rpcCalls.js +56 -26
- package/dist/esm/react/sdk/src/core/rpcCalls.js.map +1 -1
- package/dist/esm/react/sdk/src/core/types/emailRecovery.js +26 -0
- package/dist/esm/react/sdk/src/core/types/emailRecovery.js.map +1 -0
- package/dist/esm/react/styles/styles.css +58 -3
- package/dist/esm/sdk/{defaultConfigs-DpslkAQd.js → defaultConfigs-CfQDV-ya.js} +3 -7
- package/dist/esm/sdk/{getDeviceNumber-fXizNGQl.js → getDeviceNumber-BpernPnM.js} +4 -8
- package/dist/esm/sdk/getDeviceNumber-BpernPnM.js.map +1 -0
- package/dist/esm/sdk/offline-export-app.js +23 -6
- package/dist/esm/sdk/offline-export-app.js.map +1 -1
- package/dist/esm/sdk/{router-DuGYOd3G.js → router-BWtacLJg.js} +1 -1
- package/dist/esm/sdk/{rpcCalls-BQrJMTdg.js → rpcCalls-CYGJSCgm.js} +3 -3
- package/dist/esm/sdk/{rpcCalls-YVeUVMk2.js → rpcCalls-DZZSa-sk.js} +57 -27
- package/dist/esm/sdk/{transactions-bqaAwL4k.js → transactions-Cn9xTWlK.js} +2 -2
- package/dist/esm/sdk/{transactions-bqaAwL4k.js.map → transactions-Cn9xTWlK.js.map} +1 -1
- package/dist/esm/sdk/{transactions-BalIhtJ9.js → transactions-DfdwDQCn.js} +1 -1
- package/dist/esm/sdk/wallet-iframe-host.js +660 -590
- package/dist/esm/server/email-recovery/emailParsers.js +3 -1
- package/dist/esm/server/email-recovery/emailParsers.js.map +1 -1
- package/dist/esm/server/email-recovery/index.js +6 -6
- package/dist/esm/server/email-recovery/index.js.map +1 -1
- package/dist/esm/server/email-recovery/rpcCalls.js +22 -3
- package/dist/esm/server/email-recovery/rpcCalls.js.map +1 -1
- package/dist/esm/server/router/cloudflare.js +8 -3
- package/dist/esm/server/router/cloudflare.js.map +1 -1
- package/dist/esm/server/router/express.js.map +1 -1
- package/dist/esm/server/sdk/src/core/defaultConfigs.js +2 -4
- package/dist/esm/server/sdk/src/core/defaultConfigs.js.map +1 -1
- package/dist/esm/server/sdk/src/core/nearCrypto.js +26 -8
- package/dist/esm/server/sdk/src/core/nearCrypto.js.map +1 -1
- package/dist/esm/wasm_vrf_worker/pkg/wasm_vrf_worker_bg.wasm +0 -0
- package/dist/types/src/core/EmailRecovery/index.d.ts +8 -0
- package/dist/types/src/core/EmailRecovery/index.d.ts.map +1 -1
- package/dist/types/src/core/TatchiPasskey/emailRecovery.d.ts +8 -5
- package/dist/types/src/core/TatchiPasskey/emailRecovery.d.ts.map +1 -1
- package/dist/types/src/core/TatchiPasskey/index.d.ts +1 -1
- package/dist/types/src/core/TatchiPasskey/index.d.ts.map +1 -1
- package/dist/types/src/core/TatchiPasskey/scanDevice.d.ts.map +1 -1
- package/dist/types/src/core/WalletIframe/TatchiPasskeyIframe.d.ts +1 -1
- package/dist/types/src/core/WalletIframe/TatchiPasskeyIframe.d.ts.map +1 -1
- package/dist/types/src/core/WalletIframe/client/router.d.ts +1 -1
- package/dist/types/src/core/WalletIframe/client/router.d.ts.map +1 -1
- package/dist/types/src/core/WalletIframe/shared/messages.d.ts +1 -1
- package/dist/types/src/core/WalletIframe/shared/messages.d.ts.map +1 -1
- package/dist/types/src/core/WebAuthnManager/SignerWorkerManager/handlers/validation.d.ts +2 -1
- package/dist/types/src/core/WebAuthnManager/SignerWorkerManager/handlers/validation.d.ts.map +1 -1
- package/dist/types/src/core/defaultConfigs.d.ts.map +1 -1
- package/dist/types/src/core/nearCrypto.d.ts +14 -0
- package/dist/types/src/core/nearCrypto.d.ts.map +1 -1
- package/dist/types/src/core/rpcCalls.d.ts +11 -8
- package/dist/types/src/core/rpcCalls.d.ts.map +1 -1
- package/dist/types/src/core/types/emailRecovery.d.ts +10 -0
- package/dist/types/src/core/types/emailRecovery.d.ts.map +1 -0
- package/dist/types/src/core/types/index.d.ts +1 -0
- package/dist/types/src/core/types/index.d.ts.map +1 -1
- package/dist/types/src/core/types/tatchi.d.ts +0 -4
- package/dist/types/src/core/types/tatchi.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/adapters/tatchi.d.ts +2 -0
- package/dist/types/src/react/components/PasskeyAuthMenu/adapters/tatchi.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/client.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/controller/useSDKEvents.d.ts +10 -0
- package/dist/types/src/react/components/PasskeyAuthMenu/controller/useSDKEvents.d.ts.map +1 -0
- package/dist/types/src/react/components/PasskeyAuthMenu/types.d.ts +8 -3
- package/dist/types/src/react/components/PasskeyAuthMenu/types.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/ui/ContentSwitcher.d.ts +2 -0
- package/dist/types/src/react/components/PasskeyAuthMenu/ui/ContentSwitcher.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.d.ts +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.d.ts.map +1 -1
- package/dist/types/src/react/context/useSDKFlowRuntime.d.ts +10 -0
- package/dist/types/src/react/context/useSDKFlowRuntime.d.ts.map +1 -0
- package/dist/types/src/react/context/useTatchiContextValue.d.ts.map +1 -1
- package/dist/types/src/react/context/useTatchiWithSdkFlow.d.ts +9 -0
- package/dist/types/src/react/context/useTatchiWithSdkFlow.d.ts.map +1 -0
- package/dist/types/src/react/types.d.ts +31 -0
- package/dist/types/src/react/types.d.ts.map +1 -1
- package/dist/types/src/server/email-recovery/emailParsers.d.ts.map +1 -1
- package/dist/types/src/server/email-recovery/index.d.ts +5 -6
- package/dist/types/src/server/email-recovery/index.d.ts.map +1 -1
- package/dist/types/src/server/email-recovery/rpcCalls.d.ts +1 -0
- package/dist/types/src/server/email-recovery/rpcCalls.d.ts.map +1 -1
- package/dist/types/src/server/router/cloudflare-adaptor.d.ts.map +1 -1
- package/dist/workers/wasm_vrf_worker_bg.wasm +0 -0
- package/package.json +1 -1
- package/dist/esm/sdk/getDeviceNumber-fXizNGQl.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nearCrypto.js","names":["ed25519"],"sources":["../../../../../../src/core/nearCrypto.ts"],"sourcesContent":["import * as ed25519 from '@noble/ed25519';\nimport bs58 from 'bs58';\n\
|
|
1
|
+
{"version":3,"file":"nearCrypto.js","names":["ed25519"],"sources":["../../../../../../src/core/nearCrypto.ts"],"sourcesContent":["import * as ed25519 from '@noble/ed25519';\nimport bs58 from 'bs58';\n\nexport const NEAR_ED25519_KEY_PREFIX = 'ed25519:' as const;\n\n/**\n * Ensure a key string has the NEAR Ed25519 prefix (`ed25519:`).\n *\n * - Accepts either `ed25519:<base58>` or a bare `<base58>` string.\n * - Canonicalizes `ED25519:` → `ed25519:`.\n * - If a different prefix is present (e.g. `secp256k1:`), returns the input unchanged.\n */\nexport function ensureEd25519Prefix(value: string): string {\n const raw = String(value || '').trim();\n if (!raw) return '';\n\n // If it already looks like \"<prefix>:...\", only normalize when the prefix is ed25519.\n if (/^[a-z0-9_]+:/i.test(raw)) {\n if (/^ed25519:/i.test(raw)) {\n return `${NEAR_ED25519_KEY_PREFIX}${raw.replace(/^ed25519:/i, '')}`;\n }\n return raw;\n }\n\n return `${NEAR_ED25519_KEY_PREFIX}${raw}`;\n}\n\n/**\n * Remove the NEAR Ed25519 prefix (`ed25519:`) if present.\n * Useful for comparing keys where one side may omit the prefix.\n */\nexport function stripEd25519Prefix(value: string): string {\n const raw = String(value || '').trim();\n return raw.replace(/^ed25519:/i, '');\n}\n\n/**\n * Creates a NEAR-compatible Ed25519 keypair formatted as strings:\n * - publicKey: 'ed25519:' + base58(pub)\n * - privateKey: 'ed25519:' + base58(seed(32) | pub(32))\n */\nexport async function createNearKeypair(): Promise<{ publicKey: string; privateKey: string }> {\n const seed = ed25519.utils.randomPrivateKey(); // 32 bytes\n const pub = await ed25519.getPublicKeyAsync(seed); // 32 bytes\n\n const secret = new Uint8Array(64);\n secret.set(seed, 0);\n secret.set(pub, 32);\n\n const publicKey = ensureEd25519Prefix(bs58.encode(pub));\n const privateKey = ensureEd25519Prefix(bs58.encode(secret));\n return { publicKey, privateKey };\n}\n\n/** Parse NEAR public key string ('ed25519:...') into 32-byte Uint8Array */\nexport function parseNearPublicKey(str: string): Uint8Array {\n const b58 = stripEd25519Prefix(str);\n const bytes = bs58.decode(b58);\n if (bytes.length !== 32) {\n throw new Error(`Invalid NEAR public key length: ${bytes.length}`);\n }\n return new Uint8Array(bytes);\n}\n\n/**\n * Parse NEAR secret key string ('ed25519:...') into its components.\n * NEAR secrets are 64 bytes: seed(32) | pub(32)\n */\nexport function parseNearSecretKey(str: string): { seed: Uint8Array; pub: Uint8Array } {\n const b58 = stripEd25519Prefix(str);\n const bytes = bs58.decode(b58);\n if (bytes.length !== 64) {\n throw new Error(`Invalid NEAR secret key length: ${bytes.length}`);\n }\n const all = new Uint8Array(bytes);\n const seed = all.slice(0, 32);\n const pub = all.slice(32, 64);\n return { seed, pub };\n}\n\n/** Convert raw 32-byte public key to NEAR string ('ed25519:...') */\nexport function toPublicKeyString(pub: Uint8Array): string {\n if (!(pub?.length === 32)) {\n throw new Error('Public key must be 32 bytes');\n }\n return ensureEd25519Prefix(bs58.encode(pub));\n}\n\n/** Convert raw seed(32) + pub(32) to NEAR secret string ('ed25519:...') */\nexport function toSecretKeyString(seed: Uint8Array, pub: Uint8Array): string {\n if (!(seed?.length === 32)) {\n throw new Error('Seed must be 32 bytes');\n }\n if (!(pub?.length === 32)) {\n throw new Error('Public key must be 32 bytes');\n }\n const secret = new Uint8Array(64);\n secret.set(seed, 0);\n secret.set(pub, 32);\n return ensureEd25519Prefix(bs58.encode(secret));\n}\n"],"mappings":";;;;;;;;;;;;;;AAYA,SAAgB,oBAAoB,OAAuB;CACzD,MAAM,MAAM,OAAO,SAAS,IAAI;AAChC,KAAI,CAAC,IAAK,QAAO;AAGjB,KAAI,gBAAgB,KAAK,MAAM;AAC7B,MAAI,aAAa,KAAK,KACpB,QAAO,GAAG,0BAA0B,IAAI,QAAQ,cAAc;AAEhE,SAAO;;AAGT,QAAO,GAAG,0BAA0B;;;;;;;AAiBtC,eAAsB,oBAAwE;CAC5F,MAAM,OAAOA,gBAAQ,MAAM;CAC3B,MAAM,MAAM,MAAMA,gBAAQ,kBAAkB;CAE5C,MAAM,SAAS,IAAI,WAAW;AAC9B,QAAO,IAAI,MAAM;AACjB,QAAO,IAAI,KAAK;CAEhB,MAAM,YAAY,oBAAoB,aAAK,OAAO;CAClD,MAAM,aAAa,oBAAoB,aAAK,OAAO;AACnD,QAAO;EAAE;EAAW;;;;;CAhDT,0BAA0B"}
|
|
@@ -8,12 +8,26 @@ const require_actions = require('./types/actions.js');
|
|
|
8
8
|
const require_rpc = require('./types/rpc.js');
|
|
9
9
|
|
|
10
10
|
//#region src/core/rpcCalls.ts
|
|
11
|
-
async function
|
|
12
|
-
|
|
13
|
-
account:
|
|
14
|
-
method:
|
|
11
|
+
async function getEmailRecoveryAttempt(nearClient, accountId, requestId) {
|
|
12
|
+
const raw = await nearClient.view({
|
|
13
|
+
account: accountId,
|
|
14
|
+
method: "get_recovery_attempt",
|
|
15
15
|
args: { request_id: requestId }
|
|
16
16
|
});
|
|
17
|
+
if (!raw) return null;
|
|
18
|
+
const statusRaw = raw.status;
|
|
19
|
+
const status = (() => {
|
|
20
|
+
if (typeof statusRaw === "string") return statusRaw.trim();
|
|
21
|
+
if (statusRaw && typeof statusRaw === "object") {
|
|
22
|
+
const keys = Object.keys(statusRaw);
|
|
23
|
+
if (keys.length === 1) return String(keys[0] || "").trim();
|
|
24
|
+
}
|
|
25
|
+
return "";
|
|
26
|
+
})();
|
|
27
|
+
return {
|
|
28
|
+
...raw,
|
|
29
|
+
status
|
|
30
|
+
};
|
|
17
31
|
}
|
|
18
32
|
/**
|
|
19
33
|
* Query the contract to get the account linked to a device public key
|
|
@@ -225,14 +239,26 @@ async function syncAuthenticatorsContractCall(nearClient, contractId, accountId)
|
|
|
225
239
|
return [];
|
|
226
240
|
}
|
|
227
241
|
}
|
|
242
|
+
async function hasDeployedContractCode(nearClient, accountId) {
|
|
243
|
+
try {
|
|
244
|
+
const account = await nearClient.viewAccount(accountId);
|
|
245
|
+
const codeHash = account?.code_hash;
|
|
246
|
+
const globalContractHash = account?.global_contract_hash;
|
|
247
|
+
const globalContractAccountId = account?.global_contract_account_id;
|
|
248
|
+
const hasLocalCode = typeof codeHash === "string" && codeHash !== EMPTY_NEAR_CODE_HASH;
|
|
249
|
+
const hasGlobalCode = typeof globalContractHash === "string" && globalContractHash.trim().length > 0 || typeof globalContractAccountId === "string" && globalContractAccountId.trim().length > 0;
|
|
250
|
+
return hasLocalCode || hasGlobalCode;
|
|
251
|
+
} catch {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
228
255
|
/**
|
|
229
256
|
* Fetch on-chain recovery email hashes from the per-account contract.
|
|
230
257
|
* Returns [] when no contract is deployed or on failure.
|
|
231
258
|
*/
|
|
232
259
|
async function getRecoveryEmailHashesContractCall(nearClient, accountId) {
|
|
233
260
|
try {
|
|
234
|
-
const
|
|
235
|
-
const hasContract = !!code && code.byteLength > 0;
|
|
261
|
+
const hasContract = await hasDeployedContractCode(nearClient, accountId);
|
|
236
262
|
if (!hasContract) return [];
|
|
237
263
|
const hashes = await nearClient.view({
|
|
238
264
|
account: accountId,
|
|
@@ -241,7 +267,6 @@ async function getRecoveryEmailHashesContractCall(nearClient, accountId) {
|
|
|
241
267
|
});
|
|
242
268
|
return Array.isArray(hashes) ? hashes : [];
|
|
243
269
|
} catch (error) {
|
|
244
|
-
console.error("[rpcCalls] Failed to fetch recovery email hashes", error);
|
|
245
270
|
return [];
|
|
246
271
|
}
|
|
247
272
|
}
|
|
@@ -250,27 +275,24 @@ async function getRecoveryEmailHashesContractCall(nearClient, accountId) {
|
|
|
250
275
|
* If the per-account contract is missing, deploy/attach the global recoverer via `init_email_recovery`.
|
|
251
276
|
*/
|
|
252
277
|
async function buildSetRecoveryEmailsActions(nearClient, accountId, recoveryEmailHashes, contracts = require_defaultConfigs.DEFAULT_EMAIL_RECOVERY_CONTRACTS) {
|
|
253
|
-
|
|
254
|
-
try {
|
|
255
|
-
const code = await nearClient.viewCode(accountId);
|
|
256
|
-
hasContract = !!code && code.byteLength > 0;
|
|
257
|
-
} catch {
|
|
258
|
-
hasContract = false;
|
|
259
|
-
}
|
|
278
|
+
const hasContract = await hasDeployedContractCode(nearClient, accountId);
|
|
260
279
|
const { emailRecovererGlobalContract, zkEmailVerifierContract, emailDkimVerifierContract } = contracts;
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
280
|
+
let shouldInit = !hasContract;
|
|
281
|
+
if (!shouldInit) try {
|
|
282
|
+
await nearClient.view({
|
|
283
|
+
account: accountId,
|
|
284
|
+
method: "get_recovery_emails",
|
|
285
|
+
args: {}
|
|
286
|
+
});
|
|
287
|
+
} catch (err) {
|
|
288
|
+
const msg = require_errors.errorMessage(err);
|
|
289
|
+
if (/Cannot deserialize the contract state/i.test(msg) || /CodeDoesNotExist/i.test(msg) || /MethodNotFound/i.test(msg)) shouldInit = true;
|
|
290
|
+
}
|
|
291
|
+
const base = [{
|
|
271
292
|
type: require_actions.ActionType.UseGlobalContract,
|
|
272
293
|
accountId: emailRecovererGlobalContract
|
|
273
|
-
}
|
|
294
|
+
}];
|
|
295
|
+
return shouldInit ? [...base, {
|
|
274
296
|
type: require_actions.ActionType.FunctionCall,
|
|
275
297
|
methodName: "init_email_recovery",
|
|
276
298
|
args: {
|
|
@@ -281,6 +303,12 @@ async function buildSetRecoveryEmailsActions(nearClient, accountId, recoveryEmai
|
|
|
281
303
|
},
|
|
282
304
|
gas: "80000000000000",
|
|
283
305
|
deposit: "0"
|
|
306
|
+
}] : [...base, {
|
|
307
|
+
type: require_actions.ActionType.FunctionCall,
|
|
308
|
+
methodName: "set_recovery_emails",
|
|
309
|
+
args: { recovery_emails: recoveryEmailHashes },
|
|
310
|
+
gas: "80000000000000",
|
|
311
|
+
deposit: "0"
|
|
284
312
|
}];
|
|
285
313
|
}
|
|
286
314
|
/**
|
|
@@ -386,6 +414,7 @@ async function verifyAuthenticationResponse(relayServerUrl, routePath, sessionKi
|
|
|
386
414
|
};
|
|
387
415
|
}
|
|
388
416
|
}
|
|
417
|
+
var EMPTY_NEAR_CODE_HASH;
|
|
389
418
|
var init_rpcCalls = require_rolldown_runtime.__esm({ "src/core/rpcCalls.ts": (() => {
|
|
390
419
|
require_sdkSentEvents.init_sdkSentEvents();
|
|
391
420
|
require_actions.init_actions();
|
|
@@ -393,6 +422,7 @@ var init_rpcCalls = require_rolldown_runtime.__esm({ "src/core/rpcCalls.ts": (()
|
|
|
393
422
|
require_encoders.init_encoders();
|
|
394
423
|
require_errors.init_errors();
|
|
395
424
|
require_defaultConfigs.init_defaultConfigs();
|
|
425
|
+
EMPTY_NEAR_CODE_HASH = "11111111111111111111111111111111";
|
|
396
426
|
}) });
|
|
397
427
|
|
|
398
428
|
//#endregion
|
|
@@ -403,7 +433,7 @@ exports.executeDeviceLinkingContractCalls = executeDeviceLinkingContractCalls;
|
|
|
403
433
|
exports.getAuthenticatorsByUser = getAuthenticatorsByUser;
|
|
404
434
|
exports.getCredentialIdsContractCall = getCredentialIdsContractCall;
|
|
405
435
|
exports.getDeviceLinkingAccountContractCall = getDeviceLinkingAccountContractCall;
|
|
406
|
-
exports.
|
|
436
|
+
exports.getEmailRecoveryAttempt = getEmailRecoveryAttempt;
|
|
407
437
|
exports.getRecoveryEmailHashesContractCall = getRecoveryEmailHashesContractCall;
|
|
408
438
|
Object.defineProperty(exports, 'init_rpcCalls', {
|
|
409
439
|
enumerable: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpcCalls.js","names":["error: any","ActionType","ActionPhase","DeviceLinkingPhase","DeviceLinkingStatus","addKeyTxResult: FinalExecutionOutcome","storeDeviceLinkingTxResult: FinalExecutionOutcome","DEFAULT_WAIT_STATUS","txError: any","base64UrlEncode","DEFAULT_EMAIL_RECOVERY_CONTRACTS","base64UrlDecode","err: unknown","errorMessage"],"sources":["../../../../../../src/core/rpcCalls.ts"],"sourcesContent":["/**\n * Consolidated NEAR Contract Calls\n *\n * This file contains all the NEAR contract calls made to the web3authn contract\n * throughout the passkey SDK. It provides a centralized location for all\n * contract interactions and makes it easier to maintain and update contract\n * call patterns.\n */\n\nimport type { FinalExecutionOutcome } from '@near-js/types';\nimport type { NearClient, SignedTransaction } from './NearClient';\nimport type { ContractStoredAuthenticator } from './TatchiPasskey/recoverAccount';\nimport type { PasskeyManagerContext } from './TatchiPasskey';\nimport type { AccountId } from './types/accountIds';\nimport type { DeviceLinkingSSEEvent } from './types/sdkSentEvents';\nimport type {\n StoredAuthenticator,\n WebAuthnRegistrationCredential,\n WebAuthnAuthenticationCredential\n} from './types/webauthn';\n\nimport { ActionPhase, DeviceLinkingPhase, DeviceLinkingStatus } from './types/sdkSentEvents';\nimport { ActionType, type ActionArgs } from './types/actions';\nimport { VRFChallenge } from './types/vrf-worker';\nimport { DEFAULT_WAIT_STATUS, TransactionContext } from './types/rpc';\nimport type { AuthenticatorOptions } from './types/authenticatorOptions';\nimport type { ConfirmationConfig } from './types/signer-worker';\nimport { base64UrlDecode, base64UrlEncode } from '../utils/encoders';\nimport { errorMessage } from '../utils/errors';\nimport type { EmailRecoveryContracts } from './types/tatchi';\nimport { DEFAULT_EMAIL_RECOVERY_CONTRACTS } from './defaultConfigs';\n\n// ===========================\n// CONTRACT CALL RESPONSES\n// ===========================\n\nexport interface DeviceLinkingResult {\n linkedAccountId: string;\n deviceNumber: number;\n}\n\nexport interface CredentialIdsResult {\n credentialIds: string[];\n}\n\nexport interface AuthenticatorsResult {\n authenticators: Array<[string, ContractStoredAuthenticator]>;\n}\n\nexport type EmailRecoveryVerificationResult = {\n verified: boolean;\n account_id?: string;\n new_public_key?: string;\n transaction_hash?: string;\n error_code?: string;\n error_message?: string;\n};\n\nexport async function getEmailRecoveryVerificationResult(\n nearClient: NearClient,\n dkimVerifierAccountId: string,\n verificationViewMethod: string,\n requestId: string\n): Promise<EmailRecoveryVerificationResult | null> {\n return await nearClient.view<{ request_id: string }, EmailRecoveryVerificationResult | null>({\n account: dkimVerifierAccountId,\n method: verificationViewMethod,\n args: { request_id: requestId },\n });\n}\n\n// ===========================\n// DEVICE LINKING CONTRACT CALLS\n// ===========================\n\n/**\n * Query the contract to get the account linked to a device public key\n * Used in device linking flow to check if a device key has been added\n *\n * NEAR does not provide a way to lookup the AccountID an access key has access to.\n * So we store a temporary mapping in the contract to lookup pubkey -> account ID.\n */\nexport async function getDeviceLinkingAccountContractCall(\n nearClient: NearClient,\n contractId: string,\n devicePublicKey: string\n): Promise<DeviceLinkingResult | null> {\n try {\n const result = await nearClient.callFunction<\n { device_public_key: string },\n [string, number | string]\n >(\n contractId,\n 'get_device_linking_account',\n { device_public_key: devicePublicKey }\n );\n\n // Handle different result formats\n if (result && Array.isArray(result) && result.length >= 2) {\n const [linkedAccountId, deviceNumberRaw] = result;\n const deviceNumber = Number(deviceNumberRaw);\n if (!Number.isSafeInteger(deviceNumber) || deviceNumber < 0) {\n console.warn(\n 'Invalid deviceNumber returned from get_device_linking_account:',\n deviceNumberRaw\n );\n return null;\n }\n return {\n linkedAccountId,\n deviceNumber\n };\n }\n\n return null;\n } catch (error: any) {\n console.warn('Failed to get device linking account:', error.message);\n return null;\n }\n}\n\n// ===========================\n// DEVICE LINKING TRANSACTION CALLS\n// ===========================\n\n/**\n * Execute device1's linking transactions (AddKey + Contract mapping)\n * This function signs and broadcasts both transactions required for device linking\n */\nexport async function executeDeviceLinkingContractCalls({\n context,\n device1AccountId,\n device2PublicKey,\n nextNonce,\n nextNextNonce,\n nextNextNextNonce,\n txBlockHash,\n vrfChallenge,\n onEvent,\n confirmationConfigOverride,\n confirmerText,\n}: {\n context: PasskeyManagerContext,\n device1AccountId: AccountId,\n device2PublicKey: string,\n nextNonce: string,\n nextNextNonce: string,\n nextNextNextNonce: string,\n txBlockHash: string,\n vrfChallenge: VRFChallenge,\n onEvent?: (event: DeviceLinkingSSEEvent) => void;\n confirmationConfigOverride?: Partial<ConfirmationConfig>;\n confirmerText?: { title?: string; body?: string };\n}): Promise<{\n addKeyTxResult: FinalExecutionOutcome;\n storeDeviceLinkingTxResult: FinalExecutionOutcome;\n signedDeleteKeyTransaction: SignedTransaction\n}> {\n\n // Sign three transactions with one PRF authentication\n const signedTransactions = await context.webAuthnManager.signTransactionsWithActions({\n rpcCall: {\n contractId: context.webAuthnManager.tatchiPasskeyConfigs.contractId,\n nearRpcUrl: context.webAuthnManager.tatchiPasskeyConfigs.nearRpcUrl,\n nearAccountId: device1AccountId\n },\n confirmationConfigOverride,\n title: confirmerText?.title,\n body: confirmerText?.body,\n transactions: [\n // Transaction 1: AddKey - Add Device2's key to Device1's account\n {\n receiverId: device1AccountId,\n actions: [{\n action_type: ActionType.AddKey,\n public_key: device2PublicKey,\n access_key: JSON.stringify({\n // NEAR-style AccessKey JSON shape, matching near-api-js:\n // { nonce: number, permission: { FullAccess: {} } }\n nonce: 0,\n permission: { FullAccess: {} },\n }),\n }],\n nonce: nextNonce,\n },\n // Transaction 2: Store temporary mapping in contract so Device2 can lookup Device1's accountID.\n {\n receiverId: context.webAuthnManager.tatchiPasskeyConfigs.contractId,\n actions: [{\n action_type: ActionType.FunctionCall,\n method_name: 'store_device_linking_mapping',\n args: JSON.stringify({\n device_public_key: device2PublicKey,\n target_account_id: device1AccountId,\n }),\n gas: '30000000000000', // 30 TGas for device linking with yield promise automatic cleanup\n deposit: '0'\n }],\n nonce: nextNextNonce,\n },\n // Transaction 3: Remove Device2's temporary key if it fails to complete linking after a timeout\n {\n receiverId: device1AccountId,\n actions: [{\n action_type: ActionType.DeleteKey,\n public_key: device2PublicKey\n }],\n nonce: nextNextNextNonce,\n }\n ],\n onEvent: (progress) => {\n // Bridge all action progress events to the parent so the wallet iframe overlay\n // can expand during user confirmation in wallet-iframe mode.\n try { onEvent?.(progress as any); } catch { }\n // Keep existing mapping for device linking semantics; surface signing as a loading state\n if (progress.phase == ActionPhase.STEP_6_TRANSACTION_SIGNING_COMPLETE) {\n onEvent?.({\n step: 3,\n phase: DeviceLinkingPhase.STEP_3_AUTHORIZATION,\n status: DeviceLinkingStatus.PROGRESS,\n message: progress.message || 'Transaction signing in progress...'\n })\n }\n }\n });\n\n if (!signedTransactions[0].signedTransaction) {\n throw new Error('AddKey transaction signing failed');\n }\n if (!signedTransactions[1].signedTransaction) {\n throw new Error('Contract mapping transaction signing failed');\n }\n if (!signedTransactions[2].signedTransaction) {\n throw new Error('DeleteKey transaction signing failed');\n }\n\n // Broadcast just the first 2 transactions: addKey and store device linking mapping\n let addKeyTxResult: FinalExecutionOutcome;\n let storeDeviceLinkingTxResult: FinalExecutionOutcome;\n try {\n console.debug('LinkDeviceFlow: AddKey transaction details:', {\n receiverId: signedTransactions[0].signedTransaction.transaction.receiverId,\n actions: signedTransactions[0].signedTransaction.transaction.actions || [],\n transactionKeys: Object.keys(signedTransactions[0].signedTransaction.transaction),\n });\n\n addKeyTxResult = await context.nearClient.sendTransaction(\n signedTransactions[0].signedTransaction,\n DEFAULT_WAIT_STATUS.linkDeviceAddKey\n );\n console.log('LinkDeviceFlow: AddKey transaction result:', addKeyTxResult?.transaction?.hash);\n\n // Send success events immediately after AddKey succeeds\n onEvent?.({\n step: 3,\n phase: DeviceLinkingPhase.STEP_3_AUTHORIZATION,\n status: DeviceLinkingStatus.SUCCESS,\n message: `AddKey transaction completed successfully!`\n });\n\n // Check if contract mapping transaction is valid before attempting to broadcast\n const contractTx = signedTransactions[1].signedTransaction;\n console.log('LinkDeviceFlow: Contract mapping transaction details:', {\n receiverId: contractTx.transaction.receiverId,\n actions: (contractTx.transaction.actions || []).length\n });\n\n // Standard timeout since nonce conflict should be resolved by the 2s delay\n storeDeviceLinkingTxResult = await context.nearClient.sendTransaction(\n contractTx,\n DEFAULT_WAIT_STATUS.linkDeviceAccountMapping\n );\n\n } catch (txError: any) {\n console.error('LinkDeviceFlow: Transaction broadcasting failed:', txError);\n throw new Error(`Transaction broadcasting failed: ${txError.message}`);\n }\n\n onEvent?.({\n step: 6,\n phase: DeviceLinkingPhase.STEP_6_REGISTRATION,\n status: DeviceLinkingStatus.SUCCESS,\n message: `Device linking completed successfully!`\n });\n\n return {\n addKeyTxResult,\n storeDeviceLinkingTxResult,\n signedDeleteKeyTransaction: signedTransactions[2].signedTransaction\n };\n}\n\n// ===========================\n// ACCOUNT RECOVERY CONTRACT CALLS\n// ===========================\n\n/**\n * Get credential IDs associated with an account from the contract\n * Used in account recovery to discover available credentials\n */\nexport async function getCredentialIdsContractCall(\n nearClient: NearClient,\n contractId: string,\n accountId: AccountId\n): Promise<string[]> {\n try {\n const credentialIds = await nearClient.callFunction<{ account_id: AccountId }, string[]>(\n contractId,\n 'get_credential_ids_by_account',\n { account_id: accountId }\n );\n return credentialIds || [];\n } catch (error: any) {\n console.warn('Failed to fetch credential IDs from contract:', error.message);\n return [];\n }\n}\n\n/**\n * Get all authenticators stored for a user from the contract\n * Used in account recovery to sync authenticator data\n */\nexport async function getAuthenticatorsByUser(\n nearClient: NearClient,\n contractId: string,\n accountId: AccountId\n): Promise<[string, ContractStoredAuthenticator][]> {\n try {\n const authenticatorsResult = await nearClient.view<{ user_id: AccountId }, [string, ContractStoredAuthenticator][]>({\n account: contractId,\n method: 'get_authenticators_by_user',\n args: { user_id: accountId }\n });\n\n if (authenticatorsResult && Array.isArray(authenticatorsResult)) {\n return authenticatorsResult;\n }\n return [];\n } catch (error: any) {\n console.warn('Failed to fetch authenticators from contract:', error.message);\n return [];\n }\n}\n\nexport async function syncAuthenticatorsContractCall(\n nearClient: NearClient,\n contractId: string,\n accountId: AccountId\n): Promise<Array<{ credentialId: string, authenticator: StoredAuthenticator }>> {\n try {\n const authenticatorsResult = await getAuthenticatorsByUser(nearClient, contractId, accountId);\n if (authenticatorsResult && Array.isArray(authenticatorsResult)) {\n return authenticatorsResult.map(([credentialId, contractAuthenticator]) => {\n console.log(`Contract authenticator device_number for ${credentialId}:`, contractAuthenticator.device_number);\n\n const transports = Array.isArray(contractAuthenticator.transports)\n ? contractAuthenticator.transports\n : [];\n\n const registered = (() => {\n const raw = String((contractAuthenticator as any).registered ?? '');\n if (!raw) return new Date(0);\n if (/^\\d+$/.test(raw)) {\n const ts = Number(raw);\n return Number.isFinite(ts) ? new Date(ts) : new Date(0);\n }\n const d = new Date(raw);\n return Number.isFinite(d.getTime()) ? d : new Date(0);\n })();\n\n const vrfPublicKeys = (() => {\n const raw = (contractAuthenticator as any).vrf_public_keys;\n if (!raw) return undefined;\n if (Array.isArray(raw) && raw.length > 0 && typeof raw[0] === 'string') {\n return raw as string[];\n }\n if (Array.isArray(raw)) {\n return raw\n .map((entry: unknown) => {\n if (!entry) return null;\n if (entry instanceof Uint8Array) return base64UrlEncode(entry);\n if (Array.isArray(entry)) return base64UrlEncode(new Uint8Array(entry));\n return null;\n })\n .filter((x): x is string => typeof x === 'string' && x.length > 0);\n }\n return undefined;\n })();\n\n return {\n credentialId,\n authenticator: {\n credentialId,\n credentialPublicKey: new Uint8Array(contractAuthenticator.credential_public_key),\n transports,\n userId: accountId,\n name: `Device ${contractAuthenticator.device_number} Authenticator`,\n registered,\n // Store the actual device number from contract (no fallback)\n deviceNumber: contractAuthenticator.device_number,\n vrfPublicKeys\n }\n };\n });\n }\n return [];\n } catch (error: any) {\n console.warn('Failed to fetch authenticators from contract:', error.message);\n return [];\n }\n}\n\n// ===========================\n// RECOVERY EMAIL CONTRACT CALLS\n// ===========================\n\n/**\n * Fetch on-chain recovery email hashes from the per-account contract.\n * Returns [] when no contract is deployed or on failure.\n */\nexport async function getRecoveryEmailHashesContractCall(\n nearClient: NearClient,\n accountId: AccountId\n): Promise<number[][]> {\n try {\n const code = await nearClient.viewCode(accountId);\n const hasContract = !!code && code.byteLength > 0;\n if (!hasContract) return [];\n\n const hashes = await nearClient.view<Record<string, never>, number[][]>({\n account: accountId,\n method: 'get_recovery_emails',\n args: {} as Record<string, never>,\n });\n\n return Array.isArray(hashes) ? (hashes as number[][]) : [];\n } catch (error) {\n console.error('[rpcCalls] Failed to fetch recovery email hashes', error);\n return [];\n }\n}\n\n/**\n * Build action args to update on-chain recovery emails for an account.\n * If the per-account contract is missing, deploy/attach the global recoverer via `init_email_recovery`.\n */\nexport async function buildSetRecoveryEmailsActions(\n nearClient: NearClient,\n accountId: AccountId,\n recoveryEmailHashes: number[][],\n contracts: EmailRecoveryContracts = DEFAULT_EMAIL_RECOVERY_CONTRACTS\n): Promise<ActionArgs[]> {\n let hasContract = false;\n try {\n const code = await nearClient.viewCode(accountId);\n hasContract = !!code && code.byteLength > 0;\n } catch {\n hasContract = false;\n }\n\n const {\n emailRecovererGlobalContract,\n zkEmailVerifierContract,\n emailDkimVerifierContract,\n } = contracts;\n\n return hasContract\n ? [\n {\n type: ActionType.UseGlobalContract,\n accountId: emailRecovererGlobalContract,\n },\n {\n type: ActionType.FunctionCall,\n methodName: 'set_recovery_emails',\n args: {\n recovery_emails: recoveryEmailHashes,\n },\n gas: '80000000000000',\n deposit: '0',\n },\n ]\n : [\n {\n type: ActionType.UseGlobalContract,\n accountId: emailRecovererGlobalContract,\n },\n {\n type: ActionType.FunctionCall,\n methodName: 'init_email_recovery',\n args: {\n zk_email_verifier: zkEmailVerifierContract,\n email_dkim_verifier: emailDkimVerifierContract,\n policy: null,\n recovery_emails: recoveryEmailHashes,\n },\n gas: '80000000000000',\n deposit: '0',\n },\n ];\n}\n\nexport async function fetchNonceBlockHashAndHeight({ nearClient, nearPublicKeyStr, nearAccountId }: {\n nearClient: NearClient,\n nearPublicKeyStr: string,\n nearAccountId: AccountId\n}): Promise<TransactionContext> {\n // Get access key and transaction block info concurrently\n const [accessKeyInfo, txBlockInfo] = await Promise.all([\n nearClient.viewAccessKey(nearAccountId, nearPublicKeyStr)\n .catch(e => { throw new Error(`Failed to fetch Access Key`) }),\n nearClient.viewBlock({ finality: 'final' })\n .catch(e => { throw new Error(`Failed to fetch Block Info`) })\n ]);\n if (!accessKeyInfo || accessKeyInfo.nonce === undefined) {\n throw new Error(`Access key not found or invalid for account ${nearAccountId} with public key ${nearPublicKeyStr}. Response: ${JSON.stringify(accessKeyInfo)}`);\n }\n const nextNonce = (BigInt(accessKeyInfo.nonce) + BigInt(1)).toString();\n const txBlockHeight = String(txBlockInfo.header.height);\n const txBlockHash = txBlockInfo.header.hash; // Keep original base58 string\n\n return {\n nearPublicKeyStr,\n accessKeyInfo,\n nextNonce,\n txBlockHeight,\n txBlockHash,\n };\n}\n\n// ===========================\n// REGISTRATION PRE-CHECK CALL\n// ===========================\n\nexport interface CheckCanRegisterUserResult {\n success: boolean;\n verified: boolean;\n logs: string[];\n error?: string;\n}\n\n/**\n * View-only registration pre-check.\n *\n * Calls the contract's `check_can_register_user` view method with VRF data\n * derived from the provided VRF challenge and a serialized WebAuthn\n * registration credential (typically with PRF outputs embedded).\n */\nexport async function checkCanRegisterUserContractCall({\n nearClient,\n contractId,\n vrfChallenge,\n credential,\n authenticatorOptions,\n}: {\n nearClient: NearClient;\n contractId: string;\n vrfChallenge: VRFChallenge;\n credential: WebAuthnRegistrationCredential;\n authenticatorOptions?: AuthenticatorOptions;\n}): Promise<CheckCanRegisterUserResult> {\n try {\n const vrfData = {\n vrf_input_data: Array.from(base64UrlDecode(vrfChallenge.vrfInput)),\n vrf_output: Array.from(base64UrlDecode(vrfChallenge.vrfOutput)),\n vrf_proof: Array.from(base64UrlDecode(vrfChallenge.vrfProof)),\n public_key: Array.from(base64UrlDecode(vrfChallenge.vrfPublicKey)),\n user_id: vrfChallenge.userId,\n rp_id: vrfChallenge.rpId,\n block_height: Number(vrfChallenge.blockHeight),\n block_hash: Array.from(base64UrlDecode(vrfChallenge.blockHash)),\n };\n\n const args = {\n vrf_data: vrfData,\n webauthn_registration: credential,\n authenticator_options: authenticatorOptions,\n };\n\n const response = await nearClient.callFunction<typeof args, any>(\n contractId,\n 'check_can_register_user',\n args,\n );\n\n const verified = !!response?.verified;\n return {\n success: true,\n verified,\n logs: [],\n error: verified ? undefined : 'Contract registration check failed',\n };\n } catch (err: unknown) {\n return {\n success: false,\n verified: false,\n logs: [],\n error: errorMessage(err) || 'Failed to call check_can_register_user',\n };\n }\n}\n\n\n/**\n * Verify authentication response through relay server\n * Routes the request to relay server which calls the web3authn contract for verification\n * and issues a JWT or session credential\n */\nexport async function verifyAuthenticationResponse(\n relayServerUrl: string,\n routePath: string,\n sessionKind: 'jwt' | 'cookie',\n vrfChallenge: VRFChallenge,\n webauthnAuthentication: WebAuthnAuthenticationCredential\n): Promise<{\n success: boolean;\n verified?: boolean;\n jwt?: string;\n sessionCredential?: any;\n error?: string;\n contractResponse?: any;\n}> {\n try {\n // Map VRFChallenge into server ContractVrfData shape (number arrays)\n const toBytes = (b64u: string | undefined): number[] => {\n if (!b64u) return [];\n return Array.from(base64UrlDecode(b64u));\n };\n const vrf_data = {\n vrf_input_data: toBytes(vrfChallenge.vrfInput),\n vrf_output: toBytes(vrfChallenge.vrfOutput),\n vrf_proof: toBytes(vrfChallenge.vrfProof),\n public_key: toBytes(vrfChallenge.vrfPublicKey),\n user_id: vrfChallenge.userId,\n rp_id: vrfChallenge.rpId,\n block_height: Number(vrfChallenge.blockHeight || 0),\n block_hash: toBytes(vrfChallenge.blockHash),\n };\n\n // Normalize authenticatorAttachment and userHandle to null for server schema\n const webauthn_authentication = {\n ...webauthnAuthentication,\n authenticatorAttachment: webauthnAuthentication.authenticatorAttachment ?? null,\n response: {\n ...webauthnAuthentication.response,\n userHandle: webauthnAuthentication.response.userHandle ?? null,\n }\n };\n\n const url = `${relayServerUrl.replace(/\\/$/, '')}${routePath.startsWith('/') ? routePath : `/${routePath}`}`;\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n credentials: sessionKind === 'cookie' ? 'include' : 'omit',\n body: JSON.stringify({\n sessionKind: sessionKind,\n vrf_data,\n webauthn_authentication,\n }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return {\n success: false,\n error: `HTTP ${response.status}: ${errorText}`,\n };\n }\n\n const result = await response.json();\n return {\n success: true,\n verified: result.verified,\n jwt: result.jwt,\n sessionCredential: result.sessionCredential,\n contractResponse: result.contractResponse,\n };\n } catch (error: any) {\n return {\n success: false,\n error: error.message || 'Failed to verify authentication response',\n };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rpcCalls.js","names":["error: any","ActionType","ActionPhase","DeviceLinkingPhase","DeviceLinkingStatus","addKeyTxResult: FinalExecutionOutcome","storeDeviceLinkingTxResult: FinalExecutionOutcome","DEFAULT_WAIT_STATUS","txError: any","base64UrlEncode","DEFAULT_EMAIL_RECOVERY_CONTRACTS","err: unknown","errorMessage","base: ActionArgs[]","base64UrlDecode"],"sources":["../../../../../../src/core/rpcCalls.ts"],"sourcesContent":["/**\n * Consolidated NEAR Contract Calls\n *\n * This file contains all the NEAR contract calls made to the web3authn contract\n * throughout the passkey SDK. It provides a centralized location for all\n * contract interactions and makes it easier to maintain and update contract\n * call patterns.\n */\n\nimport type { FinalExecutionOutcome } from '@near-js/types';\nimport type { NearClient, SignedTransaction } from './NearClient';\nimport type { ContractStoredAuthenticator } from './TatchiPasskey/recoverAccount';\nimport type { PasskeyManagerContext } from './TatchiPasskey';\nimport type { AccountId } from './types/accountIds';\nimport type { DeviceLinkingSSEEvent } from './types/sdkSentEvents';\nimport type {\n StoredAuthenticator,\n WebAuthnRegistrationCredential,\n WebAuthnAuthenticationCredential\n} from './types/webauthn';\n\nimport { ActionPhase, DeviceLinkingPhase, DeviceLinkingStatus } from './types/sdkSentEvents';\nimport { ActionType, type ActionArgs } from './types/actions';\nimport { VRFChallenge } from './types/vrf-worker';\nimport { DEFAULT_WAIT_STATUS, TransactionContext } from './types/rpc';\nimport type { AuthenticatorOptions } from './types/authenticatorOptions';\nimport type { ConfirmationConfig } from './types/signer-worker';\nimport { base64UrlDecode, base64UrlEncode } from '../utils/encoders';\nimport { errorMessage } from '../utils/errors';\nimport type { EmailRecoveryContracts } from './types/tatchi';\nimport { DEFAULT_EMAIL_RECOVERY_CONTRACTS } from './defaultConfigs';\n\n// ===========================\n// CONTRACT CALL RESPONSES\n// ===========================\n\nexport interface DeviceLinkingResult {\n linkedAccountId: string;\n deviceNumber: number;\n}\n\nexport interface CredentialIdsResult {\n credentialIds: string[];\n}\n\nexport interface AuthenticatorsResult {\n authenticators: Array<[string, ContractStoredAuthenticator]>;\n}\n\nexport type RecoveryAttemptStatus =\n | \"Started\"\n | \"VerifyingDkim\"\n | \"VerifyingZkEmail\"\n | \"DkimFailed\"\n | \"ZkEmailFailed\"\n | \"PolicyFailed\"\n | \"Recovering\"\n | \"AwaitingMoreEmails\"\n | \"Complete\"\n | \"Failed\";\n\nexport type RecoveryAttempt = {\n request_id: string;\n status: RecoveryAttemptStatus | string;\n created_at_ms: number;\n updated_at_ms: number;\n error?: string | null;\n from_address?: string | null;\n email_timestamp_ms?: number | null;\n new_public_key?: string | null;\n};\n\nexport async function getEmailRecoveryAttempt(\n nearClient: NearClient,\n accountId: string,\n requestId: string\n): Promise<RecoveryAttempt | null> {\n const raw = await nearClient.view<{ request_id: string }, Omit<RecoveryAttempt, 'status'> & { status: any } | null>({\n account: accountId,\n method: 'get_recovery_attempt',\n args: { request_id: requestId },\n });\n\n if (!raw) return null;\n\n // Normalization logic for status (string or object enum)\n const statusRaw = raw.status;\n const status = (() => {\n if (typeof statusRaw === 'string') return statusRaw.trim();\n if (statusRaw && typeof statusRaw === 'object') {\n const keys = Object.keys(statusRaw as Record<string, unknown>);\n if (keys.length === 1) {\n return String(keys[0] || '').trim();\n }\n }\n return '';\n })();\n\n return {\n ...raw,\n status: status as RecoveryAttemptStatus,\n };\n}\n\n// ===========================\n// DEVICE LINKING CONTRACT CALLS\n// ===========================\n\n/**\n * Query the contract to get the account linked to a device public key\n * Used in device linking flow to check if a device key has been added\n *\n * NEAR does not provide a way to lookup the AccountID an access key has access to.\n * So we store a temporary mapping in the contract to lookup pubkey -> account ID.\n */\nexport async function getDeviceLinkingAccountContractCall(\n nearClient: NearClient,\n contractId: string,\n devicePublicKey: string\n): Promise<DeviceLinkingResult | null> {\n try {\n const result = await nearClient.callFunction<\n { device_public_key: string },\n [string, number | string]\n >(\n contractId,\n 'get_device_linking_account',\n { device_public_key: devicePublicKey }\n );\n\n // Handle different result formats\n if (result && Array.isArray(result) && result.length >= 2) {\n const [linkedAccountId, deviceNumberRaw] = result;\n const deviceNumber = Number(deviceNumberRaw);\n if (!Number.isSafeInteger(deviceNumber) || deviceNumber < 0) {\n console.warn(\n 'Invalid deviceNumber returned from get_device_linking_account:',\n deviceNumberRaw\n );\n return null;\n }\n return {\n linkedAccountId,\n deviceNumber\n };\n }\n\n return null;\n } catch (error: any) {\n console.warn('Failed to get device linking account:', error.message);\n return null;\n }\n}\n\n// ===========================\n// DEVICE LINKING TRANSACTION CALLS\n// ===========================\n\n/**\n * Execute device1's linking transactions (AddKey + Contract mapping)\n * This function signs and broadcasts both transactions required for device linking\n */\nexport async function executeDeviceLinkingContractCalls({\n context,\n device1AccountId,\n device2PublicKey,\n nextNonce,\n nextNextNonce,\n nextNextNextNonce,\n txBlockHash,\n vrfChallenge,\n onEvent,\n confirmationConfigOverride,\n confirmerText,\n}: {\n context: PasskeyManagerContext,\n device1AccountId: AccountId,\n device2PublicKey: string,\n nextNonce: string,\n nextNextNonce: string,\n nextNextNextNonce: string,\n txBlockHash: string,\n vrfChallenge: VRFChallenge,\n onEvent?: (event: DeviceLinkingSSEEvent) => void;\n confirmationConfigOverride?: Partial<ConfirmationConfig>;\n confirmerText?: { title?: string; body?: string };\n}): Promise<{\n addKeyTxResult: FinalExecutionOutcome;\n storeDeviceLinkingTxResult: FinalExecutionOutcome;\n signedDeleteKeyTransaction: SignedTransaction\n}> {\n\n // Sign three transactions with one PRF authentication\n const signedTransactions = await context.webAuthnManager.signTransactionsWithActions({\n rpcCall: {\n contractId: context.webAuthnManager.tatchiPasskeyConfigs.contractId,\n nearRpcUrl: context.webAuthnManager.tatchiPasskeyConfigs.nearRpcUrl,\n nearAccountId: device1AccountId\n },\n confirmationConfigOverride,\n title: confirmerText?.title,\n body: confirmerText?.body,\n transactions: [\n // Transaction 1: AddKey - Add Device2's key to Device1's account\n {\n receiverId: device1AccountId,\n actions: [{\n action_type: ActionType.AddKey,\n public_key: device2PublicKey,\n access_key: JSON.stringify({\n // NEAR-style AccessKey JSON shape, matching near-api-js:\n // { nonce: number, permission: { FullAccess: {} } }\n nonce: 0,\n permission: { FullAccess: {} },\n }),\n }],\n nonce: nextNonce,\n },\n // Transaction 2: Store temporary mapping in contract so Device2 can lookup Device1's accountID.\n {\n receiverId: context.webAuthnManager.tatchiPasskeyConfigs.contractId,\n actions: [{\n action_type: ActionType.FunctionCall,\n method_name: 'store_device_linking_mapping',\n args: JSON.stringify({\n device_public_key: device2PublicKey,\n target_account_id: device1AccountId,\n }),\n gas: '30000000000000', // 30 TGas for device linking with yield promise automatic cleanup\n deposit: '0'\n }],\n nonce: nextNextNonce,\n },\n // Transaction 3: Remove Device2's temporary key if it fails to complete linking after a timeout\n {\n receiverId: device1AccountId,\n actions: [{\n action_type: ActionType.DeleteKey,\n public_key: device2PublicKey\n }],\n nonce: nextNextNextNonce,\n }\n ],\n onEvent: (progress) => {\n // Bridge all action progress events to the parent so the wallet iframe overlay\n // can expand during user confirmation in wallet-iframe mode.\n try { onEvent?.(progress as any); } catch { }\n // Keep existing mapping for device linking semantics; surface signing as a loading state\n if (progress.phase == ActionPhase.STEP_6_TRANSACTION_SIGNING_COMPLETE) {\n onEvent?.({\n step: 3,\n phase: DeviceLinkingPhase.STEP_3_AUTHORIZATION,\n status: DeviceLinkingStatus.PROGRESS,\n message: progress.message || 'Transaction signing in progress...'\n })\n }\n }\n });\n\n if (!signedTransactions[0].signedTransaction) {\n throw new Error('AddKey transaction signing failed');\n }\n if (!signedTransactions[1].signedTransaction) {\n throw new Error('Contract mapping transaction signing failed');\n }\n if (!signedTransactions[2].signedTransaction) {\n throw new Error('DeleteKey transaction signing failed');\n }\n\n // Broadcast just the first 2 transactions: addKey and store device linking mapping\n let addKeyTxResult: FinalExecutionOutcome;\n let storeDeviceLinkingTxResult: FinalExecutionOutcome;\n try {\n console.debug('LinkDeviceFlow: AddKey transaction details:', {\n receiverId: signedTransactions[0].signedTransaction.transaction.receiverId,\n actions: signedTransactions[0].signedTransaction.transaction.actions || [],\n transactionKeys: Object.keys(signedTransactions[0].signedTransaction.transaction),\n });\n\n addKeyTxResult = await context.nearClient.sendTransaction(\n signedTransactions[0].signedTransaction,\n DEFAULT_WAIT_STATUS.linkDeviceAddKey\n );\n console.log('LinkDeviceFlow: AddKey transaction result:', addKeyTxResult?.transaction?.hash);\n\n // Send success events immediately after AddKey succeeds\n onEvent?.({\n step: 3,\n phase: DeviceLinkingPhase.STEP_3_AUTHORIZATION,\n status: DeviceLinkingStatus.SUCCESS,\n message: `AddKey transaction completed successfully!`\n });\n\n // Check if contract mapping transaction is valid before attempting to broadcast\n const contractTx = signedTransactions[1].signedTransaction;\n console.log('LinkDeviceFlow: Contract mapping transaction details:', {\n receiverId: contractTx.transaction.receiverId,\n actions: (contractTx.transaction.actions || []).length\n });\n\n // Standard timeout since nonce conflict should be resolved by the 2s delay\n storeDeviceLinkingTxResult = await context.nearClient.sendTransaction(\n contractTx,\n DEFAULT_WAIT_STATUS.linkDeviceAccountMapping\n );\n\n } catch (txError: any) {\n console.error('LinkDeviceFlow: Transaction broadcasting failed:', txError);\n throw new Error(`Transaction broadcasting failed: ${txError.message}`);\n }\n\n onEvent?.({\n step: 6,\n phase: DeviceLinkingPhase.STEP_6_REGISTRATION,\n status: DeviceLinkingStatus.SUCCESS,\n message: `Device linking completed successfully!`\n });\n\n return {\n addKeyTxResult,\n storeDeviceLinkingTxResult,\n signedDeleteKeyTransaction: signedTransactions[2].signedTransaction\n };\n}\n\n// ===========================\n// ACCOUNT RECOVERY CONTRACT CALLS\n// ===========================\n\n/**\n * Get credential IDs associated with an account from the contract\n * Used in account recovery to discover available credentials\n */\nexport async function getCredentialIdsContractCall(\n nearClient: NearClient,\n contractId: string,\n accountId: AccountId\n): Promise<string[]> {\n try {\n const credentialIds = await nearClient.callFunction<{ account_id: AccountId }, string[]>(\n contractId,\n 'get_credential_ids_by_account',\n { account_id: accountId }\n );\n return credentialIds || [];\n } catch (error: any) {\n console.warn('Failed to fetch credential IDs from contract:', error.message);\n return [];\n }\n}\n\n/**\n * Get all authenticators stored for a user from the contract\n * Used in account recovery to sync authenticator data\n */\nexport async function getAuthenticatorsByUser(\n nearClient: NearClient,\n contractId: string,\n accountId: AccountId\n): Promise<[string, ContractStoredAuthenticator][]> {\n try {\n const authenticatorsResult = await nearClient.view<{ user_id: AccountId }, [string, ContractStoredAuthenticator][]>({\n account: contractId,\n method: 'get_authenticators_by_user',\n args: { user_id: accountId }\n });\n\n if (authenticatorsResult && Array.isArray(authenticatorsResult)) {\n return authenticatorsResult;\n }\n return [];\n } catch (error: any) {\n console.warn('Failed to fetch authenticators from contract:', error.message);\n return [];\n }\n}\n\nexport async function syncAuthenticatorsContractCall(\n nearClient: NearClient,\n contractId: string,\n accountId: AccountId\n): Promise<Array<{ credentialId: string, authenticator: StoredAuthenticator }>> {\n try {\n const authenticatorsResult = await getAuthenticatorsByUser(nearClient, contractId, accountId);\n if (authenticatorsResult && Array.isArray(authenticatorsResult)) {\n return authenticatorsResult.map(([credentialId, contractAuthenticator]) => {\n console.log(`Contract authenticator device_number for ${credentialId}:`, contractAuthenticator.device_number);\n\n const transports = Array.isArray(contractAuthenticator.transports)\n ? contractAuthenticator.transports\n : [];\n\n const registered = (() => {\n const raw = String((contractAuthenticator as any).registered ?? '');\n if (!raw) return new Date(0);\n if (/^\\d+$/.test(raw)) {\n const ts = Number(raw);\n return Number.isFinite(ts) ? new Date(ts) : new Date(0);\n }\n const d = new Date(raw);\n return Number.isFinite(d.getTime()) ? d : new Date(0);\n })();\n\n const vrfPublicKeys = (() => {\n const raw = (contractAuthenticator as any).vrf_public_keys;\n if (!raw) return undefined;\n if (Array.isArray(raw) && raw.length > 0 && typeof raw[0] === 'string') {\n return raw as string[];\n }\n if (Array.isArray(raw)) {\n return raw\n .map((entry: unknown) => {\n if (!entry) return null;\n if (entry instanceof Uint8Array) return base64UrlEncode(entry);\n if (Array.isArray(entry)) return base64UrlEncode(new Uint8Array(entry));\n return null;\n })\n .filter((x): x is string => typeof x === 'string' && x.length > 0);\n }\n return undefined;\n })();\n\n return {\n credentialId,\n authenticator: {\n credentialId,\n credentialPublicKey: new Uint8Array(contractAuthenticator.credential_public_key),\n transports,\n userId: accountId,\n name: `Device ${contractAuthenticator.device_number} Authenticator`,\n registered,\n // Store the actual device number from contract (no fallback)\n deviceNumber: contractAuthenticator.device_number,\n vrfPublicKeys\n }\n };\n });\n }\n return [];\n } catch (error: any) {\n console.warn('Failed to fetch authenticators from contract:', error.message);\n return [];\n }\n}\n\n// ===========================\n// RECOVERY EMAIL CONTRACT CALLS\n// ===========================\n\nconst EMPTY_NEAR_CODE_HASH = '11111111111111111111111111111111';\n\nasync function hasDeployedContractCode(nearClient: NearClient, accountId: AccountId): Promise<boolean> {\n try {\n const account = await nearClient.viewAccount(accountId);\n const codeHash = (account as { code_hash?: unknown } | null)?.code_hash;\n const globalContractHash = (account as { global_contract_hash?: unknown } | null)?.global_contract_hash;\n const globalContractAccountId = (account as { global_contract_account_id?: unknown } | null)?.global_contract_account_id;\n\n const hasLocalCode = typeof codeHash === 'string' && codeHash !== EMPTY_NEAR_CODE_HASH;\n const hasGlobalCode =\n (typeof globalContractHash === 'string' && globalContractHash.trim().length > 0) ||\n (typeof globalContractAccountId === 'string' && globalContractAccountId.trim().length > 0);\n\n return hasLocalCode || hasGlobalCode;\n } catch {\n return false;\n }\n}\n\n/**\n * Fetch on-chain recovery email hashes from the per-account contract.\n * Returns [] when no contract is deployed or on failure.\n */\nexport async function getRecoveryEmailHashesContractCall(\n nearClient: NearClient,\n accountId: AccountId\n): Promise<number[][]> {\n try {\n // Prefer `view_account` over `view_code`:\n // - `view_code` is expected to fail for non-contract accounts and is noisy.\n // - `view_account` is lightweight and tells us whether the account uses local contract code\n // or a NEAR \"global contract\" (via `global_contract_*` fields).\n const hasContract = await hasDeployedContractCode(nearClient, accountId);\n if (!hasContract) return [];\n\n const hashes = await nearClient.view<Record<string, never>, number[][]>({\n account: accountId,\n method: 'get_recovery_emails',\n args: {} as Record<string, never>,\n });\n\n return Array.isArray(hashes) ? (hashes as number[][]) : [];\n } catch (error) {\n return [];\n }\n}\n\n/**\n * Build action args to update on-chain recovery emails for an account.\n * If the per-account contract is missing, deploy/attach the global recoverer via `init_email_recovery`.\n */\nexport async function buildSetRecoveryEmailsActions(\n nearClient: NearClient,\n accountId: AccountId,\n recoveryEmailHashes: number[][],\n contracts: EmailRecoveryContracts = DEFAULT_EMAIL_RECOVERY_CONTRACTS\n): Promise<ActionArgs[]> {\n const hasContract = await hasDeployedContractCode(nearClient, accountId);\n\n const {\n emailRecovererGlobalContract,\n zkEmailVerifierContract,\n emailDkimVerifierContract,\n } = contracts;\n\n // If the account already has a contract (local or global), it still might not be a readable\n // EmailRecoverer instance (e.g. stale state after upgrades). In that case, `set_recovery_emails`\n // would fail while `init_email_recovery` (#[init(ignore_state)]) can safely re-initialize.\n //\n // We keep this as a best-effort probe to avoid wiping state on transient RPC issues.\n let shouldInit = !hasContract;\n if (!shouldInit) {\n try {\n await nearClient.view<Record<string, never>, unknown>({\n account: accountId,\n method: 'get_recovery_emails',\n args: {} as Record<string, never>,\n });\n } catch (err: unknown) {\n const msg = errorMessage(err);\n // Common/expected cases where we should fall back to init:\n // - account has a global contract pointer but no EmailRecoverer-compatible state yet\n // - account has stale/incompatible state after a contract upgrade\n // - account has some other contract (method missing)\n if (/Cannot deserialize the contract state/i.test(msg)\n || /CodeDoesNotExist/i.test(msg)\n || /MethodNotFound/i.test(msg)) {\n shouldInit = true;\n }\n }\n }\n\n const base: ActionArgs[] = [\n {\n type: ActionType.UseGlobalContract,\n accountId: emailRecovererGlobalContract,\n },\n ];\n\n return shouldInit\n ? [\n ...base,\n {\n type: ActionType.FunctionCall,\n methodName: 'init_email_recovery',\n args: {\n zk_email_verifier: zkEmailVerifierContract,\n email_dkim_verifier: emailDkimVerifierContract,\n policy: null,\n recovery_emails: recoveryEmailHashes,\n },\n gas: '80000000000000',\n deposit: '0',\n },\n ]\n : [\n ...base,\n {\n type: ActionType.FunctionCall,\n methodName: 'set_recovery_emails',\n args: {\n recovery_emails: recoveryEmailHashes,\n },\n gas: '80000000000000',\n deposit: '0',\n },\n ];\n}\n\nexport async function fetchNonceBlockHashAndHeight({ nearClient, nearPublicKeyStr, nearAccountId }: {\n nearClient: NearClient,\n nearPublicKeyStr: string,\n nearAccountId: AccountId\n}): Promise<TransactionContext> {\n // Get access key and transaction block info concurrently\n const [accessKeyInfo, txBlockInfo] = await Promise.all([\n nearClient.viewAccessKey(nearAccountId, nearPublicKeyStr)\n .catch(e => { throw new Error(`Failed to fetch Access Key`) }),\n nearClient.viewBlock({ finality: 'final' })\n .catch(e => { throw new Error(`Failed to fetch Block Info`) })\n ]);\n if (!accessKeyInfo || accessKeyInfo.nonce === undefined) {\n throw new Error(`Access key not found or invalid for account ${nearAccountId} with public key ${nearPublicKeyStr}. Response: ${JSON.stringify(accessKeyInfo)}`);\n }\n const nextNonce = (BigInt(accessKeyInfo.nonce) + BigInt(1)).toString();\n const txBlockHeight = String(txBlockInfo.header.height);\n const txBlockHash = txBlockInfo.header.hash; // Keep original base58 string\n\n return {\n nearPublicKeyStr,\n accessKeyInfo,\n nextNonce,\n txBlockHeight,\n txBlockHash,\n };\n}\n\n// ===========================\n// REGISTRATION PRE-CHECK CALL\n// ===========================\n\nexport interface CheckCanRegisterUserResult {\n success: boolean;\n verified: boolean;\n logs: string[];\n error?: string;\n}\n\n/**\n * View-only registration pre-check.\n *\n * Calls the contract's `check_can_register_user` view method with VRF data\n * derived from the provided VRF challenge and a serialized WebAuthn\n * registration credential (typically with PRF outputs embedded).\n */\nexport async function checkCanRegisterUserContractCall({\n nearClient,\n contractId,\n vrfChallenge,\n credential,\n authenticatorOptions,\n}: {\n nearClient: NearClient;\n contractId: string;\n vrfChallenge: VRFChallenge;\n credential: WebAuthnRegistrationCredential;\n authenticatorOptions?: AuthenticatorOptions;\n}): Promise<CheckCanRegisterUserResult> {\n try {\n const vrfData = {\n vrf_input_data: Array.from(base64UrlDecode(vrfChallenge.vrfInput)),\n vrf_output: Array.from(base64UrlDecode(vrfChallenge.vrfOutput)),\n vrf_proof: Array.from(base64UrlDecode(vrfChallenge.vrfProof)),\n public_key: Array.from(base64UrlDecode(vrfChallenge.vrfPublicKey)),\n user_id: vrfChallenge.userId,\n rp_id: vrfChallenge.rpId,\n block_height: Number(vrfChallenge.blockHeight),\n block_hash: Array.from(base64UrlDecode(vrfChallenge.blockHash)),\n };\n\n const args = {\n vrf_data: vrfData,\n webauthn_registration: credential,\n authenticator_options: authenticatorOptions,\n };\n\n const response = await nearClient.callFunction<typeof args, any>(\n contractId,\n 'check_can_register_user',\n args,\n );\n\n const verified = !!response?.verified;\n return {\n success: true,\n verified,\n logs: [],\n error: verified ? undefined : 'Contract registration check failed',\n };\n } catch (err: unknown) {\n return {\n success: false,\n verified: false,\n logs: [],\n error: errorMessage(err) || 'Failed to call check_can_register_user',\n };\n }\n}\n\n\n/**\n * Verify authentication response through relay server\n * Routes the request to relay server which calls the web3authn contract for verification\n * and issues a JWT or session credential\n */\nexport async function verifyAuthenticationResponse(\n relayServerUrl: string,\n routePath: string,\n sessionKind: 'jwt' | 'cookie',\n vrfChallenge: VRFChallenge,\n webauthnAuthentication: WebAuthnAuthenticationCredential\n): Promise<{\n success: boolean;\n verified?: boolean;\n jwt?: string;\n sessionCredential?: any;\n error?: string;\n contractResponse?: any;\n}> {\n try {\n // Map VRFChallenge into server ContractVrfData shape (number arrays)\n const toBytes = (b64u: string | undefined): number[] => {\n if (!b64u) return [];\n return Array.from(base64UrlDecode(b64u));\n };\n const vrf_data = {\n vrf_input_data: toBytes(vrfChallenge.vrfInput),\n vrf_output: toBytes(vrfChallenge.vrfOutput),\n vrf_proof: toBytes(vrfChallenge.vrfProof),\n public_key: toBytes(vrfChallenge.vrfPublicKey),\n user_id: vrfChallenge.userId,\n rp_id: vrfChallenge.rpId,\n block_height: Number(vrfChallenge.blockHeight || 0),\n block_hash: toBytes(vrfChallenge.blockHash),\n };\n\n // Normalize authenticatorAttachment and userHandle to null for server schema\n const webauthn_authentication = {\n ...webauthnAuthentication,\n authenticatorAttachment: webauthnAuthentication.authenticatorAttachment ?? null,\n response: {\n ...webauthnAuthentication.response,\n userHandle: webauthnAuthentication.response.userHandle ?? null,\n }\n };\n\n const url = `${relayServerUrl.replace(/\\/$/, '')}${routePath.startsWith('/') ? routePath : `/${routePath}`}`;\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n credentials: sessionKind === 'cookie' ? 'include' : 'omit',\n body: JSON.stringify({\n sessionKind: sessionKind,\n vrf_data,\n webauthn_authentication,\n }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return {\n success: false,\n error: `HTTP ${response.status}: ${errorText}`,\n };\n }\n\n const result = await response.json();\n return {\n success: true,\n verified: result.verified,\n jwt: result.jwt,\n sessionCredential: result.sessionCredential,\n contractResponse: result.contractResponse,\n };\n } catch (error: any) {\n return {\n success: false,\n error: error.message || 'Failed to verify authentication response',\n };\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('../../../../_virtual/rolldown_runtime.js');
|
|
2
|
+
|
|
3
|
+
//#region src/core/types/emailRecovery.ts
|
|
4
|
+
var EmailRecoveryErrorCode, EmailRecoveryError;
|
|
5
|
+
var init_emailRecovery = require_rolldown_runtime.__esm({ "src/core/types/emailRecovery.ts": (() => {
|
|
6
|
+
EmailRecoveryErrorCode = /* @__PURE__ */ function(EmailRecoveryErrorCode$1) {
|
|
7
|
+
EmailRecoveryErrorCode$1["REGISTRATION_NOT_VERIFIED"] = "EMAIL_RECOVERY_REGISTRATION_NOT_VERIFIED";
|
|
8
|
+
EmailRecoveryErrorCode$1["VRF_CHALLENGE_EXPIRED"] = "EMAIL_RECOVERY_VRF_CHALLENGE_EXPIRED";
|
|
9
|
+
return EmailRecoveryErrorCode$1;
|
|
10
|
+
}({});
|
|
11
|
+
EmailRecoveryError = class extends Error {
|
|
12
|
+
code;
|
|
13
|
+
context;
|
|
14
|
+
constructor(message, code, context) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "EmailRecoveryError";
|
|
17
|
+
this.code = code;
|
|
18
|
+
this.context = context;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}) });
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
init_emailRecovery();
|
|
25
|
+
exports.EmailRecoveryError = EmailRecoveryError;
|
|
26
|
+
exports.EmailRecoveryErrorCode = EmailRecoveryErrorCode;
|
|
27
|
+
Object.defineProperty(exports, 'init_emailRecovery', {
|
|
28
|
+
enumerable: true,
|
|
29
|
+
get: function () {
|
|
30
|
+
return init_emailRecovery;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
//# sourceMappingURL=emailRecovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emailRecovery.js","names":[],"sources":["../../../../../../../src/core/types/emailRecovery.ts"],"sourcesContent":["export enum EmailRecoveryErrorCode {\n REGISTRATION_NOT_VERIFIED = 'EMAIL_RECOVERY_REGISTRATION_NOT_VERIFIED',\n VRF_CHALLENGE_EXPIRED = 'EMAIL_RECOVERY_VRF_CHALLENGE_EXPIRED',\n}\n\nexport class EmailRecoveryError extends Error {\n public readonly code: EmailRecoveryErrorCode;\n public readonly context?: Record<string, unknown>;\n\n constructor(message: string, code: EmailRecoveryErrorCode, context?: Record<string, unknown>) {\n super(message);\n this.name = 'EmailRecoveryError';\n this.code = code;\n this.context = context;\n }\n}\n\n"],"mappings":";;;;;CAAY,4EAAL;AACL;AACA;;;CAGW,qBAAb,cAAwC,MAAM;EAC5C,AAAgB;EAChB,AAAgB;EAEhB,YAAY,SAAiB,MAA8B,SAAmC;AAC5F,SAAM;AACN,QAAK,OAAO;AACZ,QAAK,OAAO;AACZ,QAAK,UAAU"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const require_nearCrypto = require('../sdk/src/core/nearCrypto.js');
|
|
1
2
|
|
|
2
3
|
//#region src/server/email-recovery/emailParsers.ts
|
|
3
4
|
let EmailRecoveryModeHint = /* @__PURE__ */ function(EmailRecoveryModeHint$1) {
|
|
@@ -82,7 +83,7 @@ function parseRecoverSubjectBindings(rawEmail) {
|
|
|
82
83
|
return {
|
|
83
84
|
requestId,
|
|
84
85
|
accountId,
|
|
85
|
-
newPublicKey
|
|
86
|
+
newPublicKey: require_nearCrypto.ensureEd25519Prefix(newPublicKey)
|
|
86
87
|
};
|
|
87
88
|
}
|
|
88
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":"
|
|
1
|
+
{"version":3,"file":"emailParsers.js","names":["headerLines: string[]","ensureEd25519Prefix"],"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,cAAcC,uCAAoB"}
|
|
@@ -16,8 +16,8 @@ const require_testHelpers = require('./testHelpers.js');
|
|
|
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
|
|
153
|
-
*
|
|
154
|
-
*
|
|
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":["normalizeLogger","e: unknown","mapZkEmailRecoveryError","ZkEmailProverClient","getOutlayerEncryptionPublicKey","normalizeRecoveryMode","extractRecoveryModeFromBody","e: any","buildEncryptedEmailRecoveryActions","encryptEmailForOutlayer","sendEmailRecoveryTransaction","buildOnchainEmailRecoveryActions","prepareZkEmailRecovery","generateZkEmailProofFromPayload","buildZkEmailRecoveryActions","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":["normalizeLogger","e: unknown","mapZkEmailRecoveryError","ZkEmailProverClient","getOutlayerEncryptionPublicKey","normalizeRecoveryMode","extractRecoveryModeFromBody","e: any","buildEncryptedEmailRecoveryActions","encryptEmailForOutlayer","sendEmailRecoveryTransaction","buildOnchainEmailRecoveryActions","prepareZkEmailRecovery","generateZkEmailProofFromPayload","buildZkEmailRecoveryActions","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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"}
|