From f1e456d43e0a2ed4619ac9155661d25f13ea8dec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Ram=C3=ADrez?= Date: Thu, 16 Jan 2025 09:19:33 -0300 Subject: [PATCH 1/3] refactor: move kernelClient to context The client needs to be part of a client state, right now as a hook local state, it was being recreated innecesarily and also could not be shared between components. Now the simple state is in redux and this client is part of a context provider. --- src/components/Create/Link/Input.view.tsx | 3 - src/context/contextProvider.tsx | 9 +- src/context/kernelClient.context.tsx | 144 ++++++++++++++++++++++ src/hooks/useZeroDev.ts | 139 ++------------------- 4 files changed, 157 insertions(+), 138 deletions(-) create mode 100644 src/context/kernelClient.context.tsx diff --git a/src/components/Create/Link/Input.view.tsx b/src/components/Create/Link/Input.view.tsx index 1e8a2d760..d4f765ceb 100644 --- a/src/components/Create/Link/Input.view.tsx +++ b/src/components/Create/Link/Input.view.tsx @@ -80,9 +80,6 @@ export const CreateLinkInputView = ({ const { open } = useAppKit() - const handleConnectWallet = async () => { - open() - } const { selectedWallet, signInModal, isConnected, address, isExternalWallet, isPeanutWallet } = useWallet() const handleOnNext = async () => { diff --git a/src/context/contextProvider.tsx b/src/context/contextProvider.tsx index f3de0dc95..d695cf200 100644 --- a/src/context/contextProvider.tsx +++ b/src/context/contextProvider.tsx @@ -3,15 +3,18 @@ import { AuthProvider } from './authContext' import { LoadingStateContextProvider } from './loadingStates.context' import { PushProvider } from './pushProvider' import { TokenContextProvider } from './tokenSelector.context' +import { KernelClientProvider } from './kernelClient.context' export const ContextProvider = ({ children }: { children: React.ReactNode }) => { return ( - - {children} - + + + {children} + + diff --git a/src/context/kernelClient.context.tsx b/src/context/kernelClient.context.tsx new file mode 100644 index 000000000..274c4589e --- /dev/null +++ b/src/context/kernelClient.context.tsx @@ -0,0 +1,144 @@ +'use client' + +import { peanutPublicClient } from '@/constants/viem.consts' +import * as consts from '@/constants/zerodev.consts' +import { useAuth } from '@/context/authContext' +import { useAppDispatch } from '@/redux/hooks' +import { zerodevActions } from '@/redux/slices/zerodev-slice' +import { getFromLocalStorage } from '@/utils' +import { PasskeyValidatorContractVersion, toPasskeyValidator, toWebAuthnKey } from '@zerodev/passkey-validator' +import { + createKernelAccount, + createKernelAccountClient, + createZeroDevPaymasterClient, + KernelAccountClient, +} from '@zerodev/sdk' +import { KERNEL_V3_1 } from '@zerodev/sdk/constants' +import { createContext, useEffect, useState, useContext, ReactNode } from 'react' +import { http, Transport } from 'viem' + +interface KernelClientContextType { + kernelClient: AppSmartAccountClient | undefined + setWebAuthnKey: (webAuthnKey: WebAuthnKey) => void +} + +// types +type AppSmartAccountClient = KernelAccountClient + +type WebAuthnKey = Awaited> + +const LOCAL_STORAGE_WEB_AUTHN_KEY = 'web-authn-key' + +const KernelClientContext = createContext(undefined) + +const createKernelClient = async (passkeyValidator: any) => { + console.log('Creating new kernel client...') + const kernelAccount = await createKernelAccount(peanutPublicClient, { + plugins: { + sudo: passkeyValidator, + }, + entryPoint: consts.USER_OP_ENTRY_POINT, + kernelVersion: KERNEL_V3_1, + }) + + const kernelClient = createKernelAccountClient({ + account: kernelAccount, + chain: consts.PEANUT_WALLET_CHAIN, + bundlerTransport: http(consts.BUNDLER_URL), + paymaster: { + getPaymasterData: async (userOperation) => { + const zerodevPaymaster = createZeroDevPaymasterClient({ + chain: consts.PEANUT_WALLET_CHAIN, + transport: http(consts.PAYMASTER_URL), + }) + + try { + return await zerodevPaymaster.sponsorUserOperation({ + userOperation, + shouldOverrideFee: true, + }) + } catch (error) { + console.error('Paymaster error:', error) + throw error + } + }, + }, + }) + + return kernelClient +} + +export const KernelClientProvider = ({ children }: { children: ReactNode }) => { + const [kernelClient, setKernelClient] = useState() + const [webAuthnKey, setWebAuthnKey] = useState(undefined) + const dispatch = useAppDispatch() + const { fetchUser } = useAuth() + + // lifecycle hooks + useEffect(() => { + const storedWebAuthnKey = getFromLocalStorage(LOCAL_STORAGE_WEB_AUTHN_KEY) + if (storedWebAuthnKey) { + setWebAuthnKey(storedWebAuthnKey) + } + }, []) + + useEffect(() => { + let isMounted = true + + if (!webAuthnKey) { + return () => { + isMounted = false + } + } + + const initializeClient = async () => { + try { + const validator = await toPasskeyValidator(peanutPublicClient, { + webAuthnKey, + entryPoint: consts.USER_OP_ENTRY_POINT, + kernelVersion: KERNEL_V3_1, + validatorContractVersion: PasskeyValidatorContractVersion.V0_0_2, + }) + + const client = await createKernelClient(validator) + + if (isMounted) { + fetchUser() + setKernelClient(client) + dispatch(zerodevActions.setAddress(client.account!.address)) + dispatch(zerodevActions.setIsKernelClientReady(true)) + dispatch(zerodevActions.setIsRegistering(false)) + dispatch(zerodevActions.setIsLoggingIn(false)) + } + } catch (error) { + console.error('Error initializing kernel client:', error) + dispatch(zerodevActions.setIsKernelClientReady(false)) + } + } + + initializeClient() + + return () => { + isMounted = false + } + }, [webAuthnKey]) + + return ( + + {children} + + ) +} + +export const useKernelClient = (): KernelClientContextType => { + const context = useContext(KernelClientContext) + if (context === undefined) { + throw new Error('useAuth must be used within an AuthProvider') + } + return context +} diff --git a/src/hooks/useZeroDev.ts b/src/hooks/useZeroDev.ts index 33ee8a8e1..49e56c90d 100644 --- a/src/hooks/useZeroDev.ts +++ b/src/hooks/useZeroDev.ts @@ -1,29 +1,16 @@ 'use client' -import { peanutPublicClient } from '@/constants/viem.consts' import * as consts from '@/constants/zerodev.consts' import { useAuth } from '@/context/authContext' +import { useKernelClient } from '@/context/kernelClient.context' import { useAppDispatch, useZerodevStore } from '@/redux/hooks' import { zerodevActions } from '@/redux/slices/zerodev-slice' -import { getFromLocalStorage, saveToLocalStorage } from '@/utils' -import { - PasskeyValidatorContractVersion, - toPasskeyValidator, - toWebAuthnKey, - WebAuthnMode, -} from '@zerodev/passkey-validator' -import { - createKernelAccount, - createKernelAccountClient, - createZeroDevPaymasterClient, - KernelAccountClient, -} from '@zerodev/sdk' -import { KERNEL_V3_1 } from '@zerodev/sdk/constants' -import { useCallback, useEffect, useState } from 'react' -import { Abi, Address, encodeFunctionData, Hex, http, Transport } from 'viem' +import { saveToLocalStorage } from '@/utils' +import { toWebAuthnKey, WebAuthnMode } from '@zerodev/passkey-validator' +import { useCallback } from 'react' +import { Abi, Address, encodeFunctionData, Hex } from 'viem' // types -type AppSmartAccountClient = KernelAccountClient type UserOpNotEncodedParams = { to: Address value: number @@ -37,128 +24,16 @@ type UserOpEncodedParams = { data?: Hex | undefined } -type WebAuthnKey = Awaited> - -const LOCAL_STORAGE_KERNEL_ADDRESS = 'kernel-address' const LOCAL_STORAGE_WEB_AUTHN_KEY = 'web-authn-key' export const useZeroDev = () => { const dispatch = useAppDispatch() - const { fetchUser, user } = useAuth() + const { user } = useAuth() const { isKernelClientReady, isRegistering, isLoggingIn, isSendingUserOp, address } = useZerodevStore() - - // local states for non-UI state - const [kernelClient, setKernelClient] = useState(undefined) - const [webAuthnKey, setWebAuthnKey] = useState(undefined) + const { kernelClient, setWebAuthnKey } = useKernelClient() const _getPasskeyName = (handle: string) => `${handle}.peanut.wallet` - // setup function - const createKernelClient = useCallback( - async (passkeyValidator: any) => { - // check if kernel client with the same address already exists - const storedAddress = getFromLocalStorage(LOCAL_STORAGE_KERNEL_ADDRESS) - if (kernelClient && storedAddress === kernelClient.account?.address) { - return kernelClient - } - - console.log('Creating new kernel client...') - const kernelAccount = await createKernelAccount(peanutPublicClient, { - plugins: { - sudo: passkeyValidator, - }, - entryPoint: consts.USER_OP_ENTRY_POINT, - kernelVersion: KERNEL_V3_1, - }) - - const newKernelClient = createKernelAccountClient({ - account: kernelAccount, - chain: consts.PEANUT_WALLET_CHAIN, - bundlerTransport: http(consts.BUNDLER_URL), - paymaster: { - getPaymasterData: async (userOperation) => { - const zerodevPaymaster = createZeroDevPaymasterClient({ - chain: consts.PEANUT_WALLET_CHAIN, - transport: http(consts.PAYMASTER_URL), - }) - - try { - return await zerodevPaymaster.sponsorUserOperation({ - userOperation, - shouldOverrideFee: true, - }) - } catch (error) { - console.error('Paymaster error:', error) - throw error - } - }, - }, - }) - - // store address for future reference - saveToLocalStorage(LOCAL_STORAGE_KERNEL_ADDRESS, newKernelClient.account?.address) - return newKernelClient - }, - [kernelClient] - ) - - // lifecycle hooks - useEffect(() => { - const storedWebAuthnKey = getFromLocalStorage(LOCAL_STORAGE_WEB_AUTHN_KEY) - if (storedWebAuthnKey) { - setWebAuthnKey(storedWebAuthnKey) - } - }, []) - - useEffect(() => { - let isMounted = true - - if (!webAuthnKey) { - return () => { - isMounted = false - } - } - - // check if valid kernel client already exists - if (kernelClient && kernelClient.account?.address) { - dispatch(zerodevActions.setIsKernelClientReady(true)) - dispatch(zerodevActions.setIsRegistering(false)) - dispatch(zerodevActions.setIsLoggingIn(false)) - return - } - - const initializeClient = async () => { - try { - const validator = await toPasskeyValidator(peanutPublicClient, { - webAuthnKey, - entryPoint: consts.USER_OP_ENTRY_POINT, - kernelVersion: KERNEL_V3_1, - validatorContractVersion: PasskeyValidatorContractVersion.V0_0_2, - }) - - const client = await createKernelClient(validator) - - if (isMounted) { - fetchUser() - setKernelClient(client) - dispatch(zerodevActions.setAddress(client.account!.address)) - dispatch(zerodevActions.setIsKernelClientReady(true)) - dispatch(zerodevActions.setIsRegistering(false)) - dispatch(zerodevActions.setIsLoggingIn(false)) - } - } catch (error) { - console.error('Error initializing kernel client:', error) - dispatch(zerodevActions.setIsKernelClientReady(false)) - } - } - - initializeClient() - - return () => { - isMounted = false - } - }, [webAuthnKey, dispatch, fetchUser, createKernelClient, kernelClient]) - // register function const handleRegister = async (handle: string): Promise => { dispatch(zerodevActions.setIsRegistering(true)) From ce4992bbb6f38e5fff917f70591958b9d6a57c77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Ram=C3=ADrez?= Date: Thu, 16 Jan 2025 09:54:25 -0300 Subject: [PATCH 2/3] fix: dont reset token context provider on peanut wallets --- src/components/Claim/Link/Onchain/Success.view.tsx | 6 +++--- src/components/Create/Create.tsx | 10 ++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/components/Claim/Link/Onchain/Success.view.tsx b/src/components/Claim/Link/Onchain/Success.view.tsx index 7c8680aeb..83c680b08 100644 --- a/src/components/Claim/Link/Onchain/Success.view.tsx +++ b/src/components/Claim/Link/Onchain/Success.view.tsx @@ -11,7 +11,7 @@ import * as _consts from '../../Claim.consts' export const SuccessClaimLinkView = ({ transactionHash, claimLinkData, type }: _consts.IClaimScreenProps) => { const connections = useConnections() - const { isConnected, address, chain: currentChain } = useWallet() + const { isConnected, address, chain: currentChain, isPeanutWallet } = useWallet() const { switchChainAsync } = useSwitchChain() const { resetTokenContextProvider, selectedChainID } = useContext(context.tokenSelectorContext) @@ -45,11 +45,11 @@ export const SuccessClaimLinkView = ({ transactionHash, claimLinkData, type }: _ } useEffect(() => { - resetTokenContextProvider() + if (!isPeanutWallet) resetTokenContextProvider() if (transactionHash && type === 'claimxchain') { fetchDestinationChain(transactionHash, setExplorerUrlDestChainWithTxHash) } - }, []) + }, [isPeanutWallet, transactionHash, type]) useEffect(() => { if (isw3mEmailWallet && isConnected) { diff --git a/src/components/Create/Create.tsx b/src/components/Create/Create.tsx index cc80508c2..8b03c7726 100644 --- a/src/components/Create/Create.tsx +++ b/src/components/Create/Create.tsx @@ -53,7 +53,7 @@ export const Create = () => { }[] >([]) - const { address } = useWallet() + const { address, isPeanutWallet } = useWallet() const { resetTokenContextProvider } = useContext(context.tokenSelectorContext) @@ -105,9 +105,11 @@ export const Create = () => { } useEffect(() => { - resetTokenContextProvider() - fetchAndSetCrossChainDetails() - }, []) + if (!isPeanutWallet) { + resetTokenContextProvider() + fetchAndSetCrossChainDetails() + } + }, [isPeanutWallet]) useEffect(() => { if (address) { From b1a6e36541193d53055230a7034193506bb5b37c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Ram=C3=ADrez?= <70615692+jjramirezn@users.noreply.github.com> Date: Thu, 16 Jan 2025 10:12:20 -0300 Subject: [PATCH 3/3] fix: correct error message in kernel client provider Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/context/kernelClient.context.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/context/kernelClient.context.tsx b/src/context/kernelClient.context.tsx index 274c4589e..b38d239c8 100644 --- a/src/context/kernelClient.context.tsx +++ b/src/context/kernelClient.context.tsx @@ -138,7 +138,7 @@ export const KernelClientProvider = ({ children }: { children: ReactNode }) => { export const useKernelClient = (): KernelClientContextType => { const context = useContext(KernelClientContext) if (context === undefined) { - throw new Error('useAuth must be used within an AuthProvider') + throw new Error('useKernelClient must be used within a KernelClientProvider') } return context }