Skip to content

Commit

Permalink
Add social validator hook (#8)
Browse files Browse the repository at this point in the history
* Add social validator hook

* update social hook

* lint fixes

* fix pending state

* update packages

* Clean up hooks for rendering issues

* chore: Update package version
  • Loading branch information
blakecduncan authored Apr 30, 2024
1 parent a96f7c4 commit 7fd0b64
Show file tree
Hide file tree
Showing 9 changed files with 291 additions and 7 deletions.
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"editor.tabSize": 4,
"editor.insertSpaces": true
}
Binary file modified bun.lockb
Binary file not shown.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zerodev/waas",
"version": "0.1.5-alpha.0",
"version": "0.1.5",
"description": "",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand All @@ -23,6 +23,7 @@
"@zerodev/permissions": "^5.2.2",
"@zerodev/sdk": "^5.2.4",
"@zerodev/session-key": "^5.2.2",
"@zerodev/social-validator": "5.0.1",
"events": "^3.3.0",
"lodash": "^4.17.21"
},
Expand All @@ -36,7 +37,8 @@
"react-dom": ">=18",
"simple-git-hooks": "^2.11.1",
"tsup": "^8.0.2",
"typescript": "^5.4.3"
"typescript": "^5.4.3",
"viem": "2.9.18"
},
"peerDependencies": {
"@tanstack/react-query": "^5.28.14",
Expand Down
7 changes: 7 additions & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,10 @@ export {
type UseBalanceReturnType,
type GetBalanceReturnType
} from "./useBalance"

export {
useCreateKernelClientSocial,
type UseCreateKernelClientSocialParameters,
type UseCreateKernelClientSocialReturnType,
type CreateKernelClientSocialReturnType
} from "./useCreateKernelClientSocial"
165 changes: 165 additions & 0 deletions src/hooks/useCreateKernelClientSocial.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { type UseMutationResult, useMutation } from "@tanstack/react-query"
import {
type KernelSmartAccount,
type KernelValidator,
createKernelAccount
} from "@zerodev/sdk"
import {
getSocialValidator,
initiateLogin,
isAuthorized
} from "@zerodev/social-validator"
import type { EntryPoint } from "permissionless/types"
import { useCallback, useEffect, useState } from "react"
import type { PublicClient } from "viem"
import { useSocial } from "../providers/SocialContext"
import { useZeroDevConfig } from "../providers/ZeroDevAppContext"
import { useSetKernelAccount } from "../providers/ZeroDevValidatorContext"
import type { KernelVersionType } from "../types"
import { getEntryPointFromVersion } from "../utils/entryPoint"

export type UseCreateKernelClientSocialParameters = {
version: KernelVersionType
oauthCallbackUrl?: string
}

export type UseCreateKernelClientSocialKey = {
version: KernelVersionType
oauthCallbackUrl?: string
publicClient?: PublicClient
appId?: string
type?: "getSocialValidator"
}

export type CreateKernelClientSocialReturnType = {
validator: KernelValidator<EntryPoint>
kernelAccount: KernelSmartAccount<EntryPoint>
entryPoint: EntryPoint
}

export type UseCreateKernelClientSocialReturnType = {
login: (socialProvider: "google" | "facebook") => void
} & Omit<
UseMutationResult<
CreateKernelClientSocialReturnType,
unknown,
UseCreateKernelClientSocialKey,
unknown
>,
"mutate"
>

function mutationKey(config: UseCreateKernelClientSocialKey) {
const { oauthCallbackUrl, publicClient, appId } = config

return [
{
entity: "CreateKernelClient",
oauthCallbackUrl,
publicClient,
appId
}
] as const
}

async function mutationFn(
config: UseCreateKernelClientSocialKey
): Promise<CreateKernelClientSocialReturnType> {
const { publicClient, appId, version, type } = config

if (!appId || !(await isAuthorized({ projectId: appId }))) {
throw new Error("Not authorized")
}

if (!publicClient || !appId) {
throw new Error("missing publicClient or appId")
}
let socialValidator: KernelValidator<EntryPoint>
const entryPoint = getEntryPointFromVersion(version)

if (type === "getSocialValidator") {
socialValidator = await getSocialValidator(publicClient, {
entryPoint,
projectId: appId
})
} else {
throw new Error("invalid type")
}

const kernelAccount = await createKernelAccount(publicClient, {
entryPoint: entryPoint,
plugins: {
sudo: socialValidator
}
})

return { validator: socialValidator, kernelAccount, entryPoint }
}

export function useCreateKernelClientSocial({
version,
oauthCallbackUrl
}: UseCreateKernelClientSocialParameters): UseCreateKernelClientSocialReturnType {
const {
setValidator,
setKernelAccount,
setEntryPoint,
setKernelAccountClient
} = useSetKernelAccount()
const { appId, client } = useZeroDevConfig()
const {
setIsSocialPending,
isSocialPending,
login: loginSocial
} = useSocial()

const { data, mutate, ...result } = useMutation({
mutationKey: mutationKey({
appId: appId ?? undefined,
publicClient: client ?? undefined,
type: undefined,
version,
oauthCallbackUrl
}),
mutationFn,
onSuccess: (data) => {
setValidator(data.validator)
setKernelAccount(data.kernelAccount)
setEntryPoint(data.entryPoint)
setKernelAccountClient(null)
},
onMutate() {
setIsSocialPending(true)
},
onSettled() {
setIsSocialPending(false)
}
})

const login = useCallback(
(socialProvider: "google" | "facebook") => {
loginSocial(socialProvider, oauthCallbackUrl)
},
[oauthCallbackUrl, loginSocial]
)

useEffect(() => {
const load = async () => {
mutate({
appId: appId ?? undefined,
publicClient: client ?? undefined,
version,
type: "getSocialValidator",
oauthCallbackUrl
})
}
load()
}, [appId, mutate, client, version, oauthCallbackUrl])

return {
...result,
data,
isPending: isSocialPending,
login
}
}
16 changes: 12 additions & 4 deletions src/hooks/useDisconnectKernelClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { EntryPoint } from "permissionless/types"
import { useMemo } from "react"
import type { Chain, Transport } from "viem"
import { useSetKernelAccount } from "../providers/ZeroDevValidatorContext"
import { useDisconnectSocial } from "./useDisconnectSocial"

export type UseDisconnectKernelClientKey = {
setValidator:
Expand All @@ -30,6 +31,7 @@ export type UseDisconnectKernelClientKey = {
) => void)
| null
| undefined
logoutSocial: (() => void) | null | undefined
}

export type DisconnectKernelClientReturnType = boolean
Expand Down Expand Up @@ -72,7 +74,8 @@ async function mutationFn(
setValidator,
setKernelAccount,
setEntryPoint,
setKernelAccountClient
setKernelAccountClient,
logoutSocial
} = config

if (
Expand All @@ -84,6 +87,7 @@ async function mutationFn(
throw new Error("setKernelAccountClient is required")
}

await logoutSocial?.()
setValidator(null)
setKernelAccount(null)
setEntryPoint(null)
Expand All @@ -99,13 +103,15 @@ export function useDisconnectKernelClient(): UseDisconnectKernelClientReturnType
setEntryPoint,
setKernelAccountClient
} = useSetKernelAccount()
const { logoutSocial } = useDisconnectSocial()

const { mutate, ...result } = useMutation({
mutationKey: mutationKey({
setValidator: undefined,
setKernelAccount: undefined,
setEntryPoint: undefined,
setKernelAccountClient: undefined
setKernelAccountClient: undefined,
logoutSocial: undefined
}),
mutationFn
})
Expand All @@ -116,14 +122,16 @@ export function useDisconnectKernelClient(): UseDisconnectKernelClientReturnType
setValidator,
setKernelAccount,
setEntryPoint,
setKernelAccountClient
setKernelAccountClient,
logoutSocial
})
}, [
mutate,
setValidator,
setKernelAccount,
setEntryPoint,
setKernelAccountClient
setKernelAccountClient,
logoutSocial
])

return {
Expand Down
19 changes: 19 additions & 0 deletions src/hooks/useDisconnectSocial.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { logout } from "@zerodev/social-validator"
import { useMemo } from "react"
import { useZeroDevConfig } from "../providers/ZeroDevAppContext"
import { useKernelAccount } from "../providers/ZeroDevValidatorContext"

export function useDisconnectSocial() {
const { validator } = useKernelAccount()
const { appId } = useZeroDevConfig()

const logoutSocial = useMemo(() => {
return async () => {
if (validator?.source === "SocialValidator" && appId) {
await logout({ projectId: appId })
}
}
}, [validator, appId])

return { logoutSocial }
}
78 changes: 78 additions & 0 deletions src/providers/SocialContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {
getSocialValidator,
initiateLogin,
isAuthorized
} from "@zerodev/social-validator"
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState
} from "react"
import { useCreateKernelClientSocial } from "../hooks/useCreateKernelClientSocial"
import { useZeroDevConfig } from "./ZeroDevAppContext"

interface SocialContextValue {
isSocialPending: boolean
setIsSocialPending: (isPending: boolean) => void
login: (
socialProvider: "google" | "facebook",
oauthCallbackUrl?: string
) => void
}

interface SocialProviderProps {
children: React.ReactNode
}

export const SocialContext = createContext<SocialContextValue>({
isSocialPending: false,
setIsSocialPending: () => {},
login: () => {}
})

export function SocialProvider({ children }: SocialProviderProps) {
const { appId } = useZeroDevConfig()
const [isSocialPending, setIsSocialPending] = useState(false)

const login = useCallback(
(socialProvider: "google" | "facebook", oauthCallbackUrl?: string) => {
if (!appId) {
throw new Error("missing appId")
}
initiateLogin({
socialProvider,
oauthCallbackUrl,
projectId: appId
})
},
[appId]
)

return (
<SocialContext.Provider
value={useMemo(
() => ({
isSocialPending,
setIsSocialPending,
login
}),
[isSocialPending, login]
)}
>
{children}
</SocialContext.Provider>
)
}

export function useSocial() {
const context = useContext(SocialContext)

if (context === undefined) {
throw new Error("useSocial must be used within a SocialProvider")
}

return context
}
3 changes: 2 additions & 1 deletion src/providers/ZeroDevProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import type { ReactNode } from "react"
import type { Chain } from "wagmi/chains"
import { SessionProvider } from "./SessionContext"
import { SocialProvider } from "./SocialContext"
import { WalletConnectProvider } from "./WalletConnectProvider"
import { ZeroDevAppProvider } from "./ZeroDevAppContext"
import { ZeroDevValidatorProvider } from "./ZeroDevValidatorContext"
Expand All @@ -25,7 +26,7 @@ export function ZeroDevProvider({
<ZeroDevValidatorProvider>
<SessionProvider>
<WalletConnectProvider>
{children}
<SocialProvider>{children}</SocialProvider>
</WalletConnectProvider>
</SessionProvider>
</ZeroDevValidatorProvider>
Expand Down

0 comments on commit 7fd0b64

Please sign in to comment.