From 019a3d132891b522254b1db2fe698f9eb09257e4 Mon Sep 17 00:00:00 2001 From: Kefan Cao Date: Thu, 16 Oct 2025 01:30:18 -0400 Subject: [PATCH 01/11] test --- src/providers/TurnkeyAuthProvider.tsx | 33 +++++++++++++++++++-------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/providers/TurnkeyAuthProvider.tsx b/src/providers/TurnkeyAuthProvider.tsx index a8086507b5..a7bdf1d254 100644 --- a/src/providers/TurnkeyAuthProvider.tsx +++ b/src/providers/TurnkeyAuthProvider.tsx @@ -1,4 +1,12 @@ -import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import { logBonsaiError, logBonsaiInfo } from '@/bonsai/logs'; import { selectIndexerUrl } from '@/bonsai/socketSelectors'; @@ -77,6 +85,7 @@ const useTurnkeyAuthContext = () => { const [emailSignInStatus, setEmailSignInStatus] = useState< 'idle' | 'loading' | 'success' | 'error' >('idle'); + const processedEmailTokenRef = useRef(null); const { embeddedPublicKey, @@ -484,6 +493,13 @@ const useTurnkeyAuthContext = () => { ] ); + const handleEmailMagicLinkRef = useRef(handleEmailMagicLink); + + // Update the ref whenever handleEmailMagicLink changes + useEffect(() => { + handleEmailMagicLinkRef.current = handleEmailMagicLink; + }, [handleEmailMagicLink]); + const signInWithOtp = useCallback( async ({ userEmail }: { userEmail: string }) => { try { @@ -522,6 +538,7 @@ const useTurnkeyAuthContext = () => { setEmailToken(undefined); setEmailSignInStatus('idle'); setEmailSignInError(undefined); + processedEmailTokenRef.current = null; }, [searchParams, setSearchParams]); /* ----------------------------- Side Effects ----------------------------- */ @@ -559,19 +576,15 @@ const useTurnkeyAuthContext = () => { emailToken && targetPublicKeys?.publicKey && authIframeClient && - emailSignInStatus === 'idle' + emailSignInStatus === 'idle' && + processedEmailTokenRef.current !== emailToken ) { + processedEmailTokenRef.current = emailToken; track(AnalyticsEvents.TurnkeyLoginEmailToken({})); logBonsaiInfo('TurnkeyOnboarding', 'Attempting to handle email magic link'); - handleEmailMagicLink({ token: emailToken }); + handleEmailMagicLinkRef.current({ token: emailToken }); } - }, [ - emailToken, - targetPublicKeys?.publicKey, - authIframeClient, - handleEmailMagicLink, - emailSignInStatus, - ]); + }, [emailToken, targetPublicKeys?.publicKey, authIframeClient, emailSignInStatus]); const needsAddressUpload = useMemo(() => { return ( From a21127e6888436168bcbaf192767c9ffc31504b5 Mon Sep 17 00:00:00 2001 From: Kefan Cao Date: Wed, 5 Nov 2025 17:27:31 -0500 Subject: [PATCH 02/11] initial --- src/providers/TurnkeyAuthProvider.tsx | 90 ++++++++++++++++++- src/providers/TurnkeyWalletProvider.tsx | 3 +- src/types/turnkey.ts | 22 ++++- src/views/dialogs/OnboardingDialog/SignIn.tsx | 8 +- 4 files changed, 112 insertions(+), 11 deletions(-) diff --git a/src/providers/TurnkeyAuthProvider.tsx b/src/providers/TurnkeyAuthProvider.tsx index a7bdf1d254..c8d05db6c2 100644 --- a/src/providers/TurnkeyAuthProvider.tsx +++ b/src/providers/TurnkeyAuthProvider.tsx @@ -11,7 +11,7 @@ import { import { logBonsaiError, logBonsaiInfo } from '@/bonsai/logs'; import { selectIndexerUrl } from '@/bonsai/socketSelectors'; import { useMutation } from '@tanstack/react-query'; -import { TurnkeyIndexedDbClient } from '@turnkey/sdk-browser'; +import { SessionType, TurnkeyIndexedDbClient } from '@turnkey/sdk-browser'; import { useTurnkey } from '@turnkey/sdk-react'; import { jwtDecode } from 'jwt-decode'; import { useSearchParams } from 'react-router-dom'; @@ -77,7 +77,7 @@ const useTurnkeyAuthContext = () => { const stringGetter = useStringGetter(); const indexerUrl = useAppSelector(selectIndexerUrl); const sourceAccount = useAppSelector(getSourceAccount); - const { indexedDbClient, authIframeClient } = useTurnkey(); + const { indexedDbClient, authIframeClient, passkeyClient } = useTurnkey(); const { dydxAddress: connectedDydxAddress, setWalletFromSignature, selectWallet } = useAccounts(); const [searchParams, setSearchParams] = useSearchParams(); const [emailToken, setEmailToken] = useState(); @@ -226,7 +226,9 @@ const useTurnkeyAuthContext = () => { handleEmailResponse({ userEmail, response }); setEmailSignInStatus('idle'); break; - case LoginMethod.Passkey: // TODO: handle passkey response + case LoginMethod.Passkey: + handlePasskeyResponse({ response }); + break; default: throw new Error('Current unsupported login method'); } @@ -341,6 +343,50 @@ const useTurnkeyAuthContext = () => { [onboardDydx, indexedDbClient, setWalletFromSignature, uploadAddress] ); + const handlePasskeyResponse = async ({ response }: { response: TurnkeyOAuthResponse }) => { + const { salt, dydxAddress: uploadedDydxAddress } = response as { + salt?: string; + dydxAddress?: string; + }; + + if (!passkeyClient) { + throw new Error('Passkey client is not available'); + } + await indexedDbClient!.resetKeyPair(); + const pubKey = await indexedDbClient!.getPublicKey(); + if (!pubKey) { + throw new Error('No public key available for passkey session'); + } + // Authenticate with the user's passkey for the returned sub-organization + await passkeyClient.loginWithPasskey({ + sessionType: SessionType.READ_WRITE, + publicKey: pubKey, + expirationSeconds: (60 * 15).toString(), // 15 minutes + organizationId: '18af402a-684a-488d-a054-3c5c688eb7d5', + }); + const derivedDydxAddress = await onboardDydx({ + salt, + setWalletFromSignature, + tkClient: indexedDbClient, + }); + + if (uploadedDydxAddress === '' && derivedDydxAddress) { + try { + await uploadAddress({ tkClient: indexedDbClient, dydxAddress: derivedDydxAddress }); + } catch (uploadAddressError) { + if ( + uploadAddressError instanceof Error && + !uploadAddressError.message.includes('Dydx address already uploaded') + ) { + throw uploadAddressError; + } + } + } + + setEmailSignInStatus('success'); + setEmailSignInError(undefined); + }; + /* ----------------------------- Email Sign In ----------------------------- */ const handleEmailResponse = useCallback( @@ -541,6 +587,43 @@ const useTurnkeyAuthContext = () => { processedEmailTokenRef.current = null; }, [searchParams, setSearchParams]); + /* ----------------------------- Passkey Sign In ----------------------------- */ + + const signInWithPasskey = useCallback(async () => { + try { + if (!passkeyClient) { + throw new Error('Passkey client is not available'); + } + + const { encodedChallenge: challenge, attestation } = await passkeyClient.createUserPasskey({ + publicKey: { + user: { + name: 'test.dydx.com', + displayName: 'wallet.dydx.com', + }, + }, + }); + + const bodyWithAttestation: SignInBody = { + signinMethod: 'passkey', + challenge, + attestation: { + transports: attestation.transports, + attestationObject: attestation.attestationObject, + clientDataJson: attestation.clientDataJson, + credentialId: attestation.credentialId, + }, + }; + + sendSignInRequest({ + body: JSON.stringify(bodyWithAttestation), + loginMethod: LoginMethod.Passkey, + }); + } catch (error) { + logBonsaiError('TurnkeyOnboarding', 'Error signing in with passkey', { error }); + } + }, [passkeyClient, sendSignInRequest]); + /* ----------------------------- Side Effects ----------------------------- */ /** @@ -605,6 +688,7 @@ const useTurnkeyAuthContext = () => { isUploadingAddress, signInWithOauth, signInWithOtp, + signInWithPasskey, resetEmailSignInStatus, }; }; diff --git a/src/providers/TurnkeyWalletProvider.tsx b/src/providers/TurnkeyWalletProvider.tsx index 45e2668f49..37aef66011 100644 --- a/src/providers/TurnkeyWalletProvider.tsx +++ b/src/providers/TurnkeyWalletProvider.tsx @@ -201,8 +201,7 @@ const useTurnkeyWalletContext = () => { setWalletFromSignature: (signature: string) => Promise; tkClient?: TurnkeyIndexedDbClient; }) => { - const selectedTurnkeyWallet = primaryTurnkeyWallet ?? (await getPrimaryUserWallets(tkClient)); - + const selectedTurnkeyWallet = await getPrimaryUserWallets(tkClient); const ethAccount = selectedTurnkeyWallet?.accounts.find( (account) => account.addressFormat === AddressFormat.Ethereum ); diff --git a/src/types/turnkey.ts b/src/types/turnkey.ts index 2539e6d37f..43a42590ba 100644 --- a/src/types/turnkey.ts +++ b/src/types/turnkey.ts @@ -49,7 +49,7 @@ export type GoogleIdTokenPayload = { export type SignInBody = | { - signinMethod: 'social' | 'passkey'; + signinMethod: 'social'; targetPublicKey: string; provider: 'google' | 'apple'; oidcToken: string; @@ -57,9 +57,27 @@ export type SignInBody = } | { signinMethod: 'email'; - targetPublicKey: string; + targetPublicKey?: string; userEmail: string; magicLink: string; + } + | { + signinMethod: 'passkey'; + // In a full implementation these are required; left optional to allow + // initiating the flow and handling multi-step server responses. + challenge?: string; + attestation?: { + transports: Array< + | 'AUTHENTICATOR_TRANSPORT_BLE' + | 'AUTHENTICATOR_TRANSPORT_INTERNAL' + | 'AUTHENTICATOR_TRANSPORT_NFC' + | 'AUTHENTICATOR_TRANSPORT_USB' + | 'AUTHENTICATOR_TRANSPORT_HYBRID' + >; + attestationObject: string; // base64url + clientDataJson: string; // base64url + credentialId: string; // base64url + }; }; export type TurnkeyEmailOnboardingData = { diff --git a/src/views/dialogs/OnboardingDialog/SignIn.tsx b/src/views/dialogs/OnboardingDialog/SignIn.tsx index f8551e5bd9..599083dfda 100644 --- a/src/views/dialogs/OnboardingDialog/SignIn.tsx +++ b/src/views/dialogs/OnboardingDialog/SignIn.tsx @@ -52,7 +52,7 @@ export const SignIn = ({ const [email, setEmail] = useState(''); const [isLoading, setIsLoading] = useState(false); const { authIframeClient } = useTurnkey(); - const { signInWithOtp } = useTurnkeyAuth(); + const { signInWithOtp, signInWithPasskey } = useTurnkeyAuth(); const appTheme = useAppSelector(getAppTheme); const { tos, privacy } = useURLConfigs(); const displayedWallets = useDisplayedWallets(); @@ -138,11 +138,11 @@ export const SignIn = ({ <$HorizontalSeparatorFiller $isLightMode={appTheme === AppTheme.Light} /> - {/* <$OtherOptionButton + <$OtherOptionButton type={ButtonType.Button} action={ButtonAction.Base} size={ButtonSize.BasePlus} - onClick={onSignInWithPasskey} + onClick={signInWithPasskey} >
@@ -150,7 +150,7 @@ export const SignIn = ({
- */} + {displayedWallets .filter( From 6610936d430785b425e35a73f4d61a43679b50e3 Mon Sep 17 00:00:00 2001 From: Kefan Cao Date: Wed, 5 Nov 2025 22:51:05 -0500 Subject: [PATCH 03/11] sign in and credential imitation --- src/providers/TurnkeyAuthProvider.tsx | 51 +++++++++++++-------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/providers/TurnkeyAuthProvider.tsx b/src/providers/TurnkeyAuthProvider.tsx index c8d05db6c2..dd3dde21c2 100644 --- a/src/providers/TurnkeyAuthProvider.tsx +++ b/src/providers/TurnkeyAuthProvider.tsx @@ -94,6 +94,7 @@ const useTurnkeyAuthContext = () => { onboardDydx, targetPublicKeys, getUploadAddressPayload, + fetchCredentialId, } = useTurnkeyWallet(); /* ----------------------------- Upload Address ----------------------------- */ @@ -352,18 +353,6 @@ const useTurnkeyAuthContext = () => { if (!passkeyClient) { throw new Error('Passkey client is not available'); } - await indexedDbClient!.resetKeyPair(); - const pubKey = await indexedDbClient!.getPublicKey(); - if (!pubKey) { - throw new Error('No public key available for passkey session'); - } - // Authenticate with the user's passkey for the returned sub-organization - await passkeyClient.loginWithPasskey({ - sessionType: SessionType.READ_WRITE, - publicKey: pubKey, - expirationSeconds: (60 * 15).toString(), // 15 minutes - organizationId: '18af402a-684a-488d-a054-3c5c688eb7d5', - }); const derivedDydxAddress = await onboardDydx({ salt, setWalletFromSignature, @@ -589,29 +578,39 @@ const useTurnkeyAuthContext = () => { /* ----------------------------- Passkey Sign In ----------------------------- */ - const signInWithPasskey = useCallback(async () => { + const signInWithPasskey = async () => { try { if (!passkeyClient) { throw new Error('Passkey client is not available'); } - const { encodedChallenge: challenge, attestation } = await passkeyClient.createUserPasskey({ - publicKey: { - user: { - name: 'test.dydx.com', - displayName: 'wallet.dydx.com', - }, - }, + await indexedDbClient!.resetKeyPair(); + const pubKey = await indexedDbClient!.getPublicKey(); + if (!pubKey) { + throw new Error('No public key available for passkey session'); + } + // Authenticate with the user's passkey for the returned sub-organization + await passkeyClient.loginWithPasskey({ + sessionType: SessionType.READ_WRITE, + publicKey: pubKey, + expirationSeconds: (60 * 15).toString(), // 15 minutes + organizationId: '18af402a-684a-488d-a054-3c5c688eb7d5', }); + const credentialId = await fetchCredentialId(indexedDbClient); + if (!credentialId) { + throw new Error('No user found'); + } + + // dummy body used to get salt. const bodyWithAttestation: SignInBody = { signinMethod: 'passkey', - challenge, + challenge: 'dummy', attestation: { - transports: attestation.transports, - attestationObject: attestation.attestationObject, - clientDataJson: attestation.clientDataJson, - credentialId: attestation.credentialId, + transports: ['AUTHENTICATOR_TRANSPORT_INTERNAL'], + attestationObject: 'dummy', + clientDataJson: 'dummy', + credentialId, }, }; @@ -622,7 +621,7 @@ const useTurnkeyAuthContext = () => { } catch (error) { logBonsaiError('TurnkeyOnboarding', 'Error signing in with passkey', { error }); } - }, [passkeyClient, sendSignInRequest]); + }; /* ----------------------------- Side Effects ----------------------------- */ From 2078998584d9ea4c9fd1809ab40a49282dd2ee6f Mon Sep 17 00:00:00 2001 From: Kefan Cao Date: Wed, 5 Nov 2025 22:51:12 -0500 Subject: [PATCH 04/11] fetch credentials --- src/providers/TurnkeyWalletProvider.tsx | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/providers/TurnkeyWalletProvider.tsx b/src/providers/TurnkeyWalletProvider.tsx index 37aef66011..91a1b679fb 100644 --- a/src/providers/TurnkeyWalletProvider.tsx +++ b/src/providers/TurnkeyWalletProvider.tsx @@ -123,6 +123,33 @@ const useTurnkeyWalletContext = () => { }, [authIframeClient]); /* ----------------------------- Onboarding Functions ----------------------------- */ + + // used to fetch the credential id for the passkey sign in + const fetchCredentialId = useCallback( + async (tkClient?: TurnkeyIndexedDbClient): Promise => { + if (turnkey == null || tkClient == null) { + return undefined; + } + // Try and get the current user + const token = await turnkey.getSession(); + + // If the user is not found, we assume the user is not logged in + if (!token?.expiry || token.expiry > Date.now()) { + return undefined; + } + + const { user: indexedDbUser } = await tkClient.getUser({ + organizationId: token.organizationId, + userId: token.userId, + }); + if (indexedDbUser.authenticators.length === 0) { + return undefined; + } + return indexedDbUser.authenticators[0]?.credentialId; + }, + [turnkey] + ); + const fetchUser = useCallback( async (tkClient?: TurnkeyIndexedDbClient): Promise => { const isIndexedDbFlow = tkClient instanceof TurnkeyIndexedDbClient; @@ -340,6 +367,7 @@ const useTurnkeyWalletContext = () => { isNewTurnkeyUser, endTurnkeySession, + fetchCredentialId, onboardDydx, getUploadAddressPayload, setIsNewTurnkeyUser, From 4f89d98e63182a32a6acf926f25ae7fdb5e633e6 Mon Sep 17 00:00:00 2001 From: Kefan Cao Date: Wed, 5 Nov 2025 22:51:30 -0500 Subject: [PATCH 05/11] add back primary turnkey wallet --- src/providers/TurnkeyWalletProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/providers/TurnkeyWalletProvider.tsx b/src/providers/TurnkeyWalletProvider.tsx index 91a1b679fb..240ba0511b 100644 --- a/src/providers/TurnkeyWalletProvider.tsx +++ b/src/providers/TurnkeyWalletProvider.tsx @@ -228,7 +228,7 @@ const useTurnkeyWalletContext = () => { setWalletFromSignature: (signature: string) => Promise; tkClient?: TurnkeyIndexedDbClient; }) => { - const selectedTurnkeyWallet = await getPrimaryUserWallets(tkClient); + const selectedTurnkeyWallet = primaryTurnkeyWallet ?? (await getPrimaryUserWallets(tkClient)); const ethAccount = selectedTurnkeyWallet?.accounts.find( (account) => account.addressFormat === AddressFormat.Ethereum ); From 7655f3bd138e108d53d1226ca889b92393afa10b Mon Sep 17 00:00:00 2001 From: Kefan Cao Date: Wed, 5 Nov 2025 23:40:45 -0500 Subject: [PATCH 06/11] change --- src/providers/TurnkeyAuthProvider.tsx | 33 ++++++++------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/src/providers/TurnkeyAuthProvider.tsx b/src/providers/TurnkeyAuthProvider.tsx index dd3dde21c2..c65dcf5570 100644 --- a/src/providers/TurnkeyAuthProvider.tsx +++ b/src/providers/TurnkeyAuthProvider.tsx @@ -1,12 +1,4 @@ -import { - createContext, - useCallback, - useContext, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; +import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { logBonsaiError, logBonsaiInfo } from '@/bonsai/logs'; import { selectIndexerUrl } from '@/bonsai/socketSelectors'; @@ -85,7 +77,6 @@ const useTurnkeyAuthContext = () => { const [emailSignInStatus, setEmailSignInStatus] = useState< 'idle' | 'loading' | 'success' | 'error' >('idle'); - const processedEmailTokenRef = useRef(null); const { embeddedPublicKey, @@ -528,13 +519,6 @@ const useTurnkeyAuthContext = () => { ] ); - const handleEmailMagicLinkRef = useRef(handleEmailMagicLink); - - // Update the ref whenever handleEmailMagicLink changes - useEffect(() => { - handleEmailMagicLinkRef.current = handleEmailMagicLink; - }, [handleEmailMagicLink]); - const signInWithOtp = useCallback( async ({ userEmail }: { userEmail: string }) => { try { @@ -573,7 +557,6 @@ const useTurnkeyAuthContext = () => { setEmailToken(undefined); setEmailSignInStatus('idle'); setEmailSignInError(undefined); - processedEmailTokenRef.current = null; }, [searchParams, setSearchParams]); /* ----------------------------- Passkey Sign In ----------------------------- */ @@ -658,15 +641,19 @@ const useTurnkeyAuthContext = () => { emailToken && targetPublicKeys?.publicKey && authIframeClient && - emailSignInStatus === 'idle' && - processedEmailTokenRef.current !== emailToken + emailSignInStatus === 'idle' ) { - processedEmailTokenRef.current = emailToken; track(AnalyticsEvents.TurnkeyLoginEmailToken({})); logBonsaiInfo('TurnkeyOnboarding', 'Attempting to handle email magic link'); - handleEmailMagicLinkRef.current({ token: emailToken }); + handleEmailMagicLink({ token: emailToken }); } - }, [emailToken, targetPublicKeys?.publicKey, authIframeClient, emailSignInStatus]); + }, [ + emailToken, + targetPublicKeys?.publicKey, + authIframeClient, + handleEmailMagicLink, + emailSignInStatus, + ]); const needsAddressUpload = useMemo(() => { return ( From e0b34e2c8b1ff7c1027823bbaff19bc96e725300 Mon Sep 17 00:00:00 2001 From: Kefan Cao Date: Wed, 5 Nov 2025 23:43:06 -0500 Subject: [PATCH 07/11] remove org id --- src/providers/TurnkeyAuthProvider.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/providers/TurnkeyAuthProvider.tsx b/src/providers/TurnkeyAuthProvider.tsx index c65dcf5570..f3ac074f47 100644 --- a/src/providers/TurnkeyAuthProvider.tsx +++ b/src/providers/TurnkeyAuthProvider.tsx @@ -577,7 +577,6 @@ const useTurnkeyAuthContext = () => { sessionType: SessionType.READ_WRITE, publicKey: pubKey, expirationSeconds: (60 * 15).toString(), // 15 minutes - organizationId: '18af402a-684a-488d-a054-3c5c688eb7d5', }); const credentialId = await fetchCredentialId(indexedDbClient); From d33a92e16788ab2f6324a15c83d9a18048ed1b1a Mon Sep 17 00:00:00 2001 From: Kefan Cao Date: Wed, 5 Nov 2025 23:49:26 -0500 Subject: [PATCH 08/11] made optional --- src/providers/TurnkeyAuthProvider.tsx | 5 +---- src/types/turnkey.ts | 6 +++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/providers/TurnkeyAuthProvider.tsx b/src/providers/TurnkeyAuthProvider.tsx index f3ac074f47..4f143b8507 100644 --- a/src/providers/TurnkeyAuthProvider.tsx +++ b/src/providers/TurnkeyAuthProvider.tsx @@ -587,11 +587,8 @@ const useTurnkeyAuthContext = () => { // dummy body used to get salt. const bodyWithAttestation: SignInBody = { signinMethod: 'passkey', - challenge: 'dummy', + challenge: credentialId, attestation: { - transports: ['AUTHENTICATOR_TRANSPORT_INTERNAL'], - attestationObject: 'dummy', - clientDataJson: 'dummy', credentialId, }, }; diff --git a/src/types/turnkey.ts b/src/types/turnkey.ts index 43a42590ba..0b50f120a0 100644 --- a/src/types/turnkey.ts +++ b/src/types/turnkey.ts @@ -67,15 +67,15 @@ export type SignInBody = // initiating the flow and handling multi-step server responses. challenge?: string; attestation?: { - transports: Array< + transports?: Array< | 'AUTHENTICATOR_TRANSPORT_BLE' | 'AUTHENTICATOR_TRANSPORT_INTERNAL' | 'AUTHENTICATOR_TRANSPORT_NFC' | 'AUTHENTICATOR_TRANSPORT_USB' | 'AUTHENTICATOR_TRANSPORT_HYBRID' >; - attestationObject: string; // base64url - clientDataJson: string; // base64url + attestationObject?: string; // base64url + clientDataJson?: string; // base64url credentialId: string; // base64url }; }; From 09918908794bdd4156e80eafc4dc1d900bcbad1c Mon Sep 17 00:00:00 2001 From: Kefan Cao Date: Wed, 5 Nov 2025 23:53:33 -0500 Subject: [PATCH 09/11] bring back callbacks --- src/providers/TurnkeyAuthProvider.tsx | 67 ++++++++++++++++----------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/src/providers/TurnkeyAuthProvider.tsx b/src/providers/TurnkeyAuthProvider.tsx index 4f143b8507..8174da4d16 100644 --- a/src/providers/TurnkeyAuthProvider.tsx +++ b/src/providers/TurnkeyAuthProvider.tsx @@ -335,37 +335,48 @@ const useTurnkeyAuthContext = () => { [onboardDydx, indexedDbClient, setWalletFromSignature, uploadAddress] ); - const handlePasskeyResponse = async ({ response }: { response: TurnkeyOAuthResponse }) => { - const { salt, dydxAddress: uploadedDydxAddress } = response as { - salt?: string; - dydxAddress?: string; - }; + const handlePasskeyResponse = useCallback( + async ({ response }: { response: TurnkeyOAuthResponse }) => { + const { salt, dydxAddress: uploadedDydxAddress } = response as { + salt?: string; + dydxAddress?: string; + }; - if (!passkeyClient) { - throw new Error('Passkey client is not available'); - } - const derivedDydxAddress = await onboardDydx({ - salt, - setWalletFromSignature, - tkClient: indexedDbClient, - }); + if (!passkeyClient) { + throw new Error('Passkey client is not available'); + } + const derivedDydxAddress = await onboardDydx({ + salt, + setWalletFromSignature, + tkClient: indexedDbClient, + }); - if (uploadedDydxAddress === '' && derivedDydxAddress) { - try { - await uploadAddress({ tkClient: indexedDbClient, dydxAddress: derivedDydxAddress }); - } catch (uploadAddressError) { - if ( - uploadAddressError instanceof Error && - !uploadAddressError.message.includes('Dydx address already uploaded') - ) { - throw uploadAddressError; + if (uploadedDydxAddress === '' && derivedDydxAddress) { + try { + await uploadAddress({ tkClient: indexedDbClient, dydxAddress: derivedDydxAddress }); + } catch (uploadAddressError) { + if ( + uploadAddressError instanceof Error && + !uploadAddressError.message.includes('Dydx address already uploaded') + ) { + throw uploadAddressError; + } } } - } - setEmailSignInStatus('success'); - setEmailSignInError(undefined); - }; + setEmailSignInStatus('success'); + setEmailSignInError(undefined); + }, + [ + onboardDydx, + indexedDbClient, + setWalletFromSignature, + uploadAddress, + setEmailSignInStatus, + setEmailSignInError, + passkeyClient, + ] + ); /* ----------------------------- Email Sign In ----------------------------- */ @@ -561,7 +572,7 @@ const useTurnkeyAuthContext = () => { /* ----------------------------- Passkey Sign In ----------------------------- */ - const signInWithPasskey = async () => { + const signInWithPasskey = useCallback(async () => { try { if (!passkeyClient) { throw new Error('Passkey client is not available'); @@ -600,7 +611,7 @@ const useTurnkeyAuthContext = () => { } catch (error) { logBonsaiError('TurnkeyOnboarding', 'Error signing in with passkey', { error }); } - }; + }, [passkeyClient, indexedDbClient, fetchCredentialId, sendSignInRequest]); /* ----------------------------- Side Effects ----------------------------- */ From 5a7308dfed9e285b42357b624cb5216b76bf3dcd Mon Sep 17 00:00:00 2001 From: Kefan Cao Date: Wed, 5 Nov 2025 23:57:31 -0500 Subject: [PATCH 10/11] test flags --- src/lib/testFlags.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/testFlags.ts b/src/lib/testFlags.ts index 5c67498e18..97ffacb767 100644 --- a/src/lib/testFlags.ts +++ b/src/lib/testFlags.ts @@ -72,6 +72,10 @@ class TestFlags { return this.booleanFlag(this.queryParams.apple_auth); } + get enablePasskeyAuth() { + return this.booleanFlag(this.queryParams.passkey_auth); + } + get spot() { return this.booleanFlag(this.queryParams.spot); } From 7881e3e2c3fe1e01760f5a73785f0afe13e4e566 Mon Sep 17 00:00:00 2001 From: Kefan Cao Date: Wed, 5 Nov 2025 23:57:51 -0500 Subject: [PATCH 11/11] test flag --- src/views/dialogs/OnboardingDialog/SignIn.tsx | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/views/dialogs/OnboardingDialog/SignIn.tsx b/src/views/dialogs/OnboardingDialog/SignIn.tsx index 599083dfda..d2c9f36b69 100644 --- a/src/views/dialogs/OnboardingDialog/SignIn.tsx +++ b/src/views/dialogs/OnboardingDialog/SignIn.tsx @@ -138,19 +138,21 @@ export const SignIn = ({ <$HorizontalSeparatorFiller $isLightMode={appTheme === AppTheme.Light} /> - <$OtherOptionButton - type={ButtonType.Button} - action={ButtonAction.Base} - size={ButtonSize.BasePlus} - onClick={signInWithPasskey} - > -
- - {stringGetter({ key: STRING_KEYS.SIGN_IN_PASSKEY })} -
+ {testFlags.enablePasskeyAuth && ( + <$OtherOptionButton + type={ButtonType.Button} + action={ButtonAction.Base} + size={ButtonSize.BasePlus} + onClick={signInWithPasskey} + > +
+ + {stringGetter({ key: STRING_KEYS.SIGN_IN_PASSKEY })} +
- - + + + )} {displayedWallets .filter(