Skip to content

Latest commit

 

History

History
324 lines (250 loc) · 7.93 KB

File metadata and controls

324 lines (250 loc) · 7.93 KB

@zerodev/wallet-react

React hooks and Wagmi connector for ZeroDev Wallet SDK with EIP-7702 gasless transactions.

Features

  • Wagmi Integration - Works seamlessly with the Wagmi ecosystem
  • Multiple Auth Methods - Passkey (WebAuthn), Email OTP, OAuth (Google)
  • Gasless Transactions - All transactions are gasless via EIP-7702
  • Session Management - Auto-refresh with configurable thresholds
  • Multi-Chain - Lazy kernel account creation per chain
  • TypeScript - Full type safety with proper generics
  • React Query - Built on TanStack Query for optimal UX

Installation

npm install @zerodev/wallet-react @zerodev/wallet-core wagmi viem
# or
yarn add @zerodev/wallet-react @zerodev/wallet-core wagmi viem
# or
pnpm add @zerodev/wallet-react @zerodev/wallet-core wagmi viem

Quick Start

1. Configure Wagmi

import { createConfig, http } from 'wagmi'
import { sepolia } from 'wagmi/chains'
import { zeroDevWallet } from '@zerodev/wallet-react'

const config = createConfig({
  chains: [sepolia],
  connectors: [
    zeroDevWallet({
      projectId: 'YOUR_PROJECT_ID',
      chains: [sepolia],
    })
  ],
  transports: {
    [sepolia.id]: http('YOUR_RPC_URL'),
  },
})

2. Wrap Your App

import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

const queryClient = new QueryClient()

function App() {
  return (
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
        <YourApp />
      </QueryClientProvider>
    </WagmiProvider>
  )
}

3. Authenticate Users

import { useRegisterPasskey, useAuthenticateOAuth, OAUTH_PROVIDERS } from '@zerodev/wallet-react'

function LoginPage() {
  const registerPasskey = useRegisterPasskey()
  const authenticateOAuth = useAuthenticateOAuth()

  return (
    <>
      <button
        onClick={() => registerPasskey.mutate()}
        disabled={registerPasskey.isPending}
      >
        {registerPasskey.isPending ? 'Registering...' : 'Register with Passkey'}
      </button>

      <button
        onClick={() => authenticateOAuth.mutate({ provider: OAUTH_PROVIDERS.GOOGLE })}
        disabled={authenticateOAuth.isPending}
      >
        {authenticateOAuth.isPending ? 'Authenticating...' : 'Login with Google'}
      </button>
    </>
  )
}

4. Use Standard Wagmi Hooks

import { useConnection, useSendTransaction, useDisconnect } from 'wagmi'
import { parseEther } from 'viem'

function Dashboard() {
  const { address } = useConnection()
  const { sendTransaction } = useSendTransaction()
  const { disconnect } = useDisconnect()

  return (
    <>
      <p>Address: {address}</p>

      <button onClick={() =>
        sendTransaction({
          to: '0x...',
          value: parseEther('0.01')
        })
      }>
        Send Gasless Transaction
      </button>

      <button onClick={() => disconnect()}>
        Logout
      </button>
    </>
  )
}

Authentication Methods

Passkey (WebAuthn)

const registerPasskey = useRegisterPasskey()
const loginPasskey = useLoginPasskey()

// Register new passkey
await registerPasskey.mutateAsync()

// Login with existing passkey
await loginPasskey.mutateAsync()

OAuth (Google)

const authenticateOAuth = useAuthenticateOAuth()

// Opens popup, backend handles PKCE and token exchange
// No callback page or OAuth library needed - SDK handles everything
await authenticateOAuth.mutateAsync({
  provider: OAUTH_PROVIDERS.GOOGLE
})

Email Magic Link

const sendMagicLink = useSendMagicLink()
const verifyMagicLink = useVerifyMagicLink()

// Send magic link
const { otpId } = await sendMagicLink.mutateAsync({
  email: 'user@example.com',
  redirectURL: 'https://yourapp.com/verify',
})

// With custom OTP code settings
const { otpId } = await sendMagicLink.mutateAsync({
  email: 'user@example.com',
  redirectURL: 'https://yourapp.com/verify',
  otpCodeCustomization: { length: 8, alphanumeric: false },
})

// Verify (on /verify page, extract code from URL)
const code = new URLSearchParams(window.location.search).get('code')
await verifyMagicLink.mutateAsync({ otpId, code })

Email OTP

const sendOTP = useSendOTP()
const verifyOTP = useVerifyOTP()

// Send OTP code
const { otpId } = await sendOTP.mutateAsync({
  email: 'user@example.com'
})

// With custom OTP code settings
const { otpId } = await sendOTP.mutateAsync({
  email: 'user@example.com',
  otpCodeCustomization: { length: 8, alphanumeric: false },
})

// Verify OTP code
await verifyOTP.mutateAsync({
  code: '12345678',
  otpId,
})

OTP Code Customization

Both useSendOTP and useSendMagicLink accept an optional otpCodeCustomization parameter:

Field Type Description
length 6 | 7 | 8 | 9 Code length (default: 6)
alphanumeric boolean Use alphanumeric characters instead of digits only (default: false)

Configuration Options

type ZeroDevWalletConnectorParams = {
  projectId: string                    // Required: Your ZeroDev project ID
  organizationId?: string               // Optional: Turnkey organization ID
  proxyBaseUrl?: string                 // Optional: KMS proxy URL
  chains: readonly Chain[]              // Required: Supported chains
  rpId?: string                         // Optional: WebAuthn RP ID
  sessionStorage?: StorageAdapter       // Optional: Custom session storage
  autoRefreshSession?: boolean          // Optional: Auto-refresh (default: true)
  sessionWarningThreshold?: number      // Optional: Refresh threshold in ms (default: 60000)
}

Advanced Usage

Custom Callbacks

const registerPasskey = useRegisterPasskey({
  mutation: {
    onSuccess: () => {
      console.log('Registration successful!')
      router.push('/dashboard')
    },
    onError: (error) => {
      console.error('Registration failed:', error)
      analytics.track('auth_failed', { method: 'passkey' })
    }
  }
})

Manual Session Refresh

const refreshSession = useRefreshSession()

await refreshSession.mutateAsync({})

Export Wallet (Seed Phrase)

const exportWallet = useExportWallet()

// Container element must exist: <div id="export-container" />
await exportWallet.mutateAsync({
  iframeContainerId: 'export-container'
})

Export Private Key

const exportPrivateKey = useExportPrivateKey()

// Container element must exist: <div id="export-container" />
await exportPrivateKey.mutateAsync({
  iframeContainerId: 'export-container'
})

Get Authenticators

const { data: authenticators, isLoading } = useAuthenticators()

// `authenticators` contains all authenticators linked to the current user:
//   { oauths, passkeys, emailContacts, apiKeys }
console.log(authenticators?.oauths)
console.log(authenticators?.passkeys)
console.log(authenticators?.emailContacts)
console.log(authenticators?.apiKeys)

API Reference

Hooks

All hooks follow the TanStack Query mutation pattern:

  • useRegisterPasskey() - Register with passkey
  • useLoginPasskey() - Login with passkey
  • useAuthenticateOAuth() - OAuth (Google popup)
  • useSendMagicLink() - Send magic link via email
  • useVerifyMagicLink() - Verify magic link code
  • useSendOTP() - Send OTP via email
  • useVerifyOTP() - Verify OTP code
  • useRefreshSession() - Manually refresh session
  • useExportWallet() - Export wallet seed phrase
  • useExportPrivateKey() - Export wallet private key
  • useAuthenticators() - Fetch all authenticators (oauths, passkeys, emailContacts, apiKeys) for the user

Connector

  • zeroDevWallet(params) - Wagmi connector factory

Constants

  • OAUTH_PROVIDERS.GOOGLE - Google OAuth provider constant

Types

  • OAuthProvider - OAuth provider type
  • ZeroDevWalletConnectorParams - Connector parameters
  • ZeroDevWalletState - Store state type
  • ZeroDevProvider - EIP-1193 provider type

License

MIT