Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/components/Claim/Link/Onchain/Success.view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -45,11 +45,11 @@ export const SuccessClaimLinkView = ({ transactionHash, claimLinkData, type }: _
}

useEffect(() => {
resetTokenContextProvider()
if (!isPeanutWallet) resetTokenContextProvider()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

if (transactionHash && type === 'claimxchain') {
fetchDestinationChain(transactionHash, setExplorerUrlDestChainWithTxHash)
}
}, [])
}, [isPeanutWallet, transactionHash, type])

useEffect(() => {
if (isw3mEmailWallet && isConnected) {
Expand Down
10 changes: 6 additions & 4 deletions src/components/Create/Create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const Create = () => {
}[]
>([])

const { address } = useWallet()
const { address, isPeanutWallet } = useWallet()

const { resetTokenContextProvider } = useContext(context.tokenSelectorContext)

Expand Down Expand Up @@ -105,9 +105,11 @@ export const Create = () => {
}

useEffect(() => {
resetTokenContextProvider()
fetchAndSetCrossChainDetails()
}, [])
if (!isPeanutWallet) {
resetTokenContextProvider()
fetchAndSetCrossChainDetails()
}
}, [isPeanutWallet])

useEffect(() => {
if (address) {
Expand Down
3 changes: 0 additions & 3 deletions src/components/Create/Link/Input.view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
9 changes: 6 additions & 3 deletions src/context/contextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<ToastProvider>
<AuthProvider>
<PushProvider>
<TokenContextProvider>
<LoadingStateContextProvider>{children}</LoadingStateContextProvider>
</TokenContextProvider>
<KernelClientProvider>
<TokenContextProvider>
<LoadingStateContextProvider>{children}</LoadingStateContextProvider>
</TokenContextProvider>
</KernelClientProvider>
</PushProvider>
</AuthProvider>
</ToastProvider>
Comment on lines 10 to 20
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

T: this is becoming a pyramid

Expand Down
144 changes: 144 additions & 0 deletions src/context/kernelClient.context.tsx
Original file line number Diff line number Diff line change
@@ -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<Transport, typeof consts.PEANUT_WALLET_CHAIN>

type WebAuthnKey = Awaited<ReturnType<typeof toWebAuthnKey>>

const LOCAL_STORAGE_WEB_AUTHN_KEY = 'web-authn-key'

const KernelClientContext = createContext<KernelClientContextType | undefined>(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<AppSmartAccountClient>()
const [webAuthnKey, setWebAuthnKey] = useState<WebAuthnKey | undefined>(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 (
<KernelClientContext.Provider
value={{
kernelClient,
setWebAuthnKey,
}}
>
{children}
</KernelClientContext.Provider>
)
}

export const useKernelClient = (): KernelClientContextType => {
const context = useContext(KernelClientContext)
if (context === undefined) {
throw new Error('useKernelClient must be used within a KernelClientProvider')
}
return context
}
139 changes: 7 additions & 132 deletions src/hooks/useZeroDev.ts
Original file line number Diff line number Diff line change
@@ -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<Transport, typeof consts.PEANUT_WALLET_CHAIN>
type UserOpNotEncodedParams = {
to: Address
value: number
Expand All @@ -37,128 +24,16 @@ type UserOpEncodedParams = {
data?: Hex | undefined
}

type WebAuthnKey = Awaited<ReturnType<typeof toWebAuthnKey>>

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<AppSmartAccountClient | undefined>(undefined)
const [webAuthnKey, setWebAuthnKey] = useState<WebAuthnKey | undefined>(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<void> => {
dispatch(zerodevActions.setIsRegistering(true))
Expand Down
Loading