Skip to content

Commit 34ea8f9

Browse files
authored
refactor: upload address as part of login (#1927)
1 parent e0e3a9a commit 34ea8f9

File tree

4 files changed

+123
-115
lines changed

4 files changed

+123
-115
lines changed

src/hooks/useAccounts.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ const useAccountsContext = () => {
162162
hdKeyManager.setHdkey(wallet.address, key);
163163
setLocalDydxWallet(wallet);
164164
setHdKey(key);
165+
return wallet.address;
165166
},
166167
[getWalletFromSignature]
167168
);

src/providers/TurnkeyAuthProvider.tsx

Lines changed: 118 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,7 @@ import { useStringGetter } from '@/hooks/useStringGetter';
2626
import { appQueryClient } from '@/state/appQueryClient';
2727
import { useAppDispatch, useAppSelector } from '@/state/appTypes';
2828
import { forceOpenDialog, openDialog } from '@/state/dialogs';
29-
import {
30-
setRequiresAddressUpload,
31-
setTurnkeyEmailOnboardingData,
32-
setWalletInfo,
33-
} from '@/state/wallet';
29+
import { setTurnkeyEmailOnboardingData, setWalletInfo } from '@/state/wallet';
3430
import { getSourceAccount, getTurnkeyEmailOnboardingData } from '@/state/walletSelectors';
3531

3632
import { identify, track } from '@/lib/analytics/analytics';
@@ -74,7 +70,7 @@ const useTurnkeyAuthContext = () => {
7470
const indexerUrl = useAppSelector(selectIndexerUrl);
7571
const sourceAccount = useAppSelector(getSourceAccount);
7672
const { indexedDbClient, authIframeClient } = useTurnkey();
77-
const { dydxAddress, setWalletFromSignature, selectWallet } = useAccounts();
73+
const { dydxAddress: connectedDydxAddress, setWalletFromSignature, selectWallet } = useAccounts();
7874
const [searchParams, setSearchParams] = useSearchParams();
7975
const [emailToken, setEmailToken] = useState<string>();
8076
const [emailSignInError, setEmailSignInError] = useState<string>();
@@ -91,6 +87,71 @@ const useTurnkeyAuthContext = () => {
9187
getUploadAddressPayload,
9288
} = useTurnkeyWallet();
9389

90+
/* ----------------------------- Upload Address ----------------------------- */
91+
92+
const { mutateAsync: sendUploadAddressRequest, isPending: isUploadingAddress } = useMutation({
93+
mutationFn: async ({
94+
payload,
95+
}: {
96+
payload: { dydxAddress: string; signature: string };
97+
}): Promise<{ success: boolean }> => {
98+
const body = JSON.stringify(payload);
99+
100+
const response = await fetch(`${indexerUrl}/v4/turnkey/uploadAddress`, {
101+
method: 'POST',
102+
headers: {
103+
'Content-Type': 'application/json',
104+
Accept: 'application/json',
105+
},
106+
body,
107+
}).then((res) => res.json());
108+
109+
if (response.errors && Array.isArray(response.errors)) {
110+
const errorMsg = response.errors.map((e: { msg: string }) => e.msg).join(', ');
111+
throw new Error(`useTurnkeyAuth: Backend Error: ${errorMsg}`);
112+
}
113+
114+
// TODO(turnkey): handle policy returned in response
115+
return response;
116+
},
117+
onError: (error, variables) => {
118+
track(
119+
AnalyticsEvents.UploadAddressError({
120+
dydxAddress: variables.payload.dydxAddress,
121+
error: error.message,
122+
})
123+
);
124+
logBonsaiError('TurnkeyOnboarding', 'Error posting to upload address', { error });
125+
},
126+
onSuccess: () => {
127+
appQueryClient.invalidateQueries({ queryKey: ['turnkeyWallets'] });
128+
},
129+
});
130+
131+
const uploadAddress = useCallback(
132+
async ({
133+
tkClient,
134+
dydxAddress,
135+
}: {
136+
tkClient?: TurnkeyIndexedDbClient;
137+
dydxAddress: string;
138+
}) => {
139+
try {
140+
logBonsaiInfo('TurnkeyOnboarding', 'Attempting to upload address');
141+
142+
if (tkClient == null) {
143+
throw new Error('No tk client provided');
144+
}
145+
146+
const payload = await getUploadAddressPayload({ dydxAddress, tkClient });
147+
await sendUploadAddressRequest({ payload });
148+
} catch (error) {
149+
logBonsaiError('TurnkeyOnboarding', 'Error uploading address', { error });
150+
}
151+
},
152+
[getUploadAddressPayload, sendUploadAddressRequest]
153+
);
154+
94155
/* ----------------------------- Sign In ----------------------------- */
95156

96157
const { mutate: sendSignInRequest, status } = useMutation({
@@ -136,7 +197,6 @@ const useTurnkeyAuthContext = () => {
136197
if (response.dydxAddress === '') {
137198
setIsNewTurnkeyUser(true);
138199
identify(AnalyticsUserProperties.IsNewUser(true));
139-
dispatch(setRequiresAddressUpload(true));
140200
} else {
141201
identify(AnalyticsUserProperties.IsNewUser(false));
142202
}
@@ -239,19 +299,37 @@ const useTurnkeyAuthContext = () => {
239299

240300
const handleOauthResponse = useCallback(
241301
async ({ response }: { response: TurnkeyOAuthResponse }) => {
242-
const { session, salt } = response;
302+
const { session, salt, dydxAddress: uploadedDydxAddress } = response;
243303
if (session == null) {
244304
throw new Error('useTurnkeyAuth: No session found');
245305
} else if (salt == null) {
246306
throw new Error('useTurnkeyAuth: No salt found');
247307
}
248308

249309
await indexedDbClient?.loginWithSession(session);
250-
await onboardDydx({ salt, setWalletFromSignature, tkClient: indexedDbClient });
310+
const derivedDydxAddress = await onboardDydx({
311+
salt,
312+
setWalletFromSignature,
313+
tkClient: indexedDbClient,
314+
});
315+
316+
if (uploadedDydxAddress === '' && derivedDydxAddress) {
317+
try {
318+
await uploadAddress({ tkClient: indexedDbClient, dydxAddress: derivedDydxAddress });
319+
} catch (uploadAddressError) {
320+
if (
321+
uploadAddressError instanceof Error &&
322+
!uploadAddressError.message.includes('Dydx address already uploaded')
323+
) {
324+
throw uploadAddressError;
325+
}
326+
}
327+
}
328+
251329
setEmailSignInStatus('success');
252330
setEmailSignInError(undefined);
253331
},
254-
[onboardDydx, indexedDbClient, setWalletFromSignature]
332+
[onboardDydx, indexedDbClient, setWalletFromSignature, uploadAddress]
255333
);
256334

257335
/* ----------------------------- Email Sign In ----------------------------- */
@@ -302,7 +380,8 @@ const useTurnkeyAuthContext = () => {
302380
throw new Error('No public key found');
303381
}
304382

305-
const { organizationId } = turnkeyEmailOnboardingData ?? {};
383+
const { organizationId, dydxAddress: uploadedDydxAddress } =
384+
turnkeyEmailOnboardingData ?? {};
306385

307386
if (!organizationId) {
308387
throw new Error('Organization ID was not found');
@@ -322,7 +401,24 @@ const useTurnkeyAuthContext = () => {
322401
}
323402

324403
await indexedDbClient.loginWithSession(session);
325-
await onboardDydx({ setWalletFromSignature, tkClient: indexedDbClient });
404+
const derivedDydxAddress = await onboardDydx({
405+
setWalletFromSignature,
406+
tkClient: indexedDbClient,
407+
});
408+
409+
if (derivedDydxAddress && uploadedDydxAddress === '') {
410+
try {
411+
await uploadAddress({ tkClient: indexedDbClient, dydxAddress: derivedDydxAddress });
412+
} catch (uploadAddressError) {
413+
if (
414+
uploadAddressError instanceof Error &&
415+
!uploadAddressError.message.includes('Dydx address already uploaded')
416+
) {
417+
throw uploadAddressError;
418+
}
419+
}
420+
}
421+
326422
setEmailSignInStatus('success');
327423

328424
track(
@@ -386,6 +482,7 @@ const useTurnkeyAuthContext = () => {
386482
setSearchParams,
387483
stringGetter,
388484
endTurnkeySession,
485+
uploadAddress,
389486
]
390487
);
391488

@@ -429,76 +526,6 @@ const useTurnkeyAuthContext = () => {
429526
setEmailSignInError(undefined);
430527
}, [searchParams, setSearchParams]);
431528

432-
/* ----------------------------- Upload Address ----------------------------- */
433-
const { mutateAsync: sendUploadAddressRequest, isPending: isUploadingAddress } = useMutation({
434-
mutationFn: async ({
435-
payload,
436-
}: {
437-
payload: { dydxAddress: string; signature: string };
438-
}): Promise<{ success: boolean }> => {
439-
const body = JSON.stringify(payload);
440-
441-
const response = await fetch(`${indexerUrl}/v4/turnkey/uploadAddress`, {
442-
method: 'POST',
443-
headers: {
444-
'Content-Type': 'application/json',
445-
Accept: 'application/json',
446-
},
447-
body,
448-
}).then((res) => res.json());
449-
450-
if (response.errors && Array.isArray(response.errors)) {
451-
const errorMsg = response.errors.map((e: { msg: string }) => e.msg).join(', ');
452-
throw new Error(`useTurnkeyAuth: Backend Error: ${errorMsg}`);
453-
}
454-
455-
// TODO(turnkey): handle policy returned in response
456-
return response;
457-
},
458-
onError: (error, variables) => {
459-
track(
460-
AnalyticsEvents.UploadAddressError({
461-
dydxAddress: variables.payload.dydxAddress,
462-
error: error.message,
463-
})
464-
);
465-
logBonsaiError('TurnkeyOnboarding', 'Error posting to upload address', { error });
466-
},
467-
onSuccess: () => {
468-
appQueryClient.invalidateQueries({ queryKey: ['turnkeyWallets'] });
469-
},
470-
});
471-
472-
const uploadAddress = useCallback(
473-
async ({ tkClient }: { tkClient?: TurnkeyIndexedDbClient }) => {
474-
try {
475-
logBonsaiInfo('TurnkeyOnboarding', 'Attempting to upload address');
476-
477-
if (dydxAddress == null) {
478-
throw new Error('No dydx address provided');
479-
}
480-
481-
if (tkClient == null) {
482-
throw new Error('No tk client provided');
483-
}
484-
485-
const payload = await getUploadAddressPayload({ dydxAddress, tkClient });
486-
const result = await sendUploadAddressRequest({ payload });
487-
488-
if (!result.success) {
489-
dispatch(setRequiresAddressUpload(true));
490-
}
491-
} catch (error) {
492-
if ((error.message ?? '').includes('Dydx address already uploaded')) {
493-
dispatch(setRequiresAddressUpload(false));
494-
} else {
495-
logBonsaiError('TurnkeyOnboarding', 'Error uploading address', { error });
496-
}
497-
}
498-
},
499-
[dispatch, dydxAddress, getUploadAddressPayload, sendUploadAddressRequest]
500-
);
501-
502529
/* ----------------------------- Side Effects ----------------------------- */
503530

504531
/**
@@ -509,14 +536,21 @@ const useTurnkeyAuthContext = () => {
509536
const turnkeyOnboardingToken = searchParams.get('token');
510537
const hasEncryptedSignature = sourceAccount.encryptedSignature != null;
511538

512-
if (turnkeyOnboardingToken && dydxAddress != null) {
539+
if (turnkeyOnboardingToken && connectedDydxAddress != null) {
513540
searchParams.delete('token');
514541
setSearchParams(searchParams);
515542
} else if (turnkeyOnboardingToken && !hasEncryptedSignature) {
516543
setEmailToken(turnkeyOnboardingToken);
517544
dispatch(openDialog(DialogTypes.EmailSignInStatus({})));
518545
}
519-
}, [searchParams, dispatch, sourceAccount, dydxAddress, resetEmailSignInStatus, setSearchParams]);
546+
}, [
547+
searchParams,
548+
dispatch,
549+
sourceAccount,
550+
connectedDydxAddress,
551+
resetEmailSignInStatus,
552+
setSearchParams,
553+
]);
520554

521555
/**
522556
* @description Side effect triggered after the email token is saved to state.
@@ -548,19 +582,6 @@ const useTurnkeyAuthContext = () => {
548582
);
549583
}, [sourceAccount.walletInfo]);
550584

551-
/**
552-
* @description Side effect to upload the address if it is required and the user has a dydx address.
553-
* This is triggered after the user has signed in with email or OAuth and has derived their dydx address.
554-
*/
555-
useEffect(() => {
556-
if (indexedDbClient && needsAddressUpload && dydxAddress != null) {
557-
// Set RequiredAddressUpload to false to prevent the upload from being triggered again.
558-
// If the uploadAddress mutation fails, the requiredAddressUpload flag will be set back to true.
559-
dispatch(setRequiresAddressUpload(false));
560-
uploadAddress({ tkClient: indexedDbClient });
561-
}
562-
}, [dispatch, dydxAddress, uploadAddress, indexedDbClient, needsAddressUpload]);
563-
564585
/* ----------------------------- Return Values----------------------------- */
565586

566587
return {

src/providers/TurnkeyWalletProvider.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ const useTurnkeyWalletContext = () => {
192192
tkClient,
193193
}: {
194194
salt?: string;
195-
setWalletFromSignature: (signature: string) => Promise<void>;
195+
setWalletFromSignature: (signature: string) => Promise<string | undefined>;
196196
tkClient?: TurnkeyIndexedDbClient;
197197
}) => {
198198
const selectedTurnkeyWallet = primaryTurnkeyWallet ?? (await getPrimaryUserWallets(tkClient));
@@ -231,7 +231,8 @@ const useTurnkeyWalletContext = () => {
231231
dispatch(setSavedEncryptedSignature(encryptedSignature));
232232
}
233233

234-
await setWalletFromSignature(signature);
234+
const dydxAddress = await setWalletFromSignature(signature);
235+
return dydxAddress;
235236
},
236237
[
237238
dispatch,

src/state/wallet.ts

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { logBonsaiError } from '@/bonsai/logs';
21
import type { PayloadAction } from '@reduxjs/toolkit';
32
import { createSlice } from '@reduxjs/toolkit';
43

5-
import { ConnectorType, WalletInfo, WalletNetworkType } from '@/constants/wallets';
4+
import { WalletInfo, WalletNetworkType } from '@/constants/wallets';
65
import { TurnkeyEmailOnboardingData, TurnkeyWallet } from '@/types/turnkey';
76

87
export type SourceAccount = {
@@ -69,19 +68,6 @@ export const walletSlice = createSlice({
6968

7069
state.sourceAccount.encryptedSignature = action.payload;
7170
},
72-
setRequiresAddressUpload: (state, action: PayloadAction<boolean>) => {
73-
if (state.sourceAccount.walletInfo?.connectorType === ConnectorType.Turnkey) {
74-
state.sourceAccount.walletInfo.requiresAddressUpload = action.payload;
75-
} else {
76-
logBonsaiError(
77-
'WalletState',
78-
'Attempting to set a Turnkey specific property on a non-turnkey wallet',
79-
{
80-
walletInfo: state.sourceAccount.walletInfo,
81-
}
82-
);
83-
}
84-
},
8571
clearSavedEncryptedSignature: (state) => {
8672
state.sourceAccount.encryptedSignature = undefined;
8773
},
@@ -140,7 +126,6 @@ export const walletEphemeralSlice = createSlice({
140126

141127
export const {
142128
setSourceAddress,
143-
setRequiresAddressUpload,
144129
setWalletInfo,
145130
setSavedEncryptedSignature,
146131
clearSavedEncryptedSignature,

0 commit comments

Comments
 (0)