@tatchi-xyz/sdk 0.31.0 → 0.32.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/README.md +2 -0
- package/dist/cjs/core/IndexedDBManager/passkeyClientDB.js +2 -2
- package/dist/cjs/core/IndexedDBManager/passkeyClientDB.js.map +1 -1
- package/dist/cjs/core/TatchiPasskey/faucets/createAccountRelayServer.js +9 -8
- package/dist/cjs/core/TatchiPasskey/faucets/createAccountRelayServer.js.map +1 -1
- package/dist/cjs/core/TatchiPasskey/login.js +1 -1
- package/dist/cjs/core/TatchiPasskey/login.js.map +1 -1
- package/dist/cjs/core/TatchiPasskey/registration.js +107 -63
- package/dist/cjs/core/TatchiPasskey/registration.js.map +1 -1
- package/dist/cjs/core/WalletIframe/client/on-events-progress-bus.js +1 -1
- package/dist/cjs/core/WalletIframe/client/on-events-progress-bus.js.map +1 -1
- package/dist/cjs/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.js +1 -10
- package/dist/cjs/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.js.map +1 -1
- package/dist/cjs/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.js +58 -67
- package/dist/cjs/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.js.map +1 -1
- package/dist/cjs/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.js +74 -75
- package/dist/cjs/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.js.map +1 -1
- package/dist/cjs/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.js +17 -7
- package/dist/cjs/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.js.map +1 -1
- package/dist/cjs/core/WebAuthnManager/index.js +3 -3
- package/dist/cjs/core/WebAuthnManager/index.js.map +1 -1
- package/dist/cjs/core/defaultConfigs.js +3 -1
- package/dist/cjs/core/defaultConfigs.js.map +1 -1
- package/dist/cjs/core/types/sdkSentEvents.js +3 -2
- package/dist/cjs/core/types/sdkSentEvents.js.map +1 -1
- package/dist/cjs/react/components/AccountMenuButton/TransactionSettingsSection.js +3 -3
- package/dist/cjs/react/components/AccountMenuButton/TransactionSettingsSection.js.map +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-CRlobBrN.css → PasskeyAuthMenu-D2eRb2-S.css} +3 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/PasskeyAuthMenu-D2eRb2-S.css.map +1 -0
- package/dist/cjs/react/components/PasskeyAuthMenu/preload.js +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/preload.js.map +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/shell.js +52 -13
- package/dist/cjs/react/components/PasskeyAuthMenu/shell.js.map +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/skeleton.js +4 -2
- package/dist/cjs/react/components/PasskeyAuthMenu/skeleton.js.map +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js +5 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js.map +1 -1
- package/dist/cjs/react/context/useTatchiWithSdkFlow.js +1 -1
- package/dist/cjs/react/context/useTatchiWithSdkFlow.js.map +1 -1
- package/dist/cjs/react/index.js +1 -1
- package/dist/cjs/react/src/core/IndexedDBManager/passkeyClientDB.js +2 -2
- package/dist/cjs/react/src/core/IndexedDBManager/passkeyClientDB.js.map +1 -1
- package/dist/cjs/react/src/core/TatchiPasskey/faucets/createAccountRelayServer.js +9 -8
- package/dist/cjs/react/src/core/TatchiPasskey/faucets/createAccountRelayServer.js.map +1 -1
- package/dist/cjs/react/src/core/TatchiPasskey/login.js +1 -1
- package/dist/cjs/react/src/core/TatchiPasskey/login.js.map +1 -1
- package/dist/cjs/react/src/core/TatchiPasskey/registration.js +107 -63
- package/dist/cjs/react/src/core/TatchiPasskey/registration.js.map +1 -1
- package/dist/cjs/react/src/core/WalletIframe/client/on-events-progress-bus.js +1 -1
- package/dist/cjs/react/src/core/WalletIframe/client/on-events-progress-bus.js.map +1 -1
- package/dist/cjs/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.js +1 -10
- package/dist/cjs/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.js.map +1 -1
- package/dist/cjs/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.js +58 -67
- package/dist/cjs/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.js.map +1 -1
- package/dist/cjs/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.js +74 -75
- package/dist/cjs/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.js.map +1 -1
- package/dist/cjs/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.js +17 -7
- package/dist/cjs/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.js.map +1 -1
- package/dist/cjs/react/src/core/WebAuthnManager/index.js +3 -3
- package/dist/cjs/react/src/core/WebAuthnManager/index.js.map +1 -1
- package/dist/cjs/react/src/core/defaultConfigs.js +3 -1
- package/dist/cjs/react/src/core/defaultConfigs.js.map +1 -1
- package/dist/cjs/react/src/core/types/sdkSentEvents.js +3 -2
- package/dist/cjs/react/src/core/types/sdkSentEvents.js.map +1 -1
- package/dist/cjs/server/core/AuthService.js +49 -6
- package/dist/cjs/server/core/AuthService.js.map +1 -1
- package/dist/cjs/server/sdk/src/core/defaultConfigs.js.map +1 -1
- package/dist/esm/core/IndexedDBManager/passkeyClientDB.js +2 -2
- package/dist/esm/core/IndexedDBManager/passkeyClientDB.js.map +1 -1
- package/dist/esm/core/TatchiPasskey/faucets/createAccountRelayServer.js +9 -8
- package/dist/esm/core/TatchiPasskey/faucets/createAccountRelayServer.js.map +1 -1
- package/dist/esm/core/TatchiPasskey/login.js +1 -1
- package/dist/esm/core/TatchiPasskey/login.js.map +1 -1
- package/dist/esm/core/TatchiPasskey/registration.js +107 -63
- package/dist/esm/core/TatchiPasskey/registration.js.map +1 -1
- package/dist/esm/core/WalletIframe/client/on-events-progress-bus.js +1 -1
- package/dist/esm/core/WalletIframe/client/on-events-progress-bus.js.map +1 -1
- package/dist/esm/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.js +1 -10
- package/dist/esm/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.js.map +1 -1
- package/dist/esm/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.js +58 -67
- package/dist/esm/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.js.map +1 -1
- package/dist/esm/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.js +74 -75
- package/dist/esm/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.js.map +1 -1
- package/dist/esm/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.js +17 -7
- package/dist/esm/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.js.map +1 -1
- package/dist/esm/core/WebAuthnManager/index.js +3 -3
- package/dist/esm/core/WebAuthnManager/index.js.map +1 -1
- package/dist/esm/core/defaultConfigs.js +3 -1
- package/dist/esm/core/defaultConfigs.js.map +1 -1
- package/dist/esm/core/types/sdkSentEvents.js +3 -2
- package/dist/esm/core/types/sdkSentEvents.js.map +1 -1
- package/dist/esm/react/components/AccountMenuButton/TransactionSettingsSection.js +3 -3
- package/dist/esm/react/components/AccountMenuButton/TransactionSettingsSection.js.map +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-D2VHZ04W.css → PasskeyAuthMenu-qTHAv58Z.css} +3 -1
- package/dist/esm/react/components/PasskeyAuthMenu/PasskeyAuthMenu-qTHAv58Z.css.map +1 -0
- package/dist/esm/react/components/PasskeyAuthMenu/preload.js +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/preload.js.map +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/shell.js +52 -13
- package/dist/esm/react/components/PasskeyAuthMenu/shell.js.map +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/skeleton.js +4 -2
- package/dist/esm/react/components/PasskeyAuthMenu/skeleton.js.map +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js +5 -1
- package/dist/esm/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js.map +1 -1
- package/dist/esm/react/context/useTatchiWithSdkFlow.js +1 -1
- package/dist/esm/react/context/useTatchiWithSdkFlow.js.map +1 -1
- package/dist/esm/react/index.js +1 -1
- package/dist/esm/react/src/core/IndexedDBManager/passkeyClientDB.js +2 -2
- package/dist/esm/react/src/core/IndexedDBManager/passkeyClientDB.js.map +1 -1
- package/dist/esm/react/src/core/TatchiPasskey/faucets/createAccountRelayServer.js +9 -8
- package/dist/esm/react/src/core/TatchiPasskey/faucets/createAccountRelayServer.js.map +1 -1
- package/dist/esm/react/src/core/TatchiPasskey/login.js +1 -1
- package/dist/esm/react/src/core/TatchiPasskey/login.js.map +1 -1
- package/dist/esm/react/src/core/TatchiPasskey/registration.js +107 -63
- package/dist/esm/react/src/core/TatchiPasskey/registration.js.map +1 -1
- package/dist/esm/react/src/core/WalletIframe/client/on-events-progress-bus.js +1 -1
- package/dist/esm/react/src/core/WalletIframe/client/on-events-progress-bus.js.map +1 -1
- package/dist/esm/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.js +1 -10
- package/dist/esm/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.js.map +1 -1
- package/dist/esm/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.js +58 -67
- package/dist/esm/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.js.map +1 -1
- package/dist/esm/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.js +74 -75
- package/dist/esm/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.js.map +1 -1
- package/dist/esm/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.js +17 -7
- package/dist/esm/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.js.map +1 -1
- package/dist/esm/react/src/core/WebAuthnManager/index.js +3 -3
- package/dist/esm/react/src/core/WebAuthnManager/index.js.map +1 -1
- package/dist/esm/react/src/core/defaultConfigs.js +3 -1
- package/dist/esm/react/src/core/defaultConfigs.js.map +1 -1
- package/dist/esm/react/src/core/types/sdkSentEvents.js +3 -2
- package/dist/esm/react/src/core/types/sdkSentEvents.js.map +1 -1
- package/dist/esm/react/styles/styles.css +2 -0
- package/dist/esm/sdk/{EmailRecovery-Dl8b4ONg.js → EmailRecovery-Y7rurd4B.js} +3 -3
- package/dist/esm/sdk/{EmailRecovery-v9oNO2Tc.js → EmailRecovery-lsjLWApQ.js} +1 -1
- package/dist/esm/sdk/{IndexedDBManager-B1cUvdyY.js → IndexedDBManager-CmdN7smS.js} +3 -3
- package/dist/esm/sdk/{createAdapters-Dv7ZJPf1.js → createAdapters-4c8mBiD5.js} +2 -11
- package/dist/esm/sdk/{createAdapters-Dv7ZJPf1.js.map → createAdapters-4c8mBiD5.js.map} +1 -1
- package/dist/esm/sdk/{createAdapters-1Hmc1vVC.js → createAdapters-DF32SIZa.js} +1 -10
- package/dist/esm/sdk/{defaultConfigs-BmCU1_qI.js → defaultConfigs-BQqiXif-.js} +3 -1
- package/dist/esm/sdk/{delegateAction-DdkvFFKA.js → delegateAction-Bq5zkOvn.js} +1 -1
- package/dist/esm/sdk/{emailRecovery-4J-g9tlY.js → emailRecovery-B1hbE_sM.js} +6 -6
- package/dist/esm/sdk/{getDeviceNumber-f8bfPB9U.js → getDeviceNumber-WiNzKx1x.js} +4 -2
- package/dist/esm/sdk/{getDeviceNumber-f8bfPB9U.js.map → getDeviceNumber-WiNzKx1x.js.map} +1 -1
- package/dist/esm/sdk/{linkDevice-C98klpcE.js → linkDevice-CRPf5aW2.js} +5 -5
- package/dist/esm/sdk/{localOnly-40zxrBMm.js → localOnly-COpDBMkm.js} +2 -2
- package/dist/esm/sdk/{localOnly-40zxrBMm.js.map → localOnly-COpDBMkm.js.map} +1 -1
- package/dist/esm/sdk/{localOnly-BZPBj14l.js → localOnly-DQQuqgjJ.js} +1 -1
- package/dist/esm/sdk/{login-DnROv3eA.js → login-DUIWZHp_.js} +4 -4
- package/dist/esm/sdk/offline-export-app.js +32 -21
- package/dist/esm/sdk/offline-export-app.js.map +1 -1
- package/dist/esm/sdk/{registration-BP9M3tE1.js → registration-BR2G9tz_.js} +59 -68
- package/dist/esm/sdk/{registration-MrAOC8Ub.js → registration-R70lvG_o.js} +60 -69
- package/dist/esm/sdk/registration-R70lvG_o.js.map +1 -0
- package/dist/esm/sdk/{relay-Dq9D7fhG.js → relay-BCEyWFew.js} +1 -1
- package/dist/esm/sdk/{router-BEGGuWaB.js → router-Cj2WexK-.js} +3 -3
- package/dist/esm/sdk/{rpcCalls-CMzj_Va_.js → rpcCalls-C1sp-Epo.js} +3 -3
- package/dist/esm/sdk/{rpcCalls-B44MZora.js → rpcCalls-VL4loDKP.js} +2 -2
- package/dist/esm/sdk/{scanDevice-Cp-r-Z2T.js → scanDevice-C0HcnZym.js} +5 -5
- package/dist/esm/sdk/{sdkSentEvents-CzAZBFjP.js → sdkSentEvents-BfkcI7EN.js} +3 -2
- package/dist/esm/sdk/{signNEP413-DsyWH_Jo.js → signNEP413-lj0swHsD.js} +1 -1
- package/dist/esm/sdk/{syncAccount-CqWCmBVb.js → syncAccount-DnQ9AstS.js} +5 -5
- package/dist/esm/sdk/{syncAccount-Dt5jJbEB.js → syncAccount-xh81Vppo.js} +3 -3
- package/dist/esm/sdk/{transactions-DAZrPW-6.js → transactions-Cg1TIUyK.js} +76 -77
- package/dist/esm/sdk/{transactions-CrjP8yPD.js → transactions-CxsklyCK.js} +77 -78
- package/dist/esm/sdk/transactions-CxsklyCK.js.map +1 -0
- package/dist/esm/sdk/wallet-iframe-host.js +160 -105
- package/dist/esm/server/core/AuthService.js +49 -6
- package/dist/esm/server/core/AuthService.js.map +1 -1
- package/dist/esm/server/sdk/src/core/defaultConfigs.js.map +1 -1
- package/dist/esm/wasm_vrf_worker/pkg/wasm_vrf_worker_bg.wasm +0 -0
- package/dist/types/src/__tests__/setup/bootstrap.d.ts.map +1 -1
- package/dist/types/src/core/IndexedDBManager/passkeyClientDB.d.ts +1 -1
- package/dist/types/src/core/IndexedDBManager/passkeyClientDB.d.ts.map +1 -1
- package/dist/types/src/core/TatchiPasskey/faucets/createAccountRelayServer.d.ts +6 -6
- package/dist/types/src/core/TatchiPasskey/faucets/createAccountRelayServer.d.ts.map +1 -1
- package/dist/types/src/core/TatchiPasskey/registration.d.ts.map +1 -1
- package/dist/types/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.d.ts +0 -5
- package/dist/types/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.d.ts.map +1 -1
- package/dist/types/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.d.ts.map +1 -1
- package/dist/types/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.d.ts.map +1 -1
- package/dist/types/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.d.ts.map +1 -1
- package/dist/types/src/core/WebAuthnManager/index.d.ts +1 -1
- package/dist/types/src/core/WebAuthnManager/index.d.ts.map +1 -1
- package/dist/types/src/core/defaultConfigs.d.ts.map +1 -1
- package/dist/types/src/core/types/sdkSentEvents.d.ts +18 -7
- package/dist/types/src/core/types/sdkSentEvents.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/preload.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/shell.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/skeleton.d.ts +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/skeleton.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.d.ts.map +1 -1
- package/dist/types/src/server/core/AuthService.d.ts.map +1 -1
- package/dist/workers/wasm_vrf_worker_bg.wasm +0 -0
- package/package.json +4 -4
- package/dist/cjs/react/components/PasskeyAuthMenu/PasskeyAuthMenu-CRlobBrN.css.map +0 -1
- package/dist/esm/react/components/PasskeyAuthMenu/PasskeyAuthMenu-D2VHZ04W.css.map +0 -1
- package/dist/esm/sdk/registration-MrAOC8Ub.js.map +0 -1
- package/dist/esm/sdk/transactions-CrjP8yPD.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.js","names":["LoginPhase","LoginStatus","parseDeviceNumber","err: any","getUserFriendlyErrorMessage","signingSession: SigningSessionStatus","IndexedDBManager","normalizeThresholdEd25519ParticipantIds","buildThresholdSessionPolicy","makeThresholdEd25519AuthSessionCacheKey","e: any","clientVerifyingShareB64u: string | null","mintThresholdEd25519AuthSession","computeLoginIntentDigest","authenticatorsToAllowCredentials","serverSessionJwt: string | undefined","verifyAuthenticationResponse","loginResult: LoginResult","createRandomVRFChallenge","hintUserPromise: Promise<ClientUserData | null>","userData: ClientUserData | null","unlockResult: { success: boolean; error?: string }","unlockCredential: WebAuthnAuthenticationCredential | undefined","error: any","relayerUrl","refreshErr: any","result: LoginResult"],"sources":["../../../../src/core/TatchiPasskey/login.ts"],"sourcesContent":["import type {\n AfterCall,\n LoginHooksOptions,\n LoginSSEvent,\n} from '../types/sdkSentEvents';\nimport { LoginPhase, LoginStatus } from '../types/sdkSentEvents';\nimport type {\n GetRecentLoginsResult,\n LoginAndCreateSessionResult,\n LoginResult,\n LoginSession,\n LoginState,\n SigningSessionStatus,\n} from '../types/tatchi';\nimport type { PasskeyManagerContext } from './index';\nimport type { AccountId } from '../types/accountIds';\nimport type { WebAuthnAuthenticationCredential } from '../types/webauthn';\nimport { getUserFriendlyErrorMessage } from '../../utils/errors';\nimport { createRandomVRFChallenge, VRFChallenge } from '../types/vrf-worker';\nimport { authenticatorsToAllowCredentials } from '../WebAuthnManager/touchIdPrompt';\nimport { IndexedDBManager } from '../IndexedDBManager';\nimport type { ClientAuthenticatorData, ClientUserData } from '../IndexedDBManager';\nimport { verifyAuthenticationResponse } from '../rpcCalls';\nimport { computeLoginIntentDigest } from '../digests/intentDigest';\nimport { buildThresholdSessionPolicy } from '../threshold/thresholdSessionPolicy';\nimport { parseDeviceNumber } from '../WebAuthnManager/SignerWorkerManager/getDeviceNumber';\nimport {\n clearAllCachedThresholdEd25519AuthSessions,\n makeThresholdEd25519AuthSessionCacheKey,\n mintThresholdEd25519AuthSession,\n putCachedThresholdEd25519AuthSession,\n} from '../threshold/thresholdEd25519AuthSession';\nimport { normalizeThresholdEd25519ParticipantIds } from '../../threshold/participants';\n\ntype WarmSigningSessionPolicy = { ttlMs: number; remainingUses: number };\n\ntype ThresholdSessionPlan = {\n sessionKind: 'jwt';\n relayerUrl: string;\n relayerKeyId: string;\n cacheKey: string;\n policy: Awaited<ReturnType<typeof buildThresholdSessionPolicy>>;\n deviceNumber: number;\n};\n\n/**\n * Core login function that handles passkey authentication without React dependencies.\n *\n * - Unlocks the VRF keypair (Shamir 3‑pass auto‑unlock when possible; falls back to TouchID).\n * - Updates local login state and returns success with account/public key info.\n * - Optional: mints a server session when `options.session` is provided.\n * - Generates a fresh, chain‑anchored VRF challenge (using latest block).\n * - Collects a WebAuthn assertion over the VRF output and posts to the relay route\n * (defaults to `/verify-authentication-response`).\n * - When `kind: 'jwt'`, returns the token in `result.jwt`.\n * - When `kind: 'cookie'`, the server sets an HttpOnly cookie and no JWT is returned.\n */\nexport async function loginAndCreateSession(\n context: PasskeyManagerContext,\n nearAccountId: AccountId,\n options?: LoginHooksOptions\n): Promise<LoginAndCreateSessionResult> {\n const { onEvent, onError, afterCall } = options || {};\n const { webAuthnManager } = context;\n\n onEvent?.({\n step: 1,\n phase: LoginPhase.STEP_1_PREPARATION,\n status: LoginStatus.PROGRESS,\n message: `Starting login for ${nearAccountId}`\n });\n\n const prevStatus = await webAuthnManager.checkVrfStatus();\n const prevVrfAccountId = prevStatus?.active ? prevStatus.nearAccountId : null;\n\n // If this call activates VRF then fails, clear the partial session.\n const rollbackVrfOnFailure = async () => {\n const status = await webAuthnManager.checkVrfStatus();\n const current = status?.active ? status.nearAccountId : null;\n if (current && current !== prevVrfAccountId) {\n await logoutAndClearSession(context);\n }\n };\n\n try {\n // Validation\n if (!window.isSecureContext) {\n const errorMessage = 'Passkey operations require a secure context (HTTPS or localhost).';\n return await finalizeLoginError({\n message: errorMessage,\n error: new Error(errorMessage),\n rollbackVrfOnFailure,\n onEvent,\n onError,\n afterCall,\n callAfterCall: false,\n });\n }\n\n const session = options?.session;\n const wantsServerSession = session !== undefined;\n const deviceNumberHint = parseDeviceNumber(options?.deviceNumber, { min: 1 });\n const base = await handleLoginUnlockVRF(\n context,\n nearAccountId,\n onEvent,\n onError,\n afterCall,\n // Defer final 'login-complete' event & afterCall until warm signing session is minted\n true,\n deviceNumberHint\n );\n // If base login failed, just return\n if (!base.result.success) {\n return base.result;\n }\n\n const preferredDeviceNumber = deviceNumberHint ?? base.activeDeviceNumber;\n const warmPolicy = resolveWarmSigningSessionPolicy(context, options);\n\n const wantsThresholdSession =\n webAuthnManager.getUserPreferences().getSignerMode().mode === 'threshold-signer';\n const relayUrl = (session?.relayUrl || context.configs.relayer.url).trim();\n const verifyRoute = (session?.route || '/verify-authentication-response').trim();\n\n const thresholdPlan = await prepareThresholdSessionPlan({\n context,\n nearAccountId,\n preferredDeviceNumber,\n relayUrl,\n ttlMs: warmPolicy.ttlMs,\n remainingUses: warmPolicy.remainingUses,\n wantsThresholdSession,\n });\n\n const wantsRelayerSession = wantsServerSession || thresholdPlan !== null;\n if (wantsRelayerSession && !relayUrl) {\n console.warn('[login] No relayUrl provided for session-style signing');\n }\n\n if (wantsRelayerSession && relayUrl) {\n return await runRelayerSessionFlow({\n context,\n nearAccountId,\n baseLoginResult: base.result,\n baseUnlockCredential: base.unlockCredential,\n preferredDeviceNumber,\n session,\n relayUrl,\n verifyRoute,\n thresholdPlan,\n warmPolicy,\n rollbackVrfOnFailure,\n onEvent,\n onError,\n afterCall,\n });\n }\n\n // No relayer session requested (or relayUrl missing): mint/refresh warm signing session only.\n await mintWarmSigningSession({\n context,\n nearAccountId,\n onEvent,\n credential: base.unlockCredential,\n ttlMs: warmPolicy.ttlMs,\n remainingUses: warmPolicy.remainingUses,\n });\n return await finalizeLoginSuccess({\n webAuthnManager,\n nearAccountId,\n loginResult: base.result,\n onEvent,\n afterCall,\n });\n } catch (err: any) {\n const errorMessage =\n getUserFriendlyErrorMessage(err, 'login') || err?.message || 'Login failed';\n return await finalizeLoginError({\n message: errorMessage,\n error: err,\n rollbackVrfOnFailure,\n onEvent,\n onError,\n afterCall,\n });\n }\n}\n\nfunction resolveWarmSigningSessionPolicy(\n context: PasskeyManagerContext,\n options?: LoginHooksOptions\n): WarmSigningSessionPolicy {\n const defaults = context.configs.signingSessionDefaults;\n return {\n ttlMs: options?.signingSession?.ttlMs ?? defaults.ttlMs,\n remainingUses: options?.signingSession?.remainingUses ?? defaults.remainingUses,\n };\n}\n\nasync function attachSigningSessionStatus(args: {\n webAuthnManager: PasskeyManagerContext['webAuthnManager'];\n nearAccountId: AccountId;\n loginResult: LoginResult;\n}): Promise<LoginAndCreateSessionResult> {\n const { webAuthnManager, nearAccountId, loginResult } = args;\n if (!loginResult.success) return loginResult;\n try {\n const signingSession: SigningSessionStatus =\n await webAuthnManager.getWarmSigningSessionStatus(nearAccountId);\n return { ...loginResult, signingSession };\n } catch {\n return loginResult;\n }\n}\n\nasync function finalizeLoginSuccess(args: {\n webAuthnManager: PasskeyManagerContext['webAuthnManager'];\n nearAccountId: AccountId;\n loginResult: LoginResult;\n onEvent?: (event: LoginSSEvent) => void;\n afterCall?: AfterCall<LoginAndCreateSessionResult>;\n}): Promise<LoginAndCreateSessionResult> {\n const { webAuthnManager, nearAccountId, loginResult, onEvent, afterCall } = args;\n const finalResult = await attachSigningSessionStatus({\n webAuthnManager,\n nearAccountId,\n loginResult,\n });\n onEvent?.({\n step: 4,\n phase: LoginPhase.STEP_4_LOGIN_COMPLETE,\n status: LoginStatus.SUCCESS,\n message: 'Login completed successfully',\n nearAccountId,\n clientNearPublicKey: loginResult.clientNearPublicKey ?? '',\n });\n await afterCall?.(true, finalResult);\n return finalResult;\n}\n\nasync function finalizeLoginError(args: {\n message: string;\n error?: unknown;\n rollbackVrfOnFailure: () => Promise<void>;\n onEvent?: (event: LoginSSEvent) => void;\n onError?: (error: Error) => void;\n afterCall?: AfterCall<LoginAndCreateSessionResult>;\n callOnError?: boolean;\n callAfterCall?: boolean;\n}): Promise<LoginAndCreateSessionResult> {\n const {\n message,\n error,\n rollbackVrfOnFailure,\n onEvent,\n onError,\n afterCall,\n callOnError = true,\n callAfterCall = true,\n } = args;\n\n try { await rollbackVrfOnFailure(); } catch {}\n\n if (callOnError) {\n onError?.(error as any);\n }\n\n onEvent?.({\n step: 0,\n phase: LoginPhase.LOGIN_ERROR,\n status: LoginStatus.ERROR,\n message,\n error: message,\n });\n\n if (callAfterCall) {\n await afterCall?.(false);\n }\n return { success: false, error: message };\n}\n\nasync function prepareThresholdSessionPlan(args: {\n context: PasskeyManagerContext;\n nearAccountId: AccountId;\n preferredDeviceNumber: number | null;\n relayUrl: string;\n ttlMs: number;\n remainingUses: number;\n wantsThresholdSession: boolean;\n}): Promise<ThresholdSessionPlan | null> {\n const {\n context,\n nearAccountId,\n preferredDeviceNumber,\n relayUrl,\n ttlMs,\n remainingUses,\n wantsThresholdSession,\n } = args;\n if (!wantsThresholdSession || !relayUrl) return null;\n\n const { webAuthnManager } = context;\n\n try {\n const rpId = webAuthnManager.getRpId();\n if (!rpId) throw new Error('Missing rpId for threshold session');\n\n const lastUser = await webAuthnManager.getLastUser();\n const deviceNumber = preferredDeviceNumber ??\n (lastUser?.nearAccountId === nearAccountId\n ? lastUser.deviceNumber\n : (await IndexedDBManager.clientDB.getLastDBUpdatedUser(nearAccountId))\n ?.deviceNumber ?? null);\n\n if (deviceNumber === null) {\n console.warn('[login] threshold-signer configured but no threshold key material found; skipping threshold session');\n return null;\n }\n\n const thresholdKeyMaterial = await IndexedDBManager.nearKeysDB.getThresholdKeyMaterial(\n nearAccountId,\n deviceNumber\n );\n const relayerKeyId = thresholdKeyMaterial?.relayerKeyId || null;\n const participantIds = thresholdKeyMaterial?.participants?.map((p) => p.id) || null;\n const normalizedParticipantIds = normalizeThresholdEd25519ParticipantIds(participantIds);\n\n if (!relayerKeyId) {\n console.warn('[login] threshold-signer configured but no threshold key material found; skipping threshold session');\n return null;\n }\n\n if (!normalizedParticipantIds || normalizedParticipantIds.length < 2) {\n console.warn('[login] threshold key material missing/invalid participantIds; skipping threshold session mint');\n return null;\n }\n\n const policy = await buildThresholdSessionPolicy({\n nearAccountId,\n rpId,\n relayerKeyId,\n ...(normalizedParticipantIds?.length ? { participantIds: normalizedParticipantIds } : {}),\n ttlMs,\n remainingUses,\n });\n\n const cacheKey = makeThresholdEd25519AuthSessionCacheKey({\n nearAccountId,\n rpId,\n relayerUrl: relayUrl,\n relayerKeyId,\n ...(normalizedParticipantIds?.length ? { participantIds: normalizedParticipantIds } : {}),\n });\n\n return {\n sessionKind: 'jwt',\n relayerUrl: relayUrl,\n relayerKeyId,\n cacheKey,\n policy,\n deviceNumber,\n };\n } catch (e: any) {\n console.warn(\n '[login] failed to prepare threshold session policy; skipping threshold session mint:',\n e?.message || e\n );\n return null;\n }\n}\n\nasync function mintThresholdSessionBestEffort(args: {\n context: PasskeyManagerContext;\n nearAccountId: AccountId;\n plan: ThresholdSessionPlan;\n vrfChallenge: VRFChallenge;\n credential: WebAuthnAuthenticationCredential;\n}): Promise<void> {\n const { context, nearAccountId, plan, vrfChallenge, credential } = args;\n const { webAuthnManager } = context;\n\n let clientVerifyingShareB64u: string | null = null;\n try {\n const localKeyMaterial = await IndexedDBManager.nearKeysDB.getLocalKeyMaterial(\n nearAccountId,\n plan.deviceNumber\n );\n const wrapKeySalt = String(localKeyMaterial?.wrapKeySalt || '').trim();\n if (wrapKeySalt) {\n const derived =\n await webAuthnManager.deriveThresholdEd25519ClientVerifyingShareFromCredential({\n credential,\n nearAccountId,\n wrapKeySalt,\n });\n if (derived.success && derived.clientVerifyingShareB64u) {\n clientVerifyingShareB64u = derived.clientVerifyingShareB64u;\n }\n }\n } catch (e: any) {\n console.warn(\n '[login] failed to derive clientVerifyingShareB64u for threshold session mint:',\n e?.message || e\n );\n }\n\n if (!clientVerifyingShareB64u) {\n console.warn(\n '[login] threshold session mint skipped: missing clientVerifyingShareB64u'\n );\n return;\n }\n\n const minted = await mintThresholdEd25519AuthSession({\n relayerUrl: plan.relayerUrl,\n sessionKind: plan.sessionKind,\n relayerKeyId: plan.relayerKeyId,\n clientVerifyingShareB64u,\n sessionPolicy: plan.policy.policy,\n vrfChallenge,\n webauthnAuthentication: credential,\n });\n\n if (minted.ok && minted.jwt) {\n putCachedThresholdEd25519AuthSession(plan.cacheKey, {\n sessionKind: plan.sessionKind,\n policy: plan.policy.policy,\n policyJson: plan.policy.policyJson,\n sessionPolicyDigest32: plan.policy.sessionPolicyDigest32,\n jwt: minted.jwt,\n ...(minted.expiresAtMs ? { expiresAtMs: minted.expiresAtMs } : {}),\n });\n return;\n }\n\n if (!minted.ok) {\n console.warn(\n '[login] threshold session mint failed:',\n minted.code || minted.message || 'unknown error'\n );\n }\n}\n\nasync function runRelayerSessionFlow(args: {\n context: PasskeyManagerContext;\n nearAccountId: AccountId;\n baseLoginResult: LoginResult;\n baseUnlockCredential?: WebAuthnAuthenticationCredential;\n preferredDeviceNumber: number | null;\n session: LoginHooksOptions['session'] | undefined;\n relayUrl: string;\n verifyRoute: string;\n thresholdPlan: ThresholdSessionPlan | null;\n warmPolicy: WarmSigningSessionPolicy;\n rollbackVrfOnFailure: () => Promise<void>;\n onEvent?: (event: LoginSSEvent) => void;\n onError?: (error: Error) => void;\n afterCall?: AfterCall<LoginAndCreateSessionResult>;\n}): Promise<LoginAndCreateSessionResult> {\n const {\n context,\n nearAccountId,\n baseLoginResult,\n baseUnlockCredential,\n preferredDeviceNumber,\n session,\n relayUrl,\n verifyRoute,\n thresholdPlan,\n warmPolicy,\n rollbackVrfOnFailure,\n onEvent,\n onError,\n afterCall,\n } = args;\n\n const { webAuthnManager } = context;\n\n try {\n const rpId = webAuthnManager.getRpId();\n if (!rpId) {\n throw new Error('Missing rpId for VRF challenge generation during login');\n }\n\n const blockInfo = await context.nearClient.viewBlock({ finality: 'final' });\n const txBlockHash = blockInfo?.header?.hash;\n const txBlockHeight = String(blockInfo.header?.height ?? '');\n const intentDigest = await computeLoginIntentDigest({ nearAccountId, rpId });\n const vrfChallenge = await webAuthnManager.generateVrfChallengeOnce({\n userId: nearAccountId,\n rpId,\n blockHash: txBlockHash,\n blockHeight: txBlockHeight,\n intentDigest,\n ...(thresholdPlan ? { sessionPolicyDigest32: thresholdPlan.policy.sessionPolicyDigest32 } : {}),\n });\n\n const authenticators = await webAuthnManager.getAuthenticatorsByUser(nearAccountId);\n const authenticatorsForPrompt = prioritizeAuthenticatorsByDeviceNumber(\n authenticators,\n preferredDeviceNumber\n );\n const credential = await webAuthnManager.getAuthenticationCredentialsSerialized({\n nearAccountId,\n challenge: vrfChallenge,\n allowCredentials: authenticatorsToAllowCredentials(authenticatorsForPrompt),\n });\n\n const effectiveLoginResult = await bindVrfToSelectedLoginPasskeyDevice({\n webAuthnManager,\n nearAccountId,\n authenticators,\n credential,\n baseUnlockCredential,\n baseLoginResult: baseLoginResult,\n });\n\n await mintWarmSigningSession({\n context,\n nearAccountId,\n onEvent,\n credential,\n ttlMs: warmPolicy.ttlMs,\n remainingUses: warmPolicy.remainingUses,\n });\n\n let serverSessionJwt: string | undefined;\n if (session) {\n const v = await verifyAuthenticationResponse(\n relayUrl,\n verifyRoute,\n session.kind,\n vrfChallenge,\n credential\n );\n if (!v.success || !v.verified) {\n const errMsg = v.error || 'Session verification failed';\n return await finalizeLoginError({\n message: errMsg,\n rollbackVrfOnFailure,\n onEvent,\n afterCall,\n callOnError: false,\n });\n }\n serverSessionJwt = v.jwt;\n }\n\n if (thresholdPlan) {\n await mintThresholdSessionBestEffort({\n context,\n nearAccountId,\n plan: thresholdPlan,\n vrfChallenge,\n credential,\n });\n }\n\n const loginResult: LoginResult = serverSessionJwt\n ? { ...effectiveLoginResult, jwt: serverSessionJwt }\n : effectiveLoginResult;\n\n return await finalizeLoginSuccess({\n webAuthnManager,\n nearAccountId,\n loginResult,\n onEvent,\n afterCall,\n });\n } catch (e: any) {\n console.error('[login] Failed to start session:', e);\n const errMsg =\n getUserFriendlyErrorMessage(e, 'login') ||\n e?.message ||\n 'Session verification failed';\n return await finalizeLoginError({\n message: errMsg,\n error: e,\n rollbackVrfOnFailure,\n onEvent,\n onError,\n afterCall,\n });\n }\n}\n\n/**\n * When multiple passkeys exist for the same account, the user can select any of them in the WebAuthn prompt.\n * If the VRF worker was auto-unlocked using a different device (e.g. Shamir 3-pass based on the \"latest\" row),\n * we must rebind the VRF worker to the same deviceNumber as the selected credential. Otherwise, WrapKeySeed\n * derivation can mix PRF.first(from selected credential) with vrf_sk(from a different device), which later\n * causes vault decryption failures (e.g. `aead::Error` during private key export).\n */\nasync function bindVrfToSelectedLoginPasskeyDevice(args: {\n webAuthnManager: PasskeyManagerContext['webAuthnManager'];\n nearAccountId: AccountId;\n authenticators: ClientAuthenticatorData[];\n credential: WebAuthnAuthenticationCredential;\n baseUnlockCredential?: WebAuthnAuthenticationCredential;\n baseLoginResult: LoginResult;\n}): Promise<LoginResult> {\n const {\n webAuthnManager,\n nearAccountId,\n authenticators,\n credential,\n baseUnlockCredential,\n baseLoginResult,\n } = args;\n\n // Start with the base login result (may reflect whichever VRF keypair was auto-unlocked).\n // If the user selects a different passkey below, update it to match the chosen device.\n let effectiveLoginResult = baseLoginResult;\n\n if (authenticators.length <= 1) {\n return effectiveLoginResult;\n }\n\n const rawId = credential.rawId;\n const matched = authenticators.find((a) => a.credentialId === rawId);\n const selectedDeviceNumber = matched?.deviceNumber ?? null;\n\n if (selectedDeviceNumber === null) {\n return effectiveLoginResult;\n }\n\n const userForDevice = await IndexedDBManager.clientDB\n .getUserByDevice(nearAccountId, selectedDeviceNumber)\n .catch(() => null);\n\n if (userForDevice?.clientNearPublicKey) {\n effectiveLoginResult = {\n ...effectiveLoginResult,\n clientNearPublicKey: userForDevice.clientNearPublicKey,\n };\n }\n\n const shouldRebindVrf = !baseUnlockCredential || baseUnlockCredential.rawId !== rawId;\n if (shouldRebindVrf) {\n const shamir = userForDevice?.serverEncryptedVrfKeypair;\n if (!shamir?.ciphertextVrfB64u || !shamir?.kek_s_b64u || !shamir?.serverKeyId) {\n throw new Error(\n `Missing serverEncryptedVrfKeypair for account ${nearAccountId} device ${selectedDeviceNumber}. ` +\n 'Open the wallet once online to refresh local state, then try again.'\n );\n }\n\n const unlock = await webAuthnManager.shamir3PassDecryptVrfKeypair({\n nearAccountId,\n kek_s_b64u: shamir.kek_s_b64u,\n ciphertextVrfB64u: shamir.ciphertextVrfB64u,\n serverKeyId: shamir.serverKeyId,\n });\n if (!unlock.success) {\n throw new Error(\n unlock.error ||\n 'Failed to bind VRF keypair to the passkey you selected. Please try again.'\n );\n }\n }\n\n // Persist the deviceNumber that the user actually selected so subsequent flows (export/signing)\n // use the correct vault entry.\n await webAuthnManager.setLastUser(nearAccountId, selectedDeviceNumber);\n // Best-effort: stamp the selected device as the one last used for login.\n await webAuthnManager.updateLastLogin(nearAccountId).catch(() => undefined);\n\n return effectiveLoginResult;\n}\n\n/**\n * Mint or refresh a \"warm signing session\" in the VRF worker.\n *\n * Notes:\n * - Reuses a previously collected WebAuthn credential when provided (e.g. TouchID fallback),\n * to avoid prompting the user twice in a single login flow.\n * - Session TTL/remainingUses policy is enforced by the VRF worker.\n */\nasync function mintWarmSigningSession(args: {\n context: PasskeyManagerContext;\n nearAccountId: AccountId;\n onEvent?: (event: LoginSSEvent) => void;\n credential?: WebAuthnAuthenticationCredential;\n ttlMs: number;\n remainingUses: number;\n}): Promise<void> {\n const { context, nearAccountId, onEvent, credential, ttlMs, remainingUses } = args;\n const { webAuthnManager } = context;\n\n onEvent?.({\n step: 3,\n phase: LoginPhase.STEP_3_VRF_UNLOCK,\n status: LoginStatus.PROGRESS,\n message: 'Unlocking warm signing session...'\n });\n\n // If we already performed a TouchID ceremony (e.g., VRF unlock fallback),\n // reuse that credential to avoid a second prompt.\n let effectiveCredential = credential;\n if (!effectiveCredential) {\n const challenge = createRandomVRFChallenge();\n const authenticators = await webAuthnManager.getAuthenticatorsByUser(nearAccountId);\n const { authenticatorsForPrompt } = await IndexedDBManager.clientDB.ensureCurrentPasskey(\n nearAccountId,\n authenticators,\n );\n effectiveCredential = await webAuthnManager.getAuthenticationCredentialsSerialized({\n nearAccountId,\n challenge: challenge as VRFChallenge,\n allowCredentials: authenticatorsToAllowCredentials(authenticatorsForPrompt),\n });\n }\n\n await webAuthnManager.mintSigningSessionFromCredential({\n nearAccountId,\n credential: effectiveCredential,\n ttlMs,\n remainingUses,\n });\n\n onEvent?.({\n step: 3,\n phase: LoginPhase.STEP_3_VRF_UNLOCK,\n status: LoginStatus.SUCCESS,\n message: 'Warm signing session unlocked'\n });\n}\n\n/**\n * Handle onchain (serverless) login using VRF flow per docs/vrf_challenges.md\n *\n * VRF AUTHENTICATION FLOW:\n * 1. Unlock VRF keypair in VRF Worker memory, either\n * - Decrypt via Shamir 3-pass (when Relayer is present), or\n * - Re-derive the VRF via credentials inside VRF worker dynamically\n * 2. Generate VRF challenge using stored VRF keypair + NEAR block data (no TouchID needed)\n * 3. Use VRF output as WebAuthn challenge for authentication\n * 4. Verify VRF proof and WebAuthn response on contract simultaneously\n * - VRF proof assures WebAuthn challenge is fresh and valid (replay protection)\n * - WebAuthn verification for origin + biometric credentials + device authenticity\n */\nasync function handleLoginUnlockVRF(\n context: PasskeyManagerContext,\n nearAccountId: AccountId,\n onEvent?: (event: LoginSSEvent) => void,\n onError?: (error: Error) => void,\n afterCall?: AfterCall<LoginAndCreateSessionResult>,\n deferCompletionHooks?: boolean,\n deviceNumberHint: number | null = null,\n): Promise<{\n result: LoginResult;\n usedFallbackTouchId: boolean;\n unlockCredential?: WebAuthnAuthenticationCredential;\n activeDeviceNumber: number | null;\n}> {\n const { webAuthnManager } = context;\n\n try {\n // Step 1: Get VRF credentials and authenticators, and validate them\n const hintUserPromise: Promise<ClientUserData | null> =\n deviceNumberHint !== null\n ? webAuthnManager.getUserByDevice(nearAccountId, deviceNumberHint).catch(() => null)\n : Promise.resolve(null);\n\n const [hintUser, lastUser, latestByAccount, authenticators] = await Promise.all([\n hintUserPromise,\n webAuthnManager.getLastUser(),\n IndexedDBManager.clientDB.getLastDBUpdatedUser(nearAccountId),\n webAuthnManager.getAuthenticatorsByUser(nearAccountId),\n ]);\n\n // Prefer the most recently updated record for this account; fall back to lastUser pointer.\n let userData: ClientUserData | null = null;\n if (hintUser && hintUser.nearAccountId === nearAccountId) {\n userData = hintUser;\n } else if (latestByAccount && latestByAccount.nearAccountId === nearAccountId) {\n userData = latestByAccount;\n } else if (lastUser && lastUser.nearAccountId === nearAccountId) {\n userData = lastUser;\n } else {\n userData = await webAuthnManager.getUserByDevice(nearAccountId, 1);\n }\n\n // Validate user data and authenticators\n if (!userData) {\n throw new Error(`User data not found for ${nearAccountId} in IndexedDB. Please register an account.`);\n }\n if (!userData.clientNearPublicKey) {\n throw new Error(`No NEAR public key found for ${nearAccountId}. Please register an account.`);\n }\n if (\n !userData.encryptedVrfKeypair?.encryptedVrfDataB64u ||\n !userData.encryptedVrfKeypair?.chacha20NonceB64u\n ) {\n throw new Error('No VRF credentials found. Please register an account.');\n }\n if (authenticators.length === 0) {\n throw new Error(`No authenticators found for account ${nearAccountId}. Please register.`);\n }\n\n // Step 2: Try Shamir 3-pass commutative unlock first (no TouchID required), fallback to TouchID\n onEvent?.({\n step: 2,\n phase: LoginPhase.STEP_2_WEBAUTHN_ASSERTION,\n status: LoginStatus.PROGRESS,\n message: 'Unlocking VRF keys...'\n });\n\n let unlockResult: { success: boolean; error?: string } = { success: false };\n let usedFallbackTouchId = false;\n let unlockCredential: WebAuthnAuthenticationCredential | undefined;\n let activeDeviceNumber = userData.deviceNumber;\n // Effective user row whose VRF/NEAR keys are actually used for this login.\n // May be switched when multiple devices exist and the user picks a different passkey.\n let effectiveUserData = userData;\n\n const shamir = userData.serverEncryptedVrfKeypair;\n const relayerUrl = context.configs.relayer?.url;\n const useShamir3PassVRFKeyUnlock = !!relayerUrl && !!shamir?.serverKeyId;\n\n if (useShamir3PassVRFKeyUnlock && shamir) {\n try {\n if (!shamir.ciphertextVrfB64u || !shamir.kek_s_b64u) {\n throw new Error('Missing Shamir3Pass fields (ciphertextVrfB64u/kek_s_b64u)');\n }\n\n unlockResult = await webAuthnManager.shamir3PassDecryptVrfKeypair({\n nearAccountId,\n kek_s_b64u: shamir.kek_s_b64u,\n ciphertextVrfB64u: shamir.ciphertextVrfB64u,\n serverKeyId: shamir.serverKeyId,\n });\n\n if (unlockResult.success) {\n const vrfStatus = await webAuthnManager.checkVrfStatus();\n const active = vrfStatus.active && vrfStatus.nearAccountId === nearAccountId;\n if (!active) {\n unlockResult = { success: false, error: 'VRF session inactive after Shamir3Pass' };\n }\n if (active) {\n // Proactive rotation if serverKeyId changed and we unlocked via Shamir\n await webAuthnManager.maybeProactiveShamirRefresh(nearAccountId);\n }\n } else {\n throw new Error(`Shamir3Pass auto-unlock failed: ${unlockResult.error}`);\n }\n } catch (error: any) {\n unlockResult = { success: false, error: error.message };\n }\n }\n\n // Fallback to TouchID if Shamir3Pass decryption failed\n if (!unlockResult.success) {\n const authenticatorsForPrompt = prioritizeAuthenticatorsByDeviceNumber(authenticators, deviceNumberHint);\n const fallback = await fallbackUnlockVrfKeypairWithTouchId({\n webAuthnManager,\n nearAccountId,\n authenticators: authenticatorsForPrompt,\n userData,\n onEvent,\n });\n unlockResult = fallback.unlockResult;\n usedFallbackTouchId = fallback.usedFallbackTouchId;\n unlockCredential = fallback.unlockCredential;\n effectiveUserData = fallback.effectiveUserData;\n activeDeviceNumber = fallback.activeDeviceNumber;\n }\n\n if (!unlockResult.success) {\n throw new Error(`Failed to unlock VRF keypair: ${unlockResult.error}`);\n }\n\n onEvent?.({\n step: 3,\n phase: LoginPhase.STEP_3_VRF_UNLOCK,\n status: LoginStatus.SUCCESS,\n message: 'VRF keypair unlocked...'\n });\n\n // Proactive refresh: if Shamir3Pass failed and we used TouchID, re-encrypt under current server key\n try {\n const relayerUrl = context.configs.relayer?.url;\n if (usedFallbackTouchId && relayerUrl) {\n const refreshed = await webAuthnManager.shamir3PassEncryptCurrentVrfKeypair();\n await webAuthnManager.updateServerEncryptedVrfKeypair(nearAccountId, refreshed);\n }\n } catch (refreshErr: any) {\n console.warn('Non-fatal: Failed to refresh serverEncryptedVrfKeypair:', refreshErr?.message || refreshErr);\n }\n\n // Step 3: Update local data and return success\n // Ensure last-user deviceNumber reflects the passkey actually used for login.\n try {\n await webAuthnManager.setLastUser(nearAccountId, activeDeviceNumber);\n } catch {\n // Non-fatal; continue even if last-user update fails.\n }\n await webAuthnManager.updateLastLogin(nearAccountId);\n\n const result: LoginResult = {\n success: true,\n loggedInNearAccountId: nearAccountId,\n // Ensure the clientNearPublicKey reflects the device whose VRF credentials\n // are actually active for this login.\n clientNearPublicKey: effectiveUserData.clientNearPublicKey,\n nearAccountId: nearAccountId\n };\n\n if (!deferCompletionHooks) {\n onEvent?.({\n step: 4,\n phase: LoginPhase.STEP_4_LOGIN_COMPLETE,\n status: LoginStatus.SUCCESS,\n message: 'Login completed successfully',\n nearAccountId: nearAccountId,\n clientNearPublicKey: effectiveUserData.clientNearPublicKey\n });\n afterCall?.(true, result);\n }\n return { result, usedFallbackTouchId, unlockCredential, activeDeviceNumber };\n\n } catch (error: any) {\n // Use centralized error handling\n const errorMessage = getUserFriendlyErrorMessage(error, 'login');\n\n onError?.(error);\n onEvent?.({\n step: 0,\n phase: LoginPhase.LOGIN_ERROR,\n status: LoginStatus.ERROR,\n message: errorMessage,\n error: errorMessage\n });\n\n const result: LoginResult = { success: false, error: errorMessage };\n afterCall?.(false);\n return { result, usedFallbackTouchId: false, activeDeviceNumber: null };\n }\n}\n\n/**\n * TouchID fallback path for VRF unlock.\n *\n * Used when Shamir 3-pass auto-unlock fails/unavailable:\n * - Prompts WebAuthn to obtain a serialized credential with PRF.first + PRF.second.\n * - Aligns the local encrypted VRF keypair blob with the passkey the user actually chose\n * (important when multiple devices/passkeys exist for the same account).\n * - Unlocks the VRF keypair inside the VRF worker and returns updated effective user context.\n */\nasync function fallbackUnlockVrfKeypairWithTouchId(args: {\n webAuthnManager: PasskeyManagerContext['webAuthnManager'];\n nearAccountId: AccountId;\n authenticators: ClientAuthenticatorData[];\n userData: ClientUserData;\n onEvent?: (event: LoginSSEvent) => void;\n}): Promise<{\n unlockResult: { success: boolean; error?: string };\n usedFallbackTouchId: boolean;\n unlockCredential: WebAuthnAuthenticationCredential;\n effectiveUserData: ClientUserData;\n activeDeviceNumber: number;\n}> {\n const { webAuthnManager, nearAccountId, authenticators, userData, onEvent } = args;\n\n onEvent?.({\n step: 2,\n phase: LoginPhase.STEP_2_WEBAUTHN_ASSERTION,\n status: LoginStatus.PROGRESS,\n message: 'Logging in, unlocking VRF credentials...'\n });\n\n const challenge = createRandomVRFChallenge();\n const credential = await webAuthnManager.getAuthenticationCredentialsSerializedDualPrf({\n nearAccountId,\n challenge: challenge as VRFChallenge,\n credentialIds: authenticators.map((a) => a.credentialId),\n });\n\n let effectiveUserData = userData;\n let activeDeviceNumber = userData.deviceNumber;\n\n // If multiple authenticators exist, align VRF credentials with the passkey\n // the user actually chose, based on credentialId → deviceNumber.\n if (authenticators.length > 1) {\n const rawId = credential.rawId;\n const matched = authenticators.find(a => a.credentialId === rawId);\n if (matched) {\n try {\n const byDevice = await IndexedDBManager.clientDB.getUserByDevice(nearAccountId, matched.deviceNumber);\n if (byDevice) {\n effectiveUserData = byDevice;\n activeDeviceNumber = matched.deviceNumber;\n }\n } catch {\n // If lookup by device fails, fall back to the base userData.\n }\n }\n }\n\n const unlockResult = await webAuthnManager.unlockVRFKeypair({\n nearAccountId: nearAccountId,\n encryptedVrfKeypair: {\n encryptedVrfDataB64u: effectiveUserData.encryptedVrfKeypair.encryptedVrfDataB64u,\n chacha20NonceB64u: effectiveUserData.encryptedVrfKeypair.chacha20NonceB64u,\n },\n credential: credential,\n });\n\n return {\n unlockResult,\n usedFallbackTouchId: unlockResult.success,\n unlockCredential: credential,\n effectiveUserData,\n activeDeviceNumber,\n };\n}\n\n/**\n * High-level login snapshot used by React contexts/UI.\n *\n * Returns:\n * - `login`: derived from IndexedDB last-user pointer + VRF worker status\n * - `signingSession`: warm signing session status when available\n *\n * The `nearAccountId` argument is treated as a \"query hint\" and must match the\n * last logged-in account to be considered logged in (prevents accidental cross-account reads).\n */\nexport async function getLoginSession(\n context: PasskeyManagerContext,\n nearAccountId?: AccountId\n): Promise<LoginSession> {\n const login = await getLoginStateInternal(context, nearAccountId);\n if (!login?.isLoggedIn || !login.nearAccountId) return { login, signingSession: null };\n try {\n const signingSession = await context.webAuthnManager.getWarmSigningSessionStatus(login.nearAccountId);\n return { login, signingSession };\n } catch {\n return { login, signingSession: null };\n }\n}\n\n/**\n * Internal helper for computing `LoginState`.\n *\n * Implementation detail:\n * - Trusts the IndexedDB last-user pointer for account selection, then confirms\n * that the VRF worker is actually active for that same account.\n */\nasync function getLoginStateInternal(\n context: PasskeyManagerContext,\n nearAccountId?: AccountId\n): Promise<LoginState> {\n const { webAuthnManager } = context;\n try {\n // Determine target account strictly from the last logged-in device.\n const lastUser = await webAuthnManager.getLastUser();\n const targetAccountId = nearAccountId ?? lastUser?.nearAccountId ?? null;\n\n // If caller requested a specific account, it must match the last logged-in account.\n if (!lastUser || (targetAccountId && lastUser.nearAccountId !== targetAccountId)) {\n return {\n isLoggedIn: false,\n nearAccountId: targetAccountId || null,\n publicKey: null,\n vrfActive: false,\n userData: null\n };\n }\n\n const userData = lastUser;\n const publicKey = userData?.clientNearPublicKey || null;\n\n // Check actual VRF worker status\n const vrfStatus = await webAuthnManager.checkVrfStatus();\n const vrfActive = vrfStatus.active && vrfStatus.nearAccountId === targetAccountId;\n\n // Determine if user is considered \"logged in\"\n // User is logged in if they have user data and VRF is active\n const isLoggedIn = !!(userData && userData.clientNearPublicKey && vrfActive);\n\n return {\n isLoggedIn,\n nearAccountId: targetAccountId,\n publicKey,\n vrfActive,\n userData,\n vrfSessionDuration: vrfStatus.sessionDuration || 0\n };\n\n } catch (error: any) {\n console.warn('Error getting login state:', error);\n return {\n isLoggedIn: false,\n nearAccountId: nearAccountId || null,\n publicKey: null,\n vrfActive: false,\n userData: null\n };\n }\n}\n\n/**\n * List recently used accounts from IndexedDB.\n *\n * Used for account picker UIs and initial app bootstrap state.\n */\nexport async function getRecentLogins(\n context: PasskeyManagerContext\n): Promise<GetRecentLoginsResult> {\n const { webAuthnManager } = context;\n // Get all user accounts from IndexDB\n const allUsersData = await webAuthnManager.getAllUsers();\n const accountIds = allUsersData.map(user => user.nearAccountId);\n // Get last used account for initial state\n const lastUsedAccount = await webAuthnManager.getLastUser();\n return {\n accountIds,\n lastUsedAccount,\n };\n}\n\n/**\n * Clear the active VRF session and any client-side nonce caches.\n *\n * This is the canonical \"logout\" operation for the SDK (does not delete accounts).\n */\nexport async function logoutAndClearSession(context: PasskeyManagerContext): Promise<void> {\n const { webAuthnManager } = context;\n await webAuthnManager.clearVrfSession();\n try { webAuthnManager.getNonceManager().clear(); } catch {}\n try { clearAllCachedThresholdEd25519AuthSessions(); } catch {}\n}\n\nfunction prioritizeAuthenticatorsByDeviceNumber(\n authenticators: ClientAuthenticatorData[],\n deviceNumber: number | null\n): ClientAuthenticatorData[] {\n if (authenticators.length <= 1) return authenticators;\n if (deviceNumber === null) return authenticators;\n const preferred = authenticators.filter((a) => a.deviceNumber === deviceNumber);\n if (preferred.length === 0) return authenticators;\n const rest = authenticators.filter((a) => a.deviceNumber !== deviceNumber);\n return [...preferred, ...rest];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAyDA,eAAsB,sBACpB,SACA,eACA,SACsC;CACtC,MAAM,EAAE,SAAS,SAAS,cAAc,WAAW;CACnD,MAAM,EAAE,oBAAoB;AAE5B,WAAU;EACR,MAAM;EACN,OAAOA,iCAAW;EAClB,QAAQC,kCAAY;EACpB,SAAS,sBAAsB;;CAGjC,MAAM,aAAa,MAAM,gBAAgB;CACzC,MAAM,mBAAmB,YAAY,SAAS,WAAW,gBAAgB;CAGzE,MAAM,uBAAuB,YAAY;EACvC,MAAM,SAAS,MAAM,gBAAgB;EACrC,MAAM,UAAU,QAAQ,SAAS,OAAO,gBAAgB;AACxD,MAAI,WAAW,YAAY,iBACzB,OAAM,sBAAsB;;AAIhC,KAAI;AAEF,MAAI,CAAC,OAAO,iBAAiB;GAC3B,MAAM,eAAe;AACrB,UAAO,MAAM,mBAAmB;IAC9B,SAAS;IACT,OAAO,IAAI,MAAM;IACjB;IACA;IACA;IACA;IACA,eAAe;;;EAInB,MAAM,UAAU,SAAS;EACzB,MAAM,qBAAqB,YAAY;EACvC,MAAM,mBAAmBC,0CAAkB,SAAS,cAAc,EAAE,KAAK;EACzE,MAAM,OAAO,MAAM,qBACjB,SACA,eACA,SACA,SACA,WAEA,MACA;AAGF,MAAI,CAAC,KAAK,OAAO,QACf,QAAO,KAAK;EAGd,MAAM,wBAAwB,oBAAoB,KAAK;EACvD,MAAM,aAAa,gCAAgC,SAAS;EAE5D,MAAM,wBACJ,gBAAgB,qBAAqB,gBAAgB,SAAS;EAChE,MAAM,YAAY,SAAS,YAAY,QAAQ,QAAQ,QAAQ,KAAK;EACpE,MAAM,eAAe,SAAS,SAAS,mCAAmC;EAE1E,MAAM,gBAAgB,MAAM,4BAA4B;GACtD;GACA;GACA;GACA;GACA,OAAO,WAAW;GAClB,eAAe,WAAW;GAC1B;;EAGF,MAAM,sBAAsB,sBAAsB,kBAAkB;AACpE,MAAI,uBAAuB,CAAC,SAC1B,SAAQ,KAAK;AAGf,MAAI,uBAAuB,SACzB,QAAO,MAAM,sBAAsB;GACjC;GACA;GACA,iBAAiB,KAAK;GACtB,sBAAsB,KAAK;GAC3B;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;;AAKJ,QAAM,uBAAuB;GAC3B;GACA;GACA;GACA,YAAY,KAAK;GACjB,OAAO,WAAW;GAClB,eAAe,WAAW;;AAE5B,SAAO,MAAM,qBAAqB;GAChC;GACA;GACA,aAAa,KAAK;GAClB;GACA;;UAEKC,KAAU;EACjB,MAAM,eACJC,2CAA4B,KAAK,YAAY,KAAK,WAAW;AAC/D,SAAO,MAAM,mBAAmB;GAC9B,SAAS;GACT,OAAO;GACP;GACA;GACA;GACA;;;;AAKN,SAAS,gCACP,SACA,SAC0B;CAC1B,MAAM,WAAW,QAAQ,QAAQ;AACjC,QAAO;EACL,OAAO,SAAS,gBAAgB,SAAS,SAAS;EAClD,eAAe,SAAS,gBAAgB,iBAAiB,SAAS;;;AAItE,eAAe,2BAA2B,MAID;CACvC,MAAM,EAAE,iBAAiB,eAAe,gBAAgB;AACxD,KAAI,CAAC,YAAY,QAAS,QAAO;AACjC,KAAI;EACF,MAAMC,iBACJ,MAAM,gBAAgB,4BAA4B;AACpD,SAAO;GAAE,GAAG;GAAa;;SACnB;AACN,SAAO;;;AAIX,eAAe,qBAAqB,MAMK;CACvC,MAAM,EAAE,iBAAiB,eAAe,aAAa,SAAS,cAAc;CAC5E,MAAM,cAAc,MAAM,2BAA2B;EACnD;EACA;EACA;;AAEF,WAAU;EACR,MAAM;EACN,OAAOL,iCAAW;EAClB,QAAQC,kCAAY;EACpB,SAAS;EACT;EACA,qBAAqB,YAAY,uBAAuB;;AAE1D,OAAM,YAAY,MAAM;AACxB,QAAO;;AAGT,eAAe,mBAAmB,MASO;CACvC,MAAM,EACJ,SACA,OACA,sBACA,SACA,SACA,WACA,cAAc,MACd,gBAAgB,SACd;AAEJ,KAAI;AAAE,QAAM;SAAgC;AAE5C,KAAI,YACF,WAAU;AAGZ,WAAU;EACR,MAAM;EACN,OAAOD,iCAAW;EAClB,QAAQC,kCAAY;EACpB;EACA,OAAO;;AAGT,KAAI,cACF,OAAM,YAAY;AAEpB,QAAO;EAAE,SAAS;EAAO,OAAO;;;AAGlC,eAAe,4BAA4B,MAQF;CACvC,MAAM,EACJ,SACA,eACA,uBACA,UACA,OACA,eACA,0BACE;AACJ,KAAI,CAAC,yBAAyB,CAAC,SAAU,QAAO;CAEhD,MAAM,EAAE,oBAAoB;AAE5B,KAAI;EACF,MAAM,OAAO,gBAAgB;AAC7B,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM;EAE3B,MAAM,WAAW,MAAM,gBAAgB;EACvC,MAAM,eAAe,0BAClB,UAAU,kBAAkB,gBACzB,SAAS,gBACR,MAAMK,+BAAiB,SAAS,qBAAqB,iBAClD,gBAAgB;AAE1B,MAAI,iBAAiB,MAAM;AACzB,WAAQ,KAAK;AACb,UAAO;;EAGT,MAAM,uBAAuB,MAAMA,+BAAiB,WAAW,wBAC7D,eACA;EAEF,MAAM,eAAe,sBAAsB,gBAAgB;EAC3D,MAAM,iBAAiB,sBAAsB,cAAc,KAAK,MAAM,EAAE,OAAO;EAC/E,MAAM,2BAA2BC,6DAAwC;AAEzE,MAAI,CAAC,cAAc;AACjB,WAAQ,KAAK;AACb,UAAO;;AAGT,MAAI,CAAC,4BAA4B,yBAAyB,SAAS,GAAG;AACpE,WAAQ,KAAK;AACb,UAAO;;EAGT,MAAM,SAAS,MAAMC,2DAA4B;GAC/C;GACA;GACA;GACA,GAAI,0BAA0B,SAAS,EAAE,gBAAgB,6BAA6B;GACtF;GACA;;EAGF,MAAM,WAAWC,4EAAwC;GACvD;GACA;GACA,YAAY;GACZ;GACA,GAAI,0BAA0B,SAAS,EAAE,gBAAgB,6BAA6B;;AAGxF,SAAO;GACL,aAAa;GACb,YAAY;GACZ;GACA;GACA;GACA;;UAEKC,GAAQ;AACf,UAAQ,KACN,wFACA,GAAG,WAAW;AAEhB,SAAO;;;AAIX,eAAe,+BAA+B,MAM5B;CAChB,MAAM,EAAE,SAAS,eAAe,MAAM,cAAc,eAAe;CACnE,MAAM,EAAE,oBAAoB;CAE5B,IAAIC,2BAA0C;AAC9C,KAAI;EACF,MAAM,mBAAmB,MAAML,+BAAiB,WAAW,oBACzD,eACA,KAAK;EAEP,MAAM,cAAc,OAAO,kBAAkB,eAAe,IAAI;AAChE,MAAI,aAAa;GACf,MAAM,UACJ,MAAM,gBAAgB,yDAAyD;IAC7E;IACA;IACA;;AAEJ,OAAI,QAAQ,WAAW,QAAQ,yBAC7B,4BAA2B,QAAQ;;UAGhCI,GAAQ;AACf,UAAQ,KACN,iFACA,GAAG,WAAW;;AAIlB,KAAI,CAAC,0BAA0B;AAC7B,UAAQ,KACN;AAEF;;CAGF,MAAM,SAAS,MAAME,oEAAgC;EACnD,YAAY,KAAK;EACjB,aAAa,KAAK;EAClB,cAAc,KAAK;EACnB;EACA,eAAe,KAAK,OAAO;EAC3B;EACA,wBAAwB;;AAG1B,KAAI,OAAO,MAAM,OAAO,KAAK;AAC3B,2EAAqC,KAAK,UAAU;GAClD,aAAa,KAAK;GAClB,QAAQ,KAAK,OAAO;GACpB,YAAY,KAAK,OAAO;GACxB,uBAAuB,KAAK,OAAO;GACnC,KAAK,OAAO;GACZ,GAAI,OAAO,cAAc,EAAE,aAAa,OAAO,gBAAgB;;AAEjE;;AAGF,KAAI,CAAC,OAAO,GACV,SAAQ,KACN,0CACA,OAAO,QAAQ,OAAO,WAAW;;AAKvC,eAAe,sBAAsB,MAeI;CACvC,MAAM,EACJ,SACA,eACA,iBACA,sBACA,uBACA,SACA,UACA,aACA,eACA,YACA,sBACA,SACA,SACA,cACE;CAEJ,MAAM,EAAE,oBAAoB;AAE5B,KAAI;EACF,MAAM,OAAO,gBAAgB;AAC7B,MAAI,CAAC,KACH,OAAM,IAAI,MAAM;EAGlB,MAAM,YAAY,MAAM,QAAQ,WAAW,UAAU,EAAE,UAAU;EACjE,MAAM,cAAc,WAAW,QAAQ;EACvC,MAAM,gBAAgB,OAAO,UAAU,QAAQ,UAAU;EACzD,MAAM,eAAe,MAAMC,8CAAyB;GAAE;GAAe;;EACrE,MAAM,eAAe,MAAM,gBAAgB,yBAAyB;GAClE,QAAQ;GACR;GACA,WAAW;GACX,aAAa;GACb;GACA,GAAI,gBAAgB,EAAE,uBAAuB,cAAc,OAAO,0BAA0B;;EAG9F,MAAM,iBAAiB,MAAM,gBAAgB,wBAAwB;EACrE,MAAM,0BAA0B,uCAC9B,gBACA;EAEF,MAAM,aAAa,MAAM,gBAAgB,uCAAuC;GAC9E;GACA,WAAW;GACX,kBAAkBC,uDAAiC;;EAGrD,MAAM,uBAAuB,MAAM,oCAAoC;GACrE;GACA;GACA;GACA;GACA;GACiB;;AAGnB,QAAM,uBAAuB;GAC3B;GACA;GACA;GACA;GACA,OAAO,WAAW;GAClB,eAAe,WAAW;;EAG5B,IAAIC;AACJ,MAAI,SAAS;GACX,MAAM,IAAI,MAAMC,8CACd,UACA,aACA,QAAQ,MACR,cACA;AAEF,OAAI,CAAC,EAAE,WAAW,CAAC,EAAE,UAAU;IAC7B,MAAM,SAAS,EAAE,SAAS;AAC1B,WAAO,MAAM,mBAAmB;KAC9B,SAAS;KACT;KACA;KACA;KACA,aAAa;;;AAGjB,sBAAmB,EAAE;;AAGvB,MAAI,cACF,OAAM,+BAA+B;GACnC;GACA;GACA,MAAM;GACN;GACA;;EAIJ,MAAMC,cAA2B,mBAC7B;GAAE,GAAG;GAAsB,KAAK;MAChC;AAEJ,SAAO,MAAM,qBAAqB;GAChC;GACA;GACA;GACA;GACA;;UAEKP,GAAQ;AACf,UAAQ,MAAM,oCAAoC;EAClD,MAAM,SACJN,2CAA4B,GAAG,YAC/B,GAAG,WACH;AACF,SAAO,MAAM,mBAAmB;GAC9B,SAAS;GACT,OAAO;GACP;GACA;GACA;GACA;;;;;;;;;;;AAYN,eAAe,oCAAoC,MAO1B;CACvB,MAAM,EACJ,iBACA,eACA,gBACA,YACA,sBACA,oBACE;CAIJ,IAAI,uBAAuB;AAE3B,KAAI,eAAe,UAAU,EAC3B,QAAO;CAGT,MAAM,QAAQ,WAAW;CACzB,MAAM,UAAU,eAAe,MAAM,MAAM,EAAE,iBAAiB;CAC9D,MAAM,uBAAuB,SAAS,gBAAgB;AAEtD,KAAI,yBAAyB,KAC3B,QAAO;CAGT,MAAM,gBAAgB,MAAME,+BAAiB,SAC1C,gBAAgB,eAAe,sBAC/B,YAAY;AAEf,KAAI,eAAe,oBACjB,wBAAuB;EACrB,GAAG;EACH,qBAAqB,cAAc;;CAIvC,MAAM,kBAAkB,CAAC,wBAAwB,qBAAqB,UAAU;AAChF,KAAI,iBAAiB;EACnB,MAAM,SAAS,eAAe;AAC9B,MAAI,CAAC,QAAQ,qBAAqB,CAAC,QAAQ,cAAc,CAAC,QAAQ,YAChE,OAAM,IAAI,MACR,iDAAiD,cAAc,UAAU,qBAAqB;EAKlG,MAAM,SAAS,MAAM,gBAAgB,6BAA6B;GAChE;GACA,YAAY,OAAO;GACnB,mBAAmB,OAAO;GAC1B,aAAa,OAAO;;AAEtB,MAAI,CAAC,OAAO,QACV,OAAM,IAAI,MACR,OAAO,SACP;;AAON,OAAM,gBAAgB,YAAY,eAAe;AAEjD,OAAM,gBAAgB,gBAAgB,eAAe,YAAY;AAEjE,QAAO;;;;;;;;;;AAWT,eAAe,uBAAuB,MAOpB;CAChB,MAAM,EAAE,SAAS,eAAe,SAAS,YAAY,OAAO,kBAAkB;CAC9E,MAAM,EAAE,oBAAoB;AAE5B,WAAU;EACR,MAAM;EACN,OAAON,iCAAW;EAClB,QAAQC,kCAAY;EACpB,SAAS;;CAKX,IAAI,sBAAsB;AAC1B,KAAI,CAAC,qBAAqB;EACxB,MAAM,YAAYiB;EAClB,MAAM,iBAAiB,MAAM,gBAAgB,wBAAwB;EACrE,MAAM,EAAE,4BAA4B,MAAMZ,+BAAiB,SAAS,qBAClE,eACA;AAEF,wBAAsB,MAAM,gBAAgB,uCAAuC;GACjF;GACW;GACX,kBAAkBQ,uDAAiC;;;AAIvD,OAAM,gBAAgB,iCAAiC;EACrD;EACA,YAAY;EACZ;EACA;;AAGF,WAAU;EACR,MAAM;EACN,OAAOd,iCAAW;EAClB,QAAQC,kCAAY;EACpB,SAAS;;;;;;;;;;;;;;;;AAiBb,eAAe,qBACb,SACA,eACA,SACA,SACA,WACA,sBACA,mBAAkC,MAMjC;CACD,MAAM,EAAE,oBAAoB;AAE5B,KAAI;EAEF,MAAMkB,kBACJ,qBAAqB,OACjB,gBAAgB,gBAAgB,eAAe,kBAAkB,YAAY,QAC7E,QAAQ,QAAQ;EAEtB,MAAM,CAAC,UAAU,UAAU,iBAAiB,kBAAkB,MAAM,QAAQ,IAAI;GAC9E;GACA,gBAAgB;GAChBb,+BAAiB,SAAS,qBAAqB;GAC/C,gBAAgB,wBAAwB;;EAI1C,IAAIc,WAAkC;AACtC,MAAI,YAAY,SAAS,kBAAkB,cACzC,YAAW;WACF,mBAAmB,gBAAgB,kBAAkB,cAC9D,YAAW;WACF,YAAY,SAAS,kBAAkB,cAChD,YAAW;MAEX,YAAW,MAAM,gBAAgB,gBAAgB,eAAe;AAIlE,MAAI,CAAC,SACH,OAAM,IAAI,MAAM,2BAA2B,cAAc;AAE3D,MAAI,CAAC,SAAS,oBACZ,OAAM,IAAI,MAAM,gCAAgC,cAAc;AAEhE,MACE,CAAC,SAAS,qBAAqB,wBAC/B,CAAC,SAAS,qBAAqB,kBAE/B,OAAM,IAAI,MAAM;AAElB,MAAI,eAAe,WAAW,EAC5B,OAAM,IAAI,MAAM,uCAAuC,cAAc;AAIvE,YAAU;GACR,MAAM;GACN,OAAOpB,iCAAW;GAClB,QAAQC,kCAAY;GACpB,SAAS;;EAGX,IAAIoB,eAAqD,EAAE,SAAS;EACpE,IAAI,sBAAsB;EAC1B,IAAIC;EACJ,IAAI,qBAAqB,SAAS;EAGlC,IAAI,oBAAoB;EAExB,MAAM,SAAS,SAAS;EACxB,MAAM,aAAa,QAAQ,QAAQ,SAAS;EAC5C,MAAM,6BAA6B,CAAC,CAAC,cAAc,CAAC,CAAC,QAAQ;AAE7D,MAAI,8BAA8B,OAChC,KAAI;AACF,OAAI,CAAC,OAAO,qBAAqB,CAAC,OAAO,WACvC,OAAM,IAAI,MAAM;AAGlB,kBAAe,MAAM,gBAAgB,6BAA6B;IAChE;IACA,YAAY,OAAO;IACnB,mBAAmB,OAAO;IAC1B,aAAa,OAAO;;AAGtB,OAAI,aAAa,SAAS;IACxB,MAAM,YAAY,MAAM,gBAAgB;IACxC,MAAM,SAAS,UAAU,UAAU,UAAU,kBAAkB;AAC/D,QAAI,CAAC,OACH,gBAAe;KAAE,SAAS;KAAO,OAAO;;AAE1C,QAAI,OAEF,OAAM,gBAAgB,4BAA4B;SAGpD,OAAM,IAAI,MAAM,mCAAmC,aAAa;WAE3DC,OAAY;AACnB,kBAAe;IAAE,SAAS;IAAO,OAAO,MAAM;;;AAKlD,MAAI,CAAC,aAAa,SAAS;GACzB,MAAM,0BAA0B,uCAAuC,gBAAgB;GACvF,MAAM,WAAW,MAAM,oCAAoC;IACzD;IACA;IACA,gBAAgB;IAChB;IACA;;AAEF,kBAAe,SAAS;AACxB,yBAAsB,SAAS;AAC/B,sBAAmB,SAAS;AAC5B,uBAAoB,SAAS;AAC7B,wBAAqB,SAAS;;AAGhC,MAAI,CAAC,aAAa,QAChB,OAAM,IAAI,MAAM,iCAAiC,aAAa;AAGhE,YAAU;GACR,MAAM;GACN,OAAOvB,iCAAW;GAClB,QAAQC,kCAAY;GACpB,SAAS;;AAIX,MAAI;GACF,MAAMuB,eAAa,QAAQ,QAAQ,SAAS;AAC5C,OAAI,uBAAuBA,cAAY;IACrC,MAAM,YAAY,MAAM,gBAAgB;AACxC,UAAM,gBAAgB,gCAAgC,eAAe;;WAEhEC,YAAiB;AACxB,WAAQ,KAAK,2DAA2D,YAAY,WAAW;;AAKjG,MAAI;AACF,SAAM,gBAAgB,YAAY,eAAe;UAC3C;AAGR,QAAM,gBAAgB,gBAAgB;EAEtC,MAAMC,SAAsB;GAC1B,SAAS;GACT,uBAAuB;GAGvB,qBAAqB,kBAAkB;GACxB;;AAGjB,MAAI,CAAC,sBAAsB;AACzB,aAAU;IACR,MAAM;IACN,OAAO1B,iCAAW;IAClB,QAAQC,kCAAY;IACpB,SAAS;IACM;IACf,qBAAqB,kBAAkB;;AAEzC,eAAY,MAAM;;AAEpB,SAAO;GAAE;GAAQ;GAAqB;GAAkB;;UAEjDsB,OAAY;EAEnB,MAAM,eAAenB,2CAA4B,OAAO;AAExD,YAAU;AACV,YAAU;GACR,MAAM;GACN,OAAOJ,iCAAW;GAClB,QAAQC,kCAAY;GACpB,SAAS;GACT,OAAO;;EAGT,MAAMyB,SAAsB;GAAE,SAAS;GAAO,OAAO;;AACrD,cAAY;AACZ,SAAO;GAAE;GAAQ,qBAAqB;GAAO,oBAAoB;;;;;;;;;;;;;AAarE,eAAe,oCAAoC,MAYhD;CACD,MAAM,EAAE,iBAAiB,eAAe,gBAAgB,UAAU,YAAY;AAE9E,WAAU;EACR,MAAM;EACN,OAAO1B,iCAAW;EAClB,QAAQC,kCAAY;EACpB,SAAS;;CAGX,MAAM,YAAYiB;CAClB,MAAM,aAAa,MAAM,gBAAgB,8CAA8C;EACrF;EACW;EACX,eAAe,eAAe,KAAK,MAAM,EAAE;;CAG7C,IAAI,oBAAoB;CACxB,IAAI,qBAAqB,SAAS;AAIlC,KAAI,eAAe,SAAS,GAAG;EAC7B,MAAM,QAAQ,WAAW;EACzB,MAAM,UAAU,eAAe,MAAK,MAAK,EAAE,iBAAiB;AAC5D,MAAI,QACF,KAAI;GACF,MAAM,WAAW,MAAMZ,+BAAiB,SAAS,gBAAgB,eAAe,QAAQ;AACxF,OAAI,UAAU;AACZ,wBAAoB;AACpB,yBAAqB,QAAQ;;UAEzB;;CAMZ,MAAM,eAAe,MAAM,gBAAgB,iBAAiB;EAC3C;EACf,qBAAqB;GACnB,sBAAsB,kBAAkB,oBAAoB;GAC5D,mBAAmB,kBAAkB,oBAAoB;;EAE/C;;AAGd,QAAO;EACL;EACA,qBAAqB,aAAa;EAClC,kBAAkB;EAClB;EACA;;;;;;;;;;;;;AAcJ,eAAsB,gBACpB,SACA,eACuB;CACvB,MAAM,QAAQ,MAAM,sBAAsB,SAAS;AACnD,KAAI,CAAC,OAAO,cAAc,CAAC,MAAM,cAAe,QAAO;EAAE;EAAO,gBAAgB;;AAChF,KAAI;EACF,MAAM,iBAAiB,MAAM,QAAQ,gBAAgB,4BAA4B,MAAM;AACvF,SAAO;GAAE;GAAO;;SACV;AACN,SAAO;GAAE;GAAO,gBAAgB;;;;;;;;;;;AAWpC,eAAe,sBACb,SACA,eACqB;CACrB,MAAM,EAAE,oBAAoB;AAC5B,KAAI;EAEF,MAAM,WAAW,MAAM,gBAAgB;EACvC,MAAM,kBAAkB,iBAAiB,UAAU,iBAAiB;AAGpE,MAAI,CAAC,YAAa,mBAAmB,SAAS,kBAAkB,gBAC9D,QAAO;GACL,YAAY;GACZ,eAAe,mBAAmB;GAClC,WAAW;GACX,WAAW;GACX,UAAU;;EAId,MAAM,WAAW;EACjB,MAAM,YAAY,UAAU,uBAAuB;EAGnD,MAAM,YAAY,MAAM,gBAAgB;EACxC,MAAM,YAAY,UAAU,UAAU,UAAU,kBAAkB;EAIlE,MAAM,aAAa,CAAC,EAAE,YAAY,SAAS,uBAAuB;AAElE,SAAO;GACL;GACA,eAAe;GACf;GACA;GACA;GACA,oBAAoB,UAAU,mBAAmB;;UAG5CiB,OAAY;AACnB,UAAQ,KAAK,8BAA8B;AAC3C,SAAO;GACL,YAAY;GACZ,eAAe,iBAAiB;GAChC,WAAW;GACX,WAAW;GACX,UAAU;;;;;;;;;AAUhB,eAAsB,gBACpB,SACgC;CAChC,MAAM,EAAE,oBAAoB;CAE5B,MAAM,eAAe,MAAM,gBAAgB;CAC3C,MAAM,aAAa,aAAa,KAAI,SAAQ,KAAK;CAEjD,MAAM,kBAAkB,MAAM,gBAAgB;AAC9C,QAAO;EACL;EACA;;;;;;;;AASJ,eAAsB,sBAAsB,SAA+C;CACzF,MAAM,EAAE,oBAAoB;AAC5B,OAAM,gBAAgB;AACtB,KAAI;AAAE,kBAAgB,kBAAkB;SAAiB;AACzD,KAAI;AAAE;SAAsD;;AAG9D,SAAS,uCACP,gBACA,cAC2B;AAC3B,KAAI,eAAe,UAAU,EAAG,QAAO;AACvC,KAAI,iBAAiB,KAAM,QAAO;CAClC,MAAM,YAAY,eAAe,QAAQ,MAAM,EAAE,iBAAiB;AAClE,KAAI,UAAU,WAAW,EAAG,QAAO;CACnC,MAAM,OAAO,eAAe,QAAQ,MAAM,EAAE,iBAAiB;AAC7D,QAAO,CAAC,GAAG,WAAW,GAAG"}
|
|
1
|
+
{"version":3,"file":"login.js","names":["LoginPhase","LoginStatus","parseDeviceNumber","err: any","getUserFriendlyErrorMessage","signingSession: SigningSessionStatus","IndexedDBManager","normalizeThresholdEd25519ParticipantIds","buildThresholdSessionPolicy","makeThresholdEd25519AuthSessionCacheKey","e: any","clientVerifyingShareB64u: string | null","mintThresholdEd25519AuthSession","computeLoginIntentDigest","authenticatorsToAllowCredentials","serverSessionJwt: string | undefined","verifyAuthenticationResponse","loginResult: LoginResult","createRandomVRFChallenge","hintUserPromise: Promise<ClientUserData | null>","userData: ClientUserData | null","unlockResult: { success: boolean; error?: string }","unlockCredential: WebAuthnAuthenticationCredential | undefined","error: any","relayerUrl","refreshErr: any","result: LoginResult"],"sources":["../../../../src/core/TatchiPasskey/login.ts"],"sourcesContent":["import type {\n AfterCall,\n LoginHooksOptions,\n LoginSSEvent,\n} from '../types/sdkSentEvents';\nimport { LoginPhase, LoginStatus } from '../types/sdkSentEvents';\nimport type {\n GetRecentLoginsResult,\n LoginAndCreateSessionResult,\n LoginResult,\n LoginSession,\n LoginState,\n SigningSessionStatus,\n} from '../types/tatchi';\nimport type { PasskeyManagerContext } from './index';\nimport type { AccountId } from '../types/accountIds';\nimport type { WebAuthnAuthenticationCredential } from '../types/webauthn';\nimport { getUserFriendlyErrorMessage } from '../../utils/errors';\nimport { createRandomVRFChallenge, VRFChallenge } from '../types/vrf-worker';\nimport { authenticatorsToAllowCredentials } from '../WebAuthnManager/touchIdPrompt';\nimport { IndexedDBManager } from '../IndexedDBManager';\nimport type { ClientAuthenticatorData, ClientUserData } from '../IndexedDBManager';\nimport { verifyAuthenticationResponse } from '../rpcCalls';\nimport { computeLoginIntentDigest } from '../digests/intentDigest';\nimport { buildThresholdSessionPolicy } from '../threshold/thresholdSessionPolicy';\nimport { parseDeviceNumber } from '../WebAuthnManager/SignerWorkerManager/getDeviceNumber';\nimport {\n clearAllCachedThresholdEd25519AuthSessions,\n makeThresholdEd25519AuthSessionCacheKey,\n mintThresholdEd25519AuthSession,\n putCachedThresholdEd25519AuthSession,\n} from '../threshold/thresholdEd25519AuthSession';\nimport { normalizeThresholdEd25519ParticipantIds } from '../../threshold/participants';\n\ntype WarmSigningSessionPolicy = { ttlMs: number; remainingUses: number };\n\ntype ThresholdSessionPlan = {\n sessionKind: 'jwt';\n relayerUrl: string;\n relayerKeyId: string;\n cacheKey: string;\n policy: Awaited<ReturnType<typeof buildThresholdSessionPolicy>>;\n deviceNumber: number;\n};\n\n/**\n * Core login function that handles passkey authentication without React dependencies.\n *\n * - Unlocks the VRF keypair (Shamir 3‑pass auto‑unlock when possible; falls back to TouchID).\n * - Updates local login state and returns success with account/public key info.\n * - Optional: mints a server session when `options.session` is provided.\n * - Generates a fresh, chain‑anchored VRF challenge (using latest block).\n * - Collects a WebAuthn assertion over the VRF output and posts to the relay route\n * (defaults to `/verify-authentication-response`).\n * - When `kind: 'jwt'`, returns the token in `result.jwt`.\n * - When `kind: 'cookie'`, the server sets an HttpOnly cookie and no JWT is returned.\n */\nexport async function loginAndCreateSession(\n context: PasskeyManagerContext,\n nearAccountId: AccountId,\n options?: LoginHooksOptions\n): Promise<LoginAndCreateSessionResult> {\n const { onEvent, onError, afterCall } = options || {};\n const { webAuthnManager } = context;\n\n onEvent?.({\n step: 1,\n phase: LoginPhase.STEP_1_PREPARATION,\n status: LoginStatus.PROGRESS,\n message: `Starting login for ${nearAccountId}`\n });\n\n const prevStatus = await webAuthnManager.checkVrfStatus();\n const prevVrfAccountId = prevStatus?.active ? prevStatus.nearAccountId : null;\n\n // If this call activates VRF then fails, clear the partial session.\n const rollbackVrfOnFailure = async () => {\n const status = await webAuthnManager.checkVrfStatus();\n const current = status?.active ? status.nearAccountId : null;\n if (current && current !== prevVrfAccountId) {\n await logoutAndClearSession(context);\n }\n };\n\n try {\n // Validation\n if (!window.isSecureContext) {\n const errorMessage = 'Passkey operations require a secure context (HTTPS or localhost).';\n return await finalizeLoginError({\n message: errorMessage,\n error: new Error(errorMessage),\n rollbackVrfOnFailure,\n onEvent,\n onError,\n afterCall,\n callAfterCall: false,\n });\n }\n\n const session = options?.session;\n const wantsServerSession = session !== undefined;\n const deviceNumberHint = parseDeviceNumber(options?.deviceNumber, { min: 1 });\n const base = await handleLoginUnlockVRF(\n context,\n nearAccountId,\n onEvent,\n onError,\n afterCall,\n // Defer final 'login-complete' event & afterCall until warm signing session is minted\n true,\n deviceNumberHint\n );\n // If base login failed, just return\n if (!base.result.success) {\n return base.result;\n }\n\n const preferredDeviceNumber = deviceNumberHint ?? base.activeDeviceNumber;\n const warmPolicy = resolveWarmSigningSessionPolicy(context, options);\n\n const wantsThresholdSession =\n webAuthnManager.getUserPreferences().getSignerMode().mode === 'threshold-signer';\n const relayUrl = (session?.relayUrl || context.configs.relayer.url).trim();\n const verifyRoute = (session?.route || '/verify-authentication-response').trim();\n\n const thresholdPlan = await prepareThresholdSessionPlan({\n context,\n nearAccountId,\n preferredDeviceNumber,\n relayUrl,\n ttlMs: warmPolicy.ttlMs,\n remainingUses: warmPolicy.remainingUses,\n wantsThresholdSession,\n });\n\n const wantsRelayerSession = wantsServerSession || thresholdPlan !== null;\n if (wantsRelayerSession && !relayUrl) {\n console.warn('[login] No relayUrl provided for session-style signing');\n }\n\n if (wantsRelayerSession && relayUrl) {\n return await runRelayerSessionFlow({\n context,\n nearAccountId,\n baseLoginResult: base.result,\n baseUnlockCredential: base.unlockCredential,\n preferredDeviceNumber,\n session,\n relayUrl,\n verifyRoute,\n thresholdPlan,\n warmPolicy,\n rollbackVrfOnFailure,\n onEvent,\n onError,\n afterCall,\n });\n }\n\n // No relayer session requested (or relayUrl missing): mint/refresh warm signing session only.\n await mintWarmSigningSession({\n context,\n nearAccountId,\n onEvent,\n credential: base.unlockCredential,\n ttlMs: warmPolicy.ttlMs,\n remainingUses: warmPolicy.remainingUses,\n });\n return await finalizeLoginSuccess({\n webAuthnManager,\n nearAccountId,\n loginResult: base.result,\n onEvent,\n afterCall,\n });\n } catch (err: any) {\n const errorMessage =\n getUserFriendlyErrorMessage(err, 'login') || err?.message || 'Login failed';\n return await finalizeLoginError({\n message: errorMessage,\n error: err,\n rollbackVrfOnFailure,\n onEvent,\n onError,\n afterCall,\n });\n }\n}\n\nfunction resolveWarmSigningSessionPolicy(\n context: PasskeyManagerContext,\n options?: LoginHooksOptions\n): WarmSigningSessionPolicy {\n const defaults = context.configs.signingSessionDefaults;\n return {\n ttlMs: options?.signingSession?.ttlMs ?? defaults.ttlMs,\n remainingUses: options?.signingSession?.remainingUses ?? defaults.remainingUses,\n };\n}\n\nasync function attachSigningSessionStatus(args: {\n webAuthnManager: PasskeyManagerContext['webAuthnManager'];\n nearAccountId: AccountId;\n loginResult: LoginResult;\n}): Promise<LoginAndCreateSessionResult> {\n const { webAuthnManager, nearAccountId, loginResult } = args;\n if (!loginResult.success) return loginResult;\n try {\n const signingSession: SigningSessionStatus =\n await webAuthnManager.getWarmSigningSessionStatus(nearAccountId);\n return { ...loginResult, signingSession };\n } catch {\n return loginResult;\n }\n}\n\nasync function finalizeLoginSuccess(args: {\n webAuthnManager: PasskeyManagerContext['webAuthnManager'];\n nearAccountId: AccountId;\n loginResult: LoginResult;\n onEvent?: (event: LoginSSEvent) => void;\n afterCall?: AfterCall<LoginAndCreateSessionResult>;\n}): Promise<LoginAndCreateSessionResult> {\n const { webAuthnManager, nearAccountId, loginResult, onEvent, afterCall } = args;\n const finalResult = await attachSigningSessionStatus({\n webAuthnManager,\n nearAccountId,\n loginResult,\n });\n onEvent?.({\n step: 4,\n phase: LoginPhase.STEP_4_LOGIN_COMPLETE,\n status: LoginStatus.SUCCESS,\n message: 'Login completed successfully',\n nearAccountId,\n clientNearPublicKey: loginResult.clientNearPublicKey ?? '',\n });\n await afterCall?.(true, finalResult);\n return finalResult;\n}\n\nasync function finalizeLoginError(args: {\n message: string;\n error?: unknown;\n rollbackVrfOnFailure: () => Promise<void>;\n onEvent?: (event: LoginSSEvent) => void;\n onError?: (error: Error) => void;\n afterCall?: AfterCall<LoginAndCreateSessionResult>;\n callOnError?: boolean;\n callAfterCall?: boolean;\n}): Promise<LoginAndCreateSessionResult> {\n const {\n message,\n error,\n rollbackVrfOnFailure,\n onEvent,\n onError,\n afterCall,\n callOnError = true,\n callAfterCall = true,\n } = args;\n\n try { await rollbackVrfOnFailure(); } catch {}\n\n if (callOnError) {\n onError?.(error as any);\n }\n\n onEvent?.({\n step: 0,\n phase: LoginPhase.LOGIN_ERROR,\n status: LoginStatus.ERROR,\n message,\n error: message,\n });\n\n if (callAfterCall) {\n await afterCall?.(false);\n }\n return { success: false, error: message };\n}\n\nasync function prepareThresholdSessionPlan(args: {\n context: PasskeyManagerContext;\n nearAccountId: AccountId;\n preferredDeviceNumber: number | null;\n relayUrl: string;\n ttlMs: number;\n remainingUses: number;\n wantsThresholdSession: boolean;\n}): Promise<ThresholdSessionPlan | null> {\n const {\n context,\n nearAccountId,\n preferredDeviceNumber,\n relayUrl,\n ttlMs,\n remainingUses,\n wantsThresholdSession,\n } = args;\n if (!wantsThresholdSession || !relayUrl) return null;\n\n const { webAuthnManager } = context;\n\n try {\n const rpId = webAuthnManager.getRpId();\n if (!rpId) throw new Error('Missing rpId for threshold session');\n\n const lastUser = await webAuthnManager.getLastUser();\n const deviceNumber = preferredDeviceNumber ??\n (lastUser?.nearAccountId === nearAccountId\n ? lastUser.deviceNumber\n : (await IndexedDBManager.clientDB.getLastDBUpdatedUser(nearAccountId))\n ?.deviceNumber ?? null);\n\n if (deviceNumber === null) {\n console.warn('[login] threshold-signer configured but no threshold key material found; skipping threshold session');\n return null;\n }\n\n const thresholdKeyMaterial = await IndexedDBManager.nearKeysDB.getThresholdKeyMaterial(\n nearAccountId,\n deviceNumber\n );\n const relayerKeyId = thresholdKeyMaterial?.relayerKeyId || null;\n const participantIds = thresholdKeyMaterial?.participants?.map((p) => p.id) || null;\n const normalizedParticipantIds = normalizeThresholdEd25519ParticipantIds(participantIds);\n\n if (!relayerKeyId) {\n console.warn('[login] threshold-signer configured but no threshold key material found; skipping threshold session');\n return null;\n }\n\n if (!normalizedParticipantIds || normalizedParticipantIds.length < 2) {\n console.warn('[login] threshold key material missing/invalid participantIds; skipping threshold session mint');\n return null;\n }\n\n const policy = await buildThresholdSessionPolicy({\n nearAccountId,\n rpId,\n relayerKeyId,\n ...(normalizedParticipantIds?.length ? { participantIds: normalizedParticipantIds } : {}),\n ttlMs,\n remainingUses,\n });\n\n const cacheKey = makeThresholdEd25519AuthSessionCacheKey({\n nearAccountId,\n rpId,\n relayerUrl: relayUrl,\n relayerKeyId,\n ...(normalizedParticipantIds?.length ? { participantIds: normalizedParticipantIds } : {}),\n });\n\n return {\n sessionKind: 'jwt',\n relayerUrl: relayUrl,\n relayerKeyId,\n cacheKey,\n policy,\n deviceNumber,\n };\n } catch (e: any) {\n console.warn(\n '[login] failed to prepare threshold session policy; skipping threshold session mint:',\n e?.message || e\n );\n return null;\n }\n}\n\nasync function mintThresholdSessionBestEffort(args: {\n context: PasskeyManagerContext;\n nearAccountId: AccountId;\n plan: ThresholdSessionPlan;\n vrfChallenge: VRFChallenge;\n credential: WebAuthnAuthenticationCredential;\n}): Promise<void> {\n const { context, nearAccountId, plan, vrfChallenge, credential } = args;\n const { webAuthnManager } = context;\n\n let clientVerifyingShareB64u: string | null = null;\n try {\n const localKeyMaterial = await IndexedDBManager.nearKeysDB.getLocalKeyMaterial(\n nearAccountId,\n plan.deviceNumber\n );\n const wrapKeySalt = String(localKeyMaterial?.wrapKeySalt || '').trim();\n if (wrapKeySalt) {\n const derived =\n await webAuthnManager.deriveThresholdEd25519ClientVerifyingShareFromCredential({\n credential,\n nearAccountId,\n wrapKeySalt,\n });\n if (derived.success && derived.clientVerifyingShareB64u) {\n clientVerifyingShareB64u = derived.clientVerifyingShareB64u;\n }\n }\n } catch (e: any) {\n console.warn(\n '[login] failed to derive clientVerifyingShareB64u for threshold session mint:',\n e?.message || e\n );\n }\n\n if (!clientVerifyingShareB64u) {\n console.warn(\n '[login] threshold session mint skipped: missing clientVerifyingShareB64u'\n );\n return;\n }\n\n const minted = await mintThresholdEd25519AuthSession({\n relayerUrl: plan.relayerUrl,\n sessionKind: plan.sessionKind,\n relayerKeyId: plan.relayerKeyId,\n clientVerifyingShareB64u,\n sessionPolicy: plan.policy.policy,\n vrfChallenge,\n webauthnAuthentication: credential,\n });\n\n if (minted.ok && minted.jwt) {\n putCachedThresholdEd25519AuthSession(plan.cacheKey, {\n sessionKind: plan.sessionKind,\n policy: plan.policy.policy,\n policyJson: plan.policy.policyJson,\n sessionPolicyDigest32: plan.policy.sessionPolicyDigest32,\n jwt: minted.jwt,\n ...(minted.expiresAtMs ? { expiresAtMs: minted.expiresAtMs } : {}),\n });\n return;\n }\n\n if (!minted.ok) {\n console.warn(\n '[login] threshold session mint failed:',\n minted.code || minted.message || 'unknown error'\n );\n }\n}\n\nasync function runRelayerSessionFlow(args: {\n context: PasskeyManagerContext;\n nearAccountId: AccountId;\n baseLoginResult: LoginResult;\n baseUnlockCredential?: WebAuthnAuthenticationCredential;\n preferredDeviceNumber: number | null;\n session: LoginHooksOptions['session'] | undefined;\n relayUrl: string;\n verifyRoute: string;\n thresholdPlan: ThresholdSessionPlan | null;\n warmPolicy: WarmSigningSessionPolicy;\n rollbackVrfOnFailure: () => Promise<void>;\n onEvent?: (event: LoginSSEvent) => void;\n onError?: (error: Error) => void;\n afterCall?: AfterCall<LoginAndCreateSessionResult>;\n}): Promise<LoginAndCreateSessionResult> {\n const {\n context,\n nearAccountId,\n baseLoginResult,\n baseUnlockCredential,\n preferredDeviceNumber,\n session,\n relayUrl,\n verifyRoute,\n thresholdPlan,\n warmPolicy,\n rollbackVrfOnFailure,\n onEvent,\n onError,\n afterCall,\n } = args;\n\n const { webAuthnManager } = context;\n\n try {\n const rpId = webAuthnManager.getRpId();\n if (!rpId) {\n throw new Error('Missing rpId for VRF challenge generation during login');\n }\n\n const blockInfo = await context.nearClient.viewBlock({ finality: 'final' });\n const txBlockHash = blockInfo?.header?.hash;\n const txBlockHeight = String(blockInfo.header?.height ?? '');\n const intentDigest = await computeLoginIntentDigest({ nearAccountId, rpId });\n const vrfChallenge = await webAuthnManager.generateVrfChallengeOnce({\n userId: nearAccountId,\n rpId,\n blockHash: txBlockHash,\n blockHeight: txBlockHeight,\n intentDigest,\n ...(thresholdPlan ? { sessionPolicyDigest32: thresholdPlan.policy.sessionPolicyDigest32 } : {}),\n });\n\n const authenticators = await webAuthnManager.getAuthenticatorsByUser(nearAccountId);\n const authenticatorsForPrompt = prioritizeAuthenticatorsByDeviceNumber(\n authenticators,\n preferredDeviceNumber\n );\n const credential = await webAuthnManager.getAuthenticationCredentialsSerialized({\n nearAccountId,\n challenge: vrfChallenge,\n allowCredentials: authenticatorsToAllowCredentials(authenticatorsForPrompt),\n });\n\n const effectiveLoginResult = await bindVrfToSelectedLoginPasskeyDevice({\n webAuthnManager,\n nearAccountId,\n authenticators,\n credential,\n baseUnlockCredential,\n baseLoginResult: baseLoginResult,\n });\n\n await mintWarmSigningSession({\n context,\n nearAccountId,\n onEvent,\n credential,\n ttlMs: warmPolicy.ttlMs,\n remainingUses: warmPolicy.remainingUses,\n });\n\n let serverSessionJwt: string | undefined;\n if (session) {\n const v = await verifyAuthenticationResponse(\n relayUrl,\n verifyRoute,\n session.kind,\n vrfChallenge,\n credential\n );\n if (!v.success || !v.verified) {\n const errMsg = v.error || 'Session verification failed';\n return await finalizeLoginError({\n message: errMsg,\n rollbackVrfOnFailure,\n onEvent,\n afterCall,\n callOnError: false,\n });\n }\n serverSessionJwt = v.jwt;\n }\n\n if (thresholdPlan) {\n await mintThresholdSessionBestEffort({\n context,\n nearAccountId,\n plan: thresholdPlan,\n vrfChallenge,\n credential,\n });\n }\n\n const loginResult: LoginResult = serverSessionJwt\n ? { ...effectiveLoginResult, jwt: serverSessionJwt }\n : effectiveLoginResult;\n\n return await finalizeLoginSuccess({\n webAuthnManager,\n nearAccountId,\n loginResult,\n onEvent,\n afterCall,\n });\n } catch (e: any) {\n console.error('[login] Failed to start session:', e);\n const errMsg =\n getUserFriendlyErrorMessage(e, 'login') ||\n e?.message ||\n 'Session verification failed';\n return await finalizeLoginError({\n message: errMsg,\n error: e,\n rollbackVrfOnFailure,\n onEvent,\n onError,\n afterCall,\n });\n }\n}\n\n/**\n * When multiple passkeys exist for the same account, the user can select any of them in the WebAuthn prompt.\n * If the VRF worker was auto-unlocked using a different device (e.g. Shamir 3-pass based on the \"latest\" row),\n * we must rebind the VRF worker to the same deviceNumber as the selected credential. Otherwise, WrapKeySeed\n * derivation can mix PRF.first(from selected credential) with vrf_sk(from a different device), which later\n * causes vault decryption failures (e.g. `aead::Error` during private key export).\n */\nasync function bindVrfToSelectedLoginPasskeyDevice(args: {\n webAuthnManager: PasskeyManagerContext['webAuthnManager'];\n nearAccountId: AccountId;\n authenticators: ClientAuthenticatorData[];\n credential: WebAuthnAuthenticationCredential;\n baseUnlockCredential?: WebAuthnAuthenticationCredential;\n baseLoginResult: LoginResult;\n}): Promise<LoginResult> {\n const {\n webAuthnManager,\n nearAccountId,\n authenticators,\n credential,\n baseUnlockCredential,\n baseLoginResult,\n } = args;\n\n // Start with the base login result (may reflect whichever VRF keypair was auto-unlocked).\n // If the user selects a different passkey below, update it to match the chosen device.\n let effectiveLoginResult = baseLoginResult;\n\n if (authenticators.length <= 1) {\n return effectiveLoginResult;\n }\n\n const rawId = credential.rawId;\n const matched = authenticators.find((a) => a.credentialId === rawId);\n const selectedDeviceNumber = matched?.deviceNumber ?? null;\n\n if (selectedDeviceNumber === null) {\n return effectiveLoginResult;\n }\n\n const userForDevice = await IndexedDBManager.clientDB\n .getUserByDevice(nearAccountId, selectedDeviceNumber)\n .catch(() => null);\n\n if (userForDevice?.clientNearPublicKey) {\n effectiveLoginResult = {\n ...effectiveLoginResult,\n clientNearPublicKey: userForDevice.clientNearPublicKey,\n };\n }\n\n const shouldRebindVrf = !baseUnlockCredential || baseUnlockCredential.rawId !== rawId;\n if (shouldRebindVrf) {\n const shamir = userForDevice?.serverEncryptedVrfKeypair;\n if (!shamir?.ciphertextVrfB64u || !shamir?.kek_s_b64u || !shamir?.serverKeyId) {\n throw new Error(\n `Missing serverEncryptedVrfKeypair for account ${nearAccountId} device ${selectedDeviceNumber}. ` +\n 'Open the wallet once online to refresh local state, then try again.'\n );\n }\n\n const unlock = await webAuthnManager.shamir3PassDecryptVrfKeypair({\n nearAccountId,\n kek_s_b64u: shamir.kek_s_b64u,\n ciphertextVrfB64u: shamir.ciphertextVrfB64u,\n serverKeyId: shamir.serverKeyId,\n });\n if (!unlock.success) {\n throw new Error(\n unlock.error ||\n 'Failed to bind VRF keypair to the passkey you selected. Please try again.'\n );\n }\n }\n\n // Persist the deviceNumber that the user actually selected so subsequent flows (export/signing)\n // use the correct vault entry.\n await webAuthnManager.setLastUser(nearAccountId, selectedDeviceNumber);\n // Best-effort: stamp the selected device as the one last used for login.\n await webAuthnManager.updateLastLogin(nearAccountId).catch(() => undefined);\n\n return effectiveLoginResult;\n}\n\n/**\n * Mint or refresh a \"warm signing session\" in the VRF worker.\n *\n * Notes:\n * - Reuses a previously collected WebAuthn credential when provided (e.g. TouchID fallback),\n * to avoid prompting the user twice in a single login flow.\n * - Session TTL/remainingUses policy is enforced by the VRF worker.\n */\nasync function mintWarmSigningSession(args: {\n context: PasskeyManagerContext;\n nearAccountId: AccountId;\n onEvent?: (event: LoginSSEvent) => void;\n credential?: WebAuthnAuthenticationCredential;\n ttlMs: number;\n remainingUses: number;\n}): Promise<void> {\n const { context, nearAccountId, onEvent, credential, ttlMs, remainingUses } = args;\n const { webAuthnManager } = context;\n\n onEvent?.({\n step: 3,\n phase: LoginPhase.STEP_3_VRF_UNLOCK,\n status: LoginStatus.PROGRESS,\n message: 'Unlocking warm signing session...'\n });\n\n // If we already performed a TouchID ceremony (e.g., VRF unlock fallback),\n // reuse that credential to avoid a second prompt.\n let effectiveCredential = credential;\n if (!effectiveCredential) {\n const challenge = createRandomVRFChallenge();\n const authenticators = await webAuthnManager.getAuthenticatorsByUser(nearAccountId);\n const { authenticatorsForPrompt } = await IndexedDBManager.clientDB.ensureCurrentPasskey(\n nearAccountId,\n authenticators,\n );\n effectiveCredential = await webAuthnManager.getAuthenticationCredentialsSerialized({\n nearAccountId,\n challenge: challenge as VRFChallenge,\n allowCredentials: authenticatorsToAllowCredentials(authenticatorsForPrompt),\n });\n }\n\n await webAuthnManager.mintSigningSessionFromCredential({\n nearAccountId,\n credential: effectiveCredential,\n ttlMs,\n remainingUses,\n });\n\n onEvent?.({\n step: 3,\n phase: LoginPhase.STEP_3_VRF_UNLOCK,\n status: LoginStatus.SUCCESS,\n message: 'Warm signing session unlocked'\n });\n}\n\n/**\n * Handle onchain (serverless) login using VRF flow per docs/vrf_challenges.md\n *\n * VRF AUTHENTICATION FLOW:\n * 1. Unlock VRF keypair in VRF Worker memory, either\n * - Decrypt via Shamir 3-pass (when Relayer is present), or\n * - Re-derive the VRF via credentials inside VRF worker dynamically\n * 2. Generate VRF challenge using stored VRF keypair + NEAR block data (no TouchID needed)\n * 3. Use VRF output as WebAuthn challenge for authentication\n * 4. Verify VRF proof and WebAuthn response on contract simultaneously\n * - VRF proof assures WebAuthn challenge is fresh and valid (replay protection)\n * - WebAuthn verification for origin + biometric credentials + device authenticity\n */\nasync function handleLoginUnlockVRF(\n context: PasskeyManagerContext,\n nearAccountId: AccountId,\n onEvent?: (event: LoginSSEvent) => void,\n onError?: (error: Error) => void,\n afterCall?: AfterCall<LoginAndCreateSessionResult>,\n deferCompletionHooks?: boolean,\n deviceNumberHint: number | null = null,\n): Promise<{\n result: LoginResult;\n usedFallbackTouchId: boolean;\n unlockCredential?: WebAuthnAuthenticationCredential;\n activeDeviceNumber: number | null;\n}> {\n const { webAuthnManager } = context;\n\n try {\n // Step 1: Get VRF credentials and authenticators, and validate them\n const hintUserPromise: Promise<ClientUserData | null> =\n deviceNumberHint !== null\n ? webAuthnManager.getUserByDevice(nearAccountId, deviceNumberHint).catch(() => null)\n : Promise.resolve(null);\n\n const [hintUser, lastUser, latestByAccount, authenticators] = await Promise.all([\n hintUserPromise,\n webAuthnManager.getLastUser(),\n IndexedDBManager.clientDB.getLastDBUpdatedUser(nearAccountId),\n webAuthnManager.getAuthenticatorsByUser(nearAccountId),\n ]);\n\n // Prefer the most recently updated record for this account; fall back to lastUser pointer.\n let userData: ClientUserData | null = null;\n if (hintUser && hintUser.nearAccountId === nearAccountId) {\n userData = hintUser;\n } else if (latestByAccount && latestByAccount.nearAccountId === nearAccountId) {\n userData = latestByAccount;\n } else if (lastUser && lastUser.nearAccountId === nearAccountId) {\n userData = lastUser;\n } else {\n userData = await webAuthnManager.getUserByDevice(nearAccountId, 1);\n }\n\n // Validate user data and authenticators\n if (!userData) {\n throw new Error(`User data not found for ${nearAccountId} in IndexedDB. Please register an account.`);\n }\n if (!userData.clientNearPublicKey) {\n throw new Error(`No NEAR public key found for ${nearAccountId}. Please register an account.`);\n }\n if (\n !userData.encryptedVrfKeypair?.encryptedVrfDataB64u ||\n !userData.encryptedVrfKeypair?.chacha20NonceB64u\n ) {\n throw new Error('No VRF credentials found. Please register an account.');\n }\n if (authenticators.length === 0) {\n throw new Error(`No authenticators found for account ${nearAccountId}. Please register.`);\n }\n\n // Step 2: Try Shamir 3-pass commutative unlock first (no TouchID required), fallback to TouchID\n onEvent?.({\n step: 2,\n phase: LoginPhase.STEP_2_WEBAUTHN_ASSERTION,\n status: LoginStatus.PROGRESS,\n message: 'Unlocking VRF keys...'\n });\n\n let unlockResult: { success: boolean; error?: string } = { success: false };\n let usedFallbackTouchId = false;\n let unlockCredential: WebAuthnAuthenticationCredential | undefined;\n let activeDeviceNumber = userData.deviceNumber;\n // Effective user row whose VRF/NEAR keys are actually used for this login.\n // May be switched when multiple devices exist and the user picks a different passkey.\n let effectiveUserData = userData;\n\n const shamir = userData.serverEncryptedVrfKeypair;\n const relayerUrl = context.configs.relayer?.url;\n const useShamir3PassVRFKeyUnlock = !!relayerUrl && !!shamir?.serverKeyId;\n\n if (useShamir3PassVRFKeyUnlock && shamir) {\n try {\n if (!shamir.ciphertextVrfB64u || !shamir.kek_s_b64u) {\n throw new Error('Missing Shamir3Pass fields (ciphertextVrfB64u/kek_s_b64u)');\n }\n\n unlockResult = await webAuthnManager.shamir3PassDecryptVrfKeypair({\n nearAccountId,\n kek_s_b64u: shamir.kek_s_b64u,\n ciphertextVrfB64u: shamir.ciphertextVrfB64u,\n serverKeyId: shamir.serverKeyId,\n });\n\n if (unlockResult.success) {\n const vrfStatus = await webAuthnManager.checkVrfStatus();\n const active = vrfStatus.active && vrfStatus.nearAccountId === nearAccountId;\n if (!active) {\n unlockResult = { success: false, error: 'VRF session inactive after Shamir3Pass' };\n }\n if (active) {\n // Proactive rotation if serverKeyId changed and we unlocked via Shamir\n await webAuthnManager.maybeProactiveShamirRefresh(nearAccountId);\n }\n } else {\n throw new Error(`Shamir3Pass auto-unlock failed: ${unlockResult.error}`);\n }\n } catch (error: any) {\n unlockResult = { success: false, error: error.message };\n }\n }\n\n // Fallback to TouchID if Shamir3Pass decryption failed\n if (!unlockResult.success) {\n const authenticatorsForPrompt = prioritizeAuthenticatorsByDeviceNumber(authenticators, deviceNumberHint);\n const fallback = await fallbackUnlockVrfKeypairWithTouchId({\n webAuthnManager,\n nearAccountId,\n authenticators: authenticatorsForPrompt,\n userData,\n onEvent,\n });\n unlockResult = fallback.unlockResult;\n usedFallbackTouchId = fallback.usedFallbackTouchId;\n unlockCredential = fallback.unlockCredential;\n effectiveUserData = fallback.effectiveUserData;\n activeDeviceNumber = fallback.activeDeviceNumber;\n }\n\n if (!unlockResult.success) {\n throw new Error(`Failed to unlock VRF keypair: ${unlockResult.error}`);\n }\n\n onEvent?.({\n step: 3,\n phase: LoginPhase.STEP_3_VRF_UNLOCK,\n status: LoginStatus.SUCCESS,\n message: 'VRF keypair unlocked...'\n });\n\n // Proactive refresh: if Shamir3Pass failed and we used TouchID, re-encrypt under current server key\n try {\n const relayerUrl = context.configs.relayer?.url;\n if (usedFallbackTouchId && relayerUrl) {\n const refreshed = await webAuthnManager.shamir3PassEncryptCurrentVrfKeypair();\n await webAuthnManager.updateServerEncryptedVrfKeypair(nearAccountId, refreshed, activeDeviceNumber);\n }\n } catch (refreshErr: any) {\n console.warn('Non-fatal: Failed to refresh serverEncryptedVrfKeypair:', refreshErr?.message || refreshErr);\n }\n\n // Step 3: Update local data and return success\n // Ensure last-user deviceNumber reflects the passkey actually used for login.\n try {\n await webAuthnManager.setLastUser(nearAccountId, activeDeviceNumber);\n } catch {\n // Non-fatal; continue even if last-user update fails.\n }\n await webAuthnManager.updateLastLogin(nearAccountId);\n\n const result: LoginResult = {\n success: true,\n loggedInNearAccountId: nearAccountId,\n // Ensure the clientNearPublicKey reflects the device whose VRF credentials\n // are actually active for this login.\n clientNearPublicKey: effectiveUserData.clientNearPublicKey,\n nearAccountId: nearAccountId\n };\n\n if (!deferCompletionHooks) {\n onEvent?.({\n step: 4,\n phase: LoginPhase.STEP_4_LOGIN_COMPLETE,\n status: LoginStatus.SUCCESS,\n message: 'Login completed successfully',\n nearAccountId: nearAccountId,\n clientNearPublicKey: effectiveUserData.clientNearPublicKey\n });\n afterCall?.(true, result);\n }\n return { result, usedFallbackTouchId, unlockCredential, activeDeviceNumber };\n\n } catch (error: any) {\n // Use centralized error handling\n const errorMessage = getUserFriendlyErrorMessage(error, 'login');\n\n onError?.(error);\n onEvent?.({\n step: 0,\n phase: LoginPhase.LOGIN_ERROR,\n status: LoginStatus.ERROR,\n message: errorMessage,\n error: errorMessage\n });\n\n const result: LoginResult = { success: false, error: errorMessage };\n afterCall?.(false);\n return { result, usedFallbackTouchId: false, activeDeviceNumber: null };\n }\n}\n\n/**\n * TouchID fallback path for VRF unlock.\n *\n * Used when Shamir 3-pass auto-unlock fails/unavailable:\n * - Prompts WebAuthn to obtain a serialized credential with PRF.first + PRF.second.\n * - Aligns the local encrypted VRF keypair blob with the passkey the user actually chose\n * (important when multiple devices/passkeys exist for the same account).\n * - Unlocks the VRF keypair inside the VRF worker and returns updated effective user context.\n */\nasync function fallbackUnlockVrfKeypairWithTouchId(args: {\n webAuthnManager: PasskeyManagerContext['webAuthnManager'];\n nearAccountId: AccountId;\n authenticators: ClientAuthenticatorData[];\n userData: ClientUserData;\n onEvent?: (event: LoginSSEvent) => void;\n}): Promise<{\n unlockResult: { success: boolean; error?: string };\n usedFallbackTouchId: boolean;\n unlockCredential: WebAuthnAuthenticationCredential;\n effectiveUserData: ClientUserData;\n activeDeviceNumber: number;\n}> {\n const { webAuthnManager, nearAccountId, authenticators, userData, onEvent } = args;\n\n onEvent?.({\n step: 2,\n phase: LoginPhase.STEP_2_WEBAUTHN_ASSERTION,\n status: LoginStatus.PROGRESS,\n message: 'Logging in, unlocking VRF credentials...'\n });\n\n const challenge = createRandomVRFChallenge();\n const credential = await webAuthnManager.getAuthenticationCredentialsSerializedDualPrf({\n nearAccountId,\n challenge: challenge as VRFChallenge,\n credentialIds: authenticators.map((a) => a.credentialId),\n });\n\n let effectiveUserData = userData;\n let activeDeviceNumber = userData.deviceNumber;\n\n // If multiple authenticators exist, align VRF credentials with the passkey\n // the user actually chose, based on credentialId → deviceNumber.\n if (authenticators.length > 1) {\n const rawId = credential.rawId;\n const matched = authenticators.find(a => a.credentialId === rawId);\n if (matched) {\n try {\n const byDevice = await IndexedDBManager.clientDB.getUserByDevice(nearAccountId, matched.deviceNumber);\n if (byDevice) {\n effectiveUserData = byDevice;\n activeDeviceNumber = matched.deviceNumber;\n }\n } catch {\n // If lookup by device fails, fall back to the base userData.\n }\n }\n }\n\n const unlockResult = await webAuthnManager.unlockVRFKeypair({\n nearAccountId: nearAccountId,\n encryptedVrfKeypair: {\n encryptedVrfDataB64u: effectiveUserData.encryptedVrfKeypair.encryptedVrfDataB64u,\n chacha20NonceB64u: effectiveUserData.encryptedVrfKeypair.chacha20NonceB64u,\n },\n credential: credential,\n });\n\n return {\n unlockResult,\n usedFallbackTouchId: unlockResult.success,\n unlockCredential: credential,\n effectiveUserData,\n activeDeviceNumber,\n };\n}\n\n/**\n * High-level login snapshot used by React contexts/UI.\n *\n * Returns:\n * - `login`: derived from IndexedDB last-user pointer + VRF worker status\n * - `signingSession`: warm signing session status when available\n *\n * The `nearAccountId` argument is treated as a \"query hint\" and must match the\n * last logged-in account to be considered logged in (prevents accidental cross-account reads).\n */\nexport async function getLoginSession(\n context: PasskeyManagerContext,\n nearAccountId?: AccountId\n): Promise<LoginSession> {\n const login = await getLoginStateInternal(context, nearAccountId);\n if (!login?.isLoggedIn || !login.nearAccountId) return { login, signingSession: null };\n try {\n const signingSession = await context.webAuthnManager.getWarmSigningSessionStatus(login.nearAccountId);\n return { login, signingSession };\n } catch {\n return { login, signingSession: null };\n }\n}\n\n/**\n * Internal helper for computing `LoginState`.\n *\n * Implementation detail:\n * - Trusts the IndexedDB last-user pointer for account selection, then confirms\n * that the VRF worker is actually active for that same account.\n */\nasync function getLoginStateInternal(\n context: PasskeyManagerContext,\n nearAccountId?: AccountId\n): Promise<LoginState> {\n const { webAuthnManager } = context;\n try {\n // Determine target account strictly from the last logged-in device.\n const lastUser = await webAuthnManager.getLastUser();\n const targetAccountId = nearAccountId ?? lastUser?.nearAccountId ?? null;\n\n // If caller requested a specific account, it must match the last logged-in account.\n if (!lastUser || (targetAccountId && lastUser.nearAccountId !== targetAccountId)) {\n return {\n isLoggedIn: false,\n nearAccountId: targetAccountId || null,\n publicKey: null,\n vrfActive: false,\n userData: null\n };\n }\n\n const userData = lastUser;\n const publicKey = userData?.clientNearPublicKey || null;\n\n // Check actual VRF worker status\n const vrfStatus = await webAuthnManager.checkVrfStatus();\n const vrfActive = vrfStatus.active && vrfStatus.nearAccountId === targetAccountId;\n\n // Determine if user is considered \"logged in\"\n // User is logged in if they have user data and VRF is active\n const isLoggedIn = !!(userData && userData.clientNearPublicKey && vrfActive);\n\n return {\n isLoggedIn,\n nearAccountId: targetAccountId,\n publicKey,\n vrfActive,\n userData,\n vrfSessionDuration: vrfStatus.sessionDuration || 0\n };\n\n } catch (error: any) {\n console.warn('Error getting login state:', error);\n return {\n isLoggedIn: false,\n nearAccountId: nearAccountId || null,\n publicKey: null,\n vrfActive: false,\n userData: null\n };\n }\n}\n\n/**\n * List recently used accounts from IndexedDB.\n *\n * Used for account picker UIs and initial app bootstrap state.\n */\nexport async function getRecentLogins(\n context: PasskeyManagerContext\n): Promise<GetRecentLoginsResult> {\n const { webAuthnManager } = context;\n // Get all user accounts from IndexDB\n const allUsersData = await webAuthnManager.getAllUsers();\n const accountIds = allUsersData.map(user => user.nearAccountId);\n // Get last used account for initial state\n const lastUsedAccount = await webAuthnManager.getLastUser();\n return {\n accountIds,\n lastUsedAccount,\n };\n}\n\n/**\n * Clear the active VRF session and any client-side nonce caches.\n *\n * This is the canonical \"logout\" operation for the SDK (does not delete accounts).\n */\nexport async function logoutAndClearSession(context: PasskeyManagerContext): Promise<void> {\n const { webAuthnManager } = context;\n await webAuthnManager.clearVrfSession();\n try { webAuthnManager.getNonceManager().clear(); } catch {}\n try { clearAllCachedThresholdEd25519AuthSessions(); } catch {}\n}\n\nfunction prioritizeAuthenticatorsByDeviceNumber(\n authenticators: ClientAuthenticatorData[],\n deviceNumber: number | null\n): ClientAuthenticatorData[] {\n if (authenticators.length <= 1) return authenticators;\n if (deviceNumber === null) return authenticators;\n const preferred = authenticators.filter((a) => a.deviceNumber === deviceNumber);\n if (preferred.length === 0) return authenticators;\n const rest = authenticators.filter((a) => a.deviceNumber !== deviceNumber);\n return [...preferred, ...rest];\n}\n"],"mappings":"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"}
|
|
@@ -45,7 +45,7 @@ async function registerPasskeyInternal(context, nearAccountId, options, authenti
|
|
|
45
45
|
step: 1,
|
|
46
46
|
phase: require_sdkSentEvents.RegistrationPhase.STEP_1_WEBAUTHN_VERIFICATION,
|
|
47
47
|
status: require_sdkSentEvents.RegistrationStatus.PROGRESS,
|
|
48
|
-
message: "
|
|
48
|
+
message: "Generating passkey credential..."
|
|
49
49
|
});
|
|
50
50
|
const confirmationConfig = {
|
|
51
51
|
uiMode: "modal",
|
|
@@ -61,26 +61,13 @@ async function registerPasskeyInternal(context, nearAccountId, options, authenti
|
|
|
61
61
|
});
|
|
62
62
|
const credential = registrationSession.credential;
|
|
63
63
|
const vrfChallenge = registrationSession.vrfChallenge;
|
|
64
|
+
const transactionContext = registrationSession.transactionContext;
|
|
64
65
|
onEvent?.({
|
|
65
66
|
step: 1,
|
|
66
67
|
phase: require_sdkSentEvents.RegistrationPhase.STEP_1_WEBAUTHN_VERIFICATION,
|
|
67
68
|
status: require_sdkSentEvents.RegistrationStatus.SUCCESS,
|
|
68
69
|
message: "WebAuthn ceremony successful"
|
|
69
70
|
});
|
|
70
|
-
const canRegisterUserPromise = webAuthnManager.checkCanRegisterUser({
|
|
71
|
-
contractId: context.configs.contractId,
|
|
72
|
-
credential,
|
|
73
|
-
vrfChallenge,
|
|
74
|
-
onEvent: (progress) => {
|
|
75
|
-
console.debug(`Registration progress: ${progress.step} - ${progress.message}`);
|
|
76
|
-
onEvent?.({
|
|
77
|
-
step: 3,
|
|
78
|
-
phase: require_sdkSentEvents.RegistrationPhase.STEP_3_CONTRACT_PRE_CHECK,
|
|
79
|
-
status: require_sdkSentEvents.RegistrationStatus.PROGRESS,
|
|
80
|
-
message: `${progress.message}`
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
71
|
const deterministicVrfKeyResult = await webAuthnManager.deriveVrfKeypair({
|
|
85
72
|
credential,
|
|
86
73
|
nearAccountId,
|
|
@@ -112,12 +99,6 @@ async function registerPasskeyInternal(context, nearAccountId, options, authenti
|
|
|
112
99
|
if (!derived.success || !derived.clientVerifyingShareB64u) throw new Error(derived.error || "Failed to derive threshold client verifying share");
|
|
113
100
|
thresholdClientVerifyingShareB64u = derived.clientVerifyingShareB64u;
|
|
114
101
|
}
|
|
115
|
-
const canRegisterUserResult = await canRegisterUserPromise;
|
|
116
|
-
if (!canRegisterUserResult.verified) {
|
|
117
|
-
console.error(canRegisterUserResult);
|
|
118
|
-
const errorMessage = canRegisterUserResult.error || "User verification failed - account may already exist or contract is unreachable";
|
|
119
|
-
throw new Error(`Web3Authn contract registration check failed: ${errorMessage}`);
|
|
120
|
-
}
|
|
121
102
|
onEvent?.({
|
|
122
103
|
step: 2,
|
|
123
104
|
phase: require_sdkSentEvents.RegistrationPhase.STEP_2_KEY_GENERATION,
|
|
@@ -143,15 +124,26 @@ async function registerPasskeyInternal(context, nearAccountId, options, authenti
|
|
|
143
124
|
const expectedAccessKeys = [nearPublicKey];
|
|
144
125
|
const thresholdPublicKey = String(accountAndRegistrationResult?.thresholdEd25519?.publicKey || "").trim();
|
|
145
126
|
const relayerKeyId = String(accountAndRegistrationResult?.thresholdEd25519?.relayerKeyId || "").trim();
|
|
146
|
-
const accessKeyVerified = await verifyAccountAccessKeysPresent(context.nearClient, nearAccountId, expectedAccessKeys
|
|
147
|
-
|
|
148
|
-
|
|
127
|
+
const accessKeyVerified = await verifyAccountAccessKeysPresent(context.nearClient, nearAccountId, expectedAccessKeys, {
|
|
128
|
+
attempts: 3,
|
|
129
|
+
delayMs: 200,
|
|
130
|
+
finality: "optimistic"
|
|
131
|
+
});
|
|
132
|
+
if (!accessKeyVerified) {
|
|
133
|
+
console.warn("[Registration] Access key not yet visible after atomic registration; continuing optimistically");
|
|
134
|
+
onEvent?.({
|
|
135
|
+
step: 6,
|
|
136
|
+
phase: require_sdkSentEvents.RegistrationPhase.STEP_6_ACCOUNT_VERIFICATION,
|
|
137
|
+
status: require_sdkSentEvents.RegistrationStatus.SUCCESS,
|
|
138
|
+
message: "Access key verification pending (optimistic); continuing..."
|
|
139
|
+
});
|
|
140
|
+
} else onEvent?.({
|
|
149
141
|
step: 6,
|
|
150
142
|
phase: require_sdkSentEvents.RegistrationPhase.STEP_6_ACCOUNT_VERIFICATION,
|
|
151
143
|
status: require_sdkSentEvents.RegistrationStatus.SUCCESS,
|
|
152
144
|
message: "Access key verified on-chain"
|
|
153
145
|
});
|
|
154
|
-
|
|
146
|
+
activateThresholdEnrollmentPostRegistration({
|
|
155
147
|
requestedSignerMode: requestedSignerModeStr,
|
|
156
148
|
nearAccountId,
|
|
157
149
|
nearPublicKey,
|
|
@@ -166,10 +158,10 @@ async function registerPasskeyInternal(context, nearAccountId, options, authenti
|
|
|
166
158
|
webAuthnManager: context.webAuthnManager,
|
|
167
159
|
nearClient: context.nearClient,
|
|
168
160
|
onEvent
|
|
169
|
-
});
|
|
161
|
+
}).catch(() => {});
|
|
170
162
|
onEvent?.({
|
|
171
|
-
step:
|
|
172
|
-
phase: require_sdkSentEvents.RegistrationPhase.
|
|
163
|
+
step: 8,
|
|
164
|
+
phase: require_sdkSentEvents.RegistrationPhase.STEP_8_DATABASE_STORAGE,
|
|
173
165
|
status: require_sdkSentEvents.RegistrationStatus.PROGRESS,
|
|
174
166
|
message: "Storing passkey wallet metadata..."
|
|
175
167
|
});
|
|
@@ -183,36 +175,17 @@ async function registerPasskeyInternal(context, nearAccountId, options, authenti
|
|
|
183
175
|
});
|
|
184
176
|
registrationState.databaseStored = true;
|
|
185
177
|
onEvent?.({
|
|
186
|
-
step:
|
|
187
|
-
phase: require_sdkSentEvents.RegistrationPhase.
|
|
178
|
+
step: 8,
|
|
179
|
+
phase: require_sdkSentEvents.RegistrationPhase.STEP_8_DATABASE_STORAGE,
|
|
188
180
|
status: require_sdkSentEvents.RegistrationStatus.SUCCESS,
|
|
189
181
|
message: "Registration metadata stored successfully"
|
|
190
182
|
});
|
|
191
|
-
try {
|
|
192
|
-
context.webAuthnManager.getNonceManager().initializeUser(nearAccountId, nearPublicKey);
|
|
193
|
-
await context.webAuthnManager.getNonceManager().prefetchBlockheight(context.nearClient);
|
|
194
|
-
} catch {}
|
|
195
183
|
let vrfStatus = await webAuthnManager.checkVrfStatus().catch(() => ({ active: false }));
|
|
196
184
|
if (!vrfStatus?.active) {
|
|
197
|
-
const
|
|
198
|
-
const txBlockHash = blockInfo?.header?.hash;
|
|
199
|
-
const txBlockHeight = String(blockInfo.header?.height ?? "");
|
|
200
|
-
const vrfChallenge2 = await webAuthnManager.generateVrfChallengeOnce({
|
|
201
|
-
userId: nearAccountId,
|
|
202
|
-
rpId: webAuthnManager.getRpId(),
|
|
203
|
-
blockHash: txBlockHash,
|
|
204
|
-
blockHeight: txBlockHeight
|
|
205
|
-
});
|
|
206
|
-
const authenticators = await webAuthnManager.getAuthenticatorsByUser(nearAccountId);
|
|
207
|
-
const authCredential = await webAuthnManager.getAuthenticationCredentialsSerializedDualPrf({
|
|
208
|
-
nearAccountId,
|
|
209
|
-
challenge: vrfChallenge2,
|
|
210
|
-
credentialIds: authenticators.map((a) => a.credentialId)
|
|
211
|
-
});
|
|
212
|
-
const unlockResult = await webAuthnManager.unlockVRFKeypair({
|
|
185
|
+
const unlockNoPrompt = await webAuthnManager.unlockVRFKeypair({
|
|
213
186
|
nearAccountId,
|
|
214
187
|
encryptedVrfKeypair: deterministicVrfKeyResult.encryptedVrfKeypair,
|
|
215
|
-
credential
|
|
188
|
+
credential
|
|
216
189
|
}).catch((unlockError) => {
|
|
217
190
|
const message = unlockError && typeof unlockError === "object" && "message" in unlockError ? String(unlockError.message || "") : String(unlockError || "");
|
|
218
191
|
return {
|
|
@@ -220,19 +193,52 @@ async function registerPasskeyInternal(context, nearAccountId, options, authenti
|
|
|
220
193
|
error: message
|
|
221
194
|
};
|
|
222
195
|
});
|
|
223
|
-
if (!
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
196
|
+
if (!unlockNoPrompt.success) {
|
|
197
|
+
let txBlockHash = String(transactionContext?.txBlockHash || "").trim();
|
|
198
|
+
let txBlockHeight = String(transactionContext?.txBlockHeight || "").trim();
|
|
199
|
+
if (!txBlockHash || !txBlockHeight) {
|
|
200
|
+
const blockInfo = await context.nearClient.viewBlock({ finality: "final" });
|
|
201
|
+
txBlockHash = String(blockInfo?.header?.hash || "").trim();
|
|
202
|
+
txBlockHeight = String(blockInfo?.header?.height ?? "").trim();
|
|
203
|
+
}
|
|
204
|
+
const vrfChallenge2 = await webAuthnManager.generateVrfChallengeOnce({
|
|
205
|
+
userId: nearAccountId,
|
|
206
|
+
rpId: webAuthnManager.getRpId(),
|
|
207
|
+
blockHash: txBlockHash,
|
|
208
|
+
blockHeight: txBlockHeight
|
|
209
|
+
});
|
|
210
|
+
const allowCredentialIds = String(credential?.rawId || "").trim() ? [String(credential.rawId)] : (await webAuthnManager.getAuthenticatorsByUser(nearAccountId)).map((a) => a.credentialId);
|
|
211
|
+
const authCredential = await webAuthnManager.getAuthenticationCredentialsSerializedDualPrf({
|
|
212
|
+
nearAccountId,
|
|
213
|
+
challenge: vrfChallenge2,
|
|
214
|
+
credentialIds: allowCredentialIds
|
|
215
|
+
});
|
|
216
|
+
const unlockResult = await webAuthnManager.unlockVRFKeypair({
|
|
217
|
+
nearAccountId,
|
|
218
|
+
encryptedVrfKeypair: deterministicVrfKeyResult.encryptedVrfKeypair,
|
|
219
|
+
credential: authCredential
|
|
220
|
+
}).catch((unlockError) => {
|
|
221
|
+
const message = unlockError && typeof unlockError === "object" && "message" in unlockError ? String(unlockError.message || "") : String(unlockError || "");
|
|
222
|
+
return {
|
|
223
|
+
success: false,
|
|
224
|
+
error: message
|
|
225
|
+
};
|
|
226
|
+
});
|
|
227
|
+
if (!unlockResult.success) {
|
|
228
|
+
console.warn("VRF keypair unlock failed:", unlockResult.error);
|
|
229
|
+
throw new Error(unlockResult.error);
|
|
230
|
+
}
|
|
231
|
+
} else console.debug("Registration: VRF unlocked using registration credential; skipping extra Touch ID unlock");
|
|
227
232
|
} else console.debug("Registration: VRF session already active; skipping extra Touch ID unlock");
|
|
228
233
|
try {
|
|
229
|
-
await webAuthnManager.initializeCurrentUser(nearAccountId
|
|
234
|
+
await webAuthnManager.initializeCurrentUser(nearAccountId);
|
|
235
|
+
webAuthnManager.getNonceManager().prefetchBlockheight(context.nearClient).catch(() => {});
|
|
230
236
|
} catch (initErr) {
|
|
231
237
|
console.warn("Failed to initialize current user after registration:", initErr);
|
|
232
238
|
}
|
|
233
239
|
onEvent?.({
|
|
234
|
-
step:
|
|
235
|
-
phase: require_sdkSentEvents.RegistrationPhase.
|
|
240
|
+
step: 9,
|
|
241
|
+
phase: require_sdkSentEvents.RegistrationPhase.STEP_9_REGISTRATION_COMPLETE,
|
|
236
242
|
status: require_sdkSentEvents.RegistrationStatus.SUCCESS,
|
|
237
243
|
message: "Registration completed!"
|
|
238
244
|
});
|
|
@@ -359,8 +365,31 @@ async function activateThresholdEnrollmentPostRegistration(opts) {
|
|
|
359
365
|
const relayerKeyId = String(opts.relayerKeyId || "").trim();
|
|
360
366
|
const clientVerifyingShareB64u = String(opts.thresholdClientVerifyingShareB64u || "").trim();
|
|
361
367
|
const relayerVerifyingShareB64u = String(opts.relayerVerifyingShareB64u || "").trim();
|
|
362
|
-
|
|
368
|
+
const emitThresholdKeyEnrollmentResult = (input) => {
|
|
369
|
+
opts.onEvent?.({
|
|
370
|
+
step: 7,
|
|
371
|
+
phase: require_sdkSentEvents.RegistrationPhase.STEP_7_THRESHOLD_KEY_ENROLLMENT,
|
|
372
|
+
status: require_sdkSentEvents.RegistrationStatus.SUCCESS,
|
|
373
|
+
message: input.message,
|
|
374
|
+
thresholdKeyReady: input.thresholdKeyReady,
|
|
375
|
+
thresholdPublicKey: thresholdPublicKey || void 0,
|
|
376
|
+
relayerKeyId: relayerKeyId || void 0,
|
|
377
|
+
deviceNumber: 1,
|
|
378
|
+
warning: input.warning
|
|
379
|
+
});
|
|
380
|
+
};
|
|
381
|
+
const missingEnrollmentDetails = [];
|
|
382
|
+
if (!thresholdPublicKey) missingEnrollmentDetails.push("thresholdPublicKey");
|
|
383
|
+
if (!relayerKeyId) missingEnrollmentDetails.push("relayerKeyId");
|
|
384
|
+
if (!clientVerifyingShareB64u) missingEnrollmentDetails.push("clientVerifyingShareB64u");
|
|
385
|
+
if (!relayerVerifyingShareB64u) missingEnrollmentDetails.push("relayerVerifyingShareB64u");
|
|
386
|
+
if (missingEnrollmentDetails.length) {
|
|
363
387
|
console.warn("[Registration] threshold-signer requested but threshold enrollment details are missing; continuing with local-signer only");
|
|
388
|
+
emitThresholdKeyEnrollmentResult({
|
|
389
|
+
thresholdKeyReady: false,
|
|
390
|
+
message: "Threshold key not ready; continuing with local-signer only",
|
|
391
|
+
warning: `Missing threshold enrollment details: ${missingEnrollmentDetails.join(", ")}`
|
|
392
|
+
});
|
|
364
393
|
return;
|
|
365
394
|
}
|
|
366
395
|
try {
|
|
@@ -387,7 +416,11 @@ async function activateThresholdEnrollmentPostRegistration(opts) {
|
|
|
387
416
|
const signedTx = signed?.signedTransaction;
|
|
388
417
|
if (!signedTx) throw new Error("Failed to sign AddKey(thresholdPublicKey) transaction");
|
|
389
418
|
await opts.nearClient.sendTransaction(signedTx, require_rpc.DEFAULT_WAIT_STATUS.thresholdAddKey);
|
|
390
|
-
const thresholdKeyVerified = await verifyAccountAccessKeysPresent(opts.nearClient, opts.nearAccountId, [opts.nearPublicKey, thresholdPublicKey]
|
|
419
|
+
const thresholdKeyVerified = await verifyAccountAccessKeysPresent(opts.nearClient, opts.nearAccountId, [opts.nearPublicKey, thresholdPublicKey], {
|
|
420
|
+
attempts: 6,
|
|
421
|
+
delayMs: 250,
|
|
422
|
+
finality: "optimistic"
|
|
423
|
+
});
|
|
391
424
|
if (!thresholdKeyVerified) throw new Error("Threshold access key not found on-chain after AddKey");
|
|
392
425
|
await require_index.IndexedDBManager.nearKeysDB.storeKeyMaterial({
|
|
393
426
|
kind: "threshold_ed25519_2p_v1",
|
|
@@ -414,8 +447,18 @@ async function activateThresholdEnrollmentPostRegistration(opts) {
|
|
|
414
447
|
status: require_sdkSentEvents.RegistrationStatus.SUCCESS,
|
|
415
448
|
message: "Threshold access key activated on-chain"
|
|
416
449
|
});
|
|
450
|
+
emitThresholdKeyEnrollmentResult({
|
|
451
|
+
thresholdKeyReady: true,
|
|
452
|
+
message: "Threshold key ready"
|
|
453
|
+
});
|
|
417
454
|
} catch (e) {
|
|
455
|
+
const warning = e && typeof e === "object" && "message" in e ? String(e.message || "") : String(e || "");
|
|
418
456
|
console.warn("[Registration] threshold enrollment activation failed; continuing with local-signer only:", e);
|
|
457
|
+
emitThresholdKeyEnrollmentResult({
|
|
458
|
+
thresholdKeyReady: false,
|
|
459
|
+
message: "Threshold key not ready; continuing with local-signer only",
|
|
460
|
+
warning
|
|
461
|
+
});
|
|
419
462
|
}
|
|
420
463
|
}
|
|
421
464
|
async function verifyAccountAccessKeysPresent(nearClient, nearAccountId, expectedPublicKeys, opts) {
|
|
@@ -423,14 +466,15 @@ async function verifyAccountAccessKeysPresent(nearClient, nearAccountId, expecte
|
|
|
423
466
|
if (!unique.length) return false;
|
|
424
467
|
const attempts = Math.max(1, Math.floor(opts?.attempts ?? 6));
|
|
425
468
|
const delayMs = Math.max(50, Math.floor(opts?.delayMs ?? 750));
|
|
469
|
+
const finality = opts?.finality ?? "optimistic";
|
|
426
470
|
for (let i = 0; i < attempts; i++) {
|
|
427
471
|
try {
|
|
428
|
-
const
|
|
429
|
-
const keys =
|
|
472
|
+
const accessKeyList = await nearClient.viewAccessKeyList(nearAccountId, { finality });
|
|
473
|
+
const keys = accessKeyList.keys.map((k) => require_validation.ensureEd25519Prefix(k.public_key)).filter(Boolean);
|
|
430
474
|
const allPresent = unique.every((expected) => keys.includes(expected));
|
|
431
475
|
if (allPresent) return true;
|
|
432
476
|
} catch {}
|
|
433
|
-
await new Promise((res) => setTimeout(res, delayMs));
|
|
477
|
+
if (i < attempts - 1) await new Promise((res) => setTimeout(res, delayMs));
|
|
434
478
|
}
|
|
435
479
|
return false;
|
|
436
480
|
}
|