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 less code to get started even faster? 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. Then open the network you enabled for this app and copy its Bundler RPC URL.

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_URL="your-chain-specific-zerodev-rpc-url"
ARBITRUM_SEPOLIA_RPC_URL="https://sepolia-rollup.arbitrum.io/rpc"
  • ZERODEV_PROJECT_ID is your ZeroDev project ID.
  • ZERODEV_AA_URL is the network-specific Bundler RPC URL from the dashboard.
  • 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 aaUrl = '<your-chain-specific-zerodev-rpc-url>'
const chainRpcUrl = 'https://sepolia-rollup.arbitrum.io/rpc'
 
export const config = createConfig({
  chains: [arbitrumSepolia],
  connectors: [
    zeroDevWallet({
      projectId,
      aaUrl,
      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 white-label SDK also supports:

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

Optional 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 white-label hooks 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, the aaUrl, and the gas policy all refer to the same chain.

Next steps