Quickstart
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 i @zerodev/wallet-react @zerodev/wallet-core wagmi viem @tanstack/react-queryAllowlist 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_IDis your ZeroDev project ID.ZERODEV_AA_HOSTis the ZeroDev bundler and paymaster host. It is optional and defaults tohttps://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_URLis 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:
| Mode | Use when | Notes |
|---|---|---|
7702 | You want the recommended default | Exposes the user's wallet address while enabling smart wallet features such as sponsorship. |
4337 | You need a counterfactual smart account address | Exposes 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
createConfigand the gas policy refer to the same chain, and that the chain is enabled for the project.aaHostis host-only, so the SDK derives the correct per-chain URL automatically.