Skip to content

Quickstart

Get up and running in 5 minutes

Add ZeroDev Wallet to any React app that can render client-side React components. Authenticate users with ZeroDev hooks, then use standard Wagmi hooks for wallet actions.

Want a faster starting point? Try the optional Wallet UI Kit for a prebuilt login flow.

For a complete working reference, see the ZeroDev Wallet SDK demo app.

Prerequisites

You will need:

  • A ZeroDev account in the ZeroDev Dashboard.
  • A project with at least one enabled network.
  • Your project ID.
  • The Bundler RPC URL for the network you want to use.
  • Your app origin added to the project's ACL allowlist.

Install packages

npm
npm i @zerodev/wallet-react @zerodev/wallet-core wagmi viem @tanstack/react-query

Allowlist your app origin

Before testing authentication, add your app's origin to the project's ACL allowlist in the ZeroDev dashboard.

For example, if you run your app at http://localhost:3000 during development, add http://localhost:3000 to the allowlist.

Add configuration values

In the ZeroDev dashboard, open your project and copy its project ID. Make sure the network you want to use is enabled for the project.

Add these values to your app's configuration. The exact environment variable prefix depends on your framework:

ZERODEV_PROJECT_ID="your-project-id"
ZERODEV_AA_HOST="https://rpc.zerodev.app"
ARBITRUM_SEPOLIA_RPC_URL="https://sepolia-rollup.arbitrum.io/rpc"
  • ZERODEV_PROJECT_ID is your ZeroDev project ID.
  • ZERODEV_AA_HOST is the ZeroDev bundler and paymaster host. It is optional and defaults to https://rpc.zerodev.app; set it only when you target staging or self-hosted infrastructure. The SDK derives the per-chain URL from this host, so the same value works across every chain.
  • ARBITRUM_SEPOLIA_RPC_URL is the chain RPC used by Wagmi's transport.

This example uses Arbitrum Sepolia. Replace the chain and RPC URLs if your project uses a different chain.

Configure Wagmi

Create src/wagmi.ts:

import { zeroDevWallet } from '@zerodev/wallet-react'
import { createConfig, http } from 'wagmi'
import { arbitrumSepolia } from 'wagmi/chains'
 
const projectId = '<your-project-id>'
const aaHost = 'https://rpc.zerodev.app'
const chainRpcUrl = 'https://sepolia-rollup.arbitrum.io/rpc'
 
export const config = createConfig({
  chains: [arbitrumSepolia],
  connectors: [
    zeroDevWallet({
      projectId,
      aaHost,
      chains: [arbitrumSepolia],
      mode: '7702',
    }),
  ],
  transports: {
    [arbitrumSepolia.id]: http(chainRpcUrl),
  },
})

In your app, replace the inline constants with your framework's public runtime config or client-safe environment variable access.

7702 is the SDK default, but setting it explicitly makes the recommended mode clear.

Add providers

Wrap your app with Wagmi and TanStack Query providers:

import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import type { ReactNode } from 'react'
import { WagmiProvider } from 'wagmi'
import { config } from './wagmi'
 
const queryClient = new QueryClient()
 
export function WalletProviders({ children }: { children: ReactNode }) {
  return (
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
        {children}
      </QueryClientProvider>
    </WagmiProvider>
  )
}

Authenticate users

Start with passkeys for a minimal passwordless flow:

import { useLoginPasskey, useRegisterPasskey } from '@zerodev/wallet-react'
import { useAccount, useDisconnect } from 'wagmi'
 
export function WalletLogin() {
  const { address, isConnected } = useAccount()
  const { disconnect } = useDisconnect()
  const registerPasskey = useRegisterPasskey()
  const loginPasskey = useLoginPasskey()
 
  if (isConnected) {
    return (
      <div>
        <p>Connected: {address}</p>
        <button type="button" onClick={() => disconnect()}>
          Disconnect
        </button>
      </div>
    )
  }
 
  return (
    <div>
      <button
        type="button"
        disabled={registerPasskey.isPending}
        onClick={() => registerPasskey.mutate()}
      >
        {registerPasskey.isPending ? 'Registering...' : 'Register passkey'}
      </button>
 
      <button
        type="button"
        disabled={loginPasskey.isPending}
        onClick={() => loginPasskey.mutate()}
      >
        {loginPasskey.isPending ? 'Logging in...' : 'Login with passkey'}
      </button>
    </div>
  )
}

The auth hooks authenticate the user and connect the ZeroDev Wagmi connector. After auth succeeds, use normal Wagmi hooks such as useAccount, useSignMessage, and useSendTransaction.

Sign a message

Message signing is offchain and does not require gas.

import { useSignMessage } from 'wagmi'
 
export function SignMessageButton() {
  const signMessage = useSignMessage()
 
  return (
    <div>
      <button
        type="button"
        disabled={signMessage.isPending}
        onClick={() =>
          signMessage.signMessage({
            message: 'Hello from ZeroDev Wallet',
          })
        }
      >
        {signMessage.isPending ? 'Waiting for signature...' : 'Sign message'}
      </button>
 
      {signMessage.data ? <p>Signature: {signMessage.data}</p> : null}
      {signMessage.error ? <p>{signMessage.error.message}</p> : null}
    </div>
  )
}

Send a gasless transaction

To test gas sponsorship, configure a gas policy for your project and chain in the ZeroDev dashboard first. See Gas Policies for setup details.

This example sends a 0 ETH self-transfer. It exercises the full account abstraction transaction path without requiring the user to hold native gas.

import {
  useAccount,
  useSendTransaction,
  useWaitForTransactionReceipt,
} from 'wagmi'
 
export function SendTransactionButton() {
  const { address } = useAccount()
  const sendTransaction = useSendTransaction()
  const receipt = useWaitForTransactionReceipt({
    hash: sendTransaction.data,
  })
 
  const isPending =
    sendTransaction.isPending ||
    (Boolean(sendTransaction.data) && receipt.isLoading)
 
  return (
    <div>
      <button
        type="button"
        disabled={!address || isPending}
        onClick={() =>
          address &&
          sendTransaction.sendTransaction({
            to: address,
            value: 0n,
          })
        }
      >
        {isPending ? 'Sending transaction...' : 'Send gasless transaction'}
      </button>
 
      {sendTransaction.data ? <p>Hash: {sendTransaction.data}</p> : null}
      {receipt.isSuccess ? <p>Transaction confirmed</p> : null}
      {sendTransaction.error ? <p>{sendTransaction.error.message}</p> : null}
      {receipt.error ? <p>{receipt.error.message}</p> : null}
    </div>
  )
}

Account modes

ZeroDev Wallet supports two account modes:

ModeUse whenNotes
7702You want the recommended defaultExposes the user's wallet address while enabling smart wallet features such as sponsorship.
4337You need a counterfactual smart account addressExposes the Kernel smart account address. The first transaction can deploy the account.

For most apps, use 7702.

Other auth methods

Passkeys are only one option. The hook-based SDK also supports:

Each auth method connects the same ZeroDev Wagmi connector after successful authentication.

Optional Wallet UI Kit

The examples above use @zerodev/wallet-react so you can build your own UI. If you want a prebuilt login flow, use the optional Wallet UI Kit. Keep the hook-based path if you want full control over layout, styling, and bundle size.

Troubleshooting

  • Allowlist errors: confirm that the dashboard allowlists the exact origin you are opening in the browser.
  • Sponsored transaction fails: confirm that a gas policy exists for the project and chain.
  • Wrong chain: make sure the chain in createConfig and the gas policy refer to the same chain, and that the chain is enabled for the project. aaHost is host-only, so the SDK derives the correct per-chain URL automatically.

Next steps