Appearance
Account abstraction with ZeroDev
TIP
Privy now allows you to natively use smart wallet for a better developer experience. Check out the docs here.
ZeroDev is a toolkit for creating ERC-4337-compatible smart wallets for your users, using the user's EOA as the smart wallet's signer. This allows you to easily add Account Abstraction features into your app.
You can easily integrate ZeroDev alongside Privy to create smart wallets from your user's embedded or external wallets, allowing you to enhance your app with gas sponsorship, batched transactions, and more!
Read below to learn how to configure your app to create smart wallets for all your users!
What is an EOA?
An EOA, or externally-owned account, is any Ethereum account that is controlled by a private key. Privy's embedded wallets and most external wallets (MetaMask, Coinbase Wallet, Rainbow Wallet, etc.) are EOAs.
EOAs differ from contract accounts, which are instead controlled by smart contract code and do not have their own private key. ZeroDev's smart wallet is a contract account. Contract accounts have enhanced capabilities, such as gas sponsorship and batched transactions.
Since they do not have their own private key, contract accounts cannot directly produce signatures and initiate transaction flows. Instead, each contract account is generally "managed" by an EOA, which authorizes actions taken by the contract account via a signature; this EOA is called a signer.
In this integration, the user's EOA (from Privy) serves as the signer for their smart wallet (from ZeroDev). The smart wallet (ZeroDev) holds all assets and submits all transactions to the network, but the signer (Privy) is responsible for producing signatures and "kicking off" transaction flows.
How much does deploying a smart wallet for a user cost?
The transaction to deploy a ZeroDev smart wallet requires approximately 258522 in gas. At time of writing, this corresponds to:
- 0.0168 ETH (28 USD) on Ethereum Mainnet
- 0.04 POL (0.024 USD) on Polygon
- 0.00005 ETH (0.08 USD) on Arbitrum
The exact deployment cost you see will vary depending on the current gas price.
Importantly, ZeroDev deploys smart wallets lazily, ensuring that you do not pay deployment costs for functionally unused wallets.
When you first initialize a ZeroDev smart wallet for a user, ZeroDev does not yet deploy the wallet, but instead predicts the smart wallet's address (via the CREATE2
opcode). This allows you to associate a smart wallet with your user, without any upfront deployment costs.
ZeroDev only deploys the smart wallet to the predicted address when the user sends their first transaction with the smart wallet. This ensures that you only ever pay deployment costs for wallets that are actually used to transact on-chain.
1. Install the required dependencies from Privy and ZeroDev
In your app's repository, install the required dependencies from Privy and ZeroDev, as well as the permissionless
, and viem
libraries:
sh
npm i @privy-io/react-auth @zerodev/sdk @zerodev/ecdsa-validator permissionless viem
2. Sign up for a ZeroDev account and get your project ID
Visit the ZeroDev dashboard and sign up for a new account if you do not have one already. Set up a new project for your required chain(s) and retrieve your ZeroDev project ID, as well as your paymaster and bundler URLs for the project.
Within this Dashboard, you can also configure settings for gas sponsorship and other ZeroDev features!
2. Configure your app's Privy settings
First, follow the instructions in the Privy Quickstart to get your app set up with a basic Privy integration.
Next, set Add confirmation modals to "off" in your app's Embedded wallets page in the Privy dashboard. This will configure Privy to not show its default UIs when your user must sign messages or send transactions. Instead, we recommend you use your own custom UIs for showing users the user operationss they sign.
Lastly, update the config.embeddedWallets.createOnLogin
property of your PrivyProvider
to 'users-without-wallets'
.This will configure Privy to create an embedded wallet for users logging in via a web2 method (email, phone, socials), ensuring that all of your users have a wallet that can be used as an EOA.
Your PrivyProvider
should then look like:
tsx
<PrivyProvider
appId='insert-your-privy-app-id'
config={{
embeddedWallets: {
createOnLogin: 'users-without-wallets',
}
...insertTheRestOfYourPrivyProviderConfig
}}
>
{/* Your app's components */}
</PrivyProvider>
3. Create a smart account for your user
You'll now create a smart account for your user, using the Privy embedded wallet (an EOA) as the signer.
To do so, when the user logs in, first find the user's embedded wallet from Privy's useWallets
hook, and get its EIP1193 provider. You can find embedded wallet by finding the only entry in the useWallets
array with a walletClientType
of 'privy'
.
tsx
import {useWallets} from '@privy-io/react-auth';
import {sepolia} from 'viem/chains'; // Replace this with the chain used by your application
import {createWalletClient, custom} from 'viem';
...
// Find the embedded wallet and get its EIP1193 provider
const {wallets} = useWallets();
const embeddedWallet = wallets.find((wallet) => (wallet.walletClientType === 'privy'));
const provider = await embeddedWallet.getEthereumProvider();
Next, pass the returned EIP1193 provider
to the providerToSmartAccountSigner
method from permissionless
to create a SmartAccountSigner
. This signer corresponds to the user's embedded wallet and authorizes actions for the user's smart account.
tsx
import { providerToSmartAccountSigner } from 'permissionless';
...
// Use the EIP1193 `provider` from Privy to create a `SmartAccountSigner`
const smartAccountSigner = await providerToSmartAccountSigner(provider);
Finally, using the smartAccountSigner
from above, initialize a Kernel (ZeroDev) smart account for the user like so:
tsx
import {sepolia} from 'viem/chains'; // Replace this with the chain used by your application
import {createPublicClient, http} from 'viem';
import {ENTRYPOINT_ADDRESS_V07} from 'permissionless';
import {createZeroDevPaymasterClient, createKernelAccount, createKernelAccountClient} from "@zerodev/sdk";
import {signerToEcdsaValidator} from "@zerodev/ecdsa-validator";
...
// Initialize a viem public client on your app's desired network
const publicClient = createPublicClient({
transport: http(sepolia.rpcUrls.default.http[0]),
})
// Create a ZeroDev ECDSA validator from the `smartAccountSigner` from above and your `publicClient`
const ecdsaValidator = await signerToEcdsaValidator(publicClient, {
signer: smartAccountSigner,
entryPoint: ENTRYPOINT_ADDRESS_V07,
})
// Create a Kernel account from the ECDSA validator
const account = await createKernelAccount(publicClient, {
plugins: {
sudo: ecdsaValidator,
},
entryPoint: ENTRYPOINT_ADDRESS_V07,
});
// Create a Kernel account client to send user operations from the smart account
const kernelClient = createKernelAccountClient({
account,
chain: sepolia,
entryPoint: ENTRYPOINT_ADDRESS_V07,
bundlerTransport: http('insert-your-bundler-RPC-from-the-dashboard'),
middleware: {
sponsorUserOperation: async ({ userOperation }) => {
const zerodevPaymaster = createZeroDevPaymasterClient({
chain: sepolia,
entryPoint: ENTRYPOINT_ADDRESS_V07,
transport: http('insert-your-paymaster-RPC-to-the-dashboard'),
})
return zerodevPaymaster.sponsorUserOperation({
userOperation,
entryPoint: ENTRYPOINT_ADDRESS_V07,
})
}
}
})
The kernelClient
is a drop-in replacement for a viem
Wallet Client, and requests to the smart account can be made using viem
's API.
TIP
You can also store the user's smart account address on Privy's user object. See this guide for more.
Want to see this code end-to-end?
You can find the code snippets above pasted in an end-to-end example below.tsx
/**
* This example assumes your app is wrapped with the `PrivyProvider` and
* is configured to create embedded wallets for users upon login. Aside from
* the imports, all of the code in this snippet must be used within a React component
* or context.
*/
import {useWallets} from '@privy-io/react-auth';
import {sepolia} from 'viem/chains'; // Replace this with the chain used by your application
import {createPublicClient, http} from 'viem';
import {providerToSmartAccountSigner, ENTRYPOINT_ADDRESS_V07} from "permissionless";
import {createZeroDevPaymasterClient, createKernelAccountClient} from "@zerodev/sdk";
import {signerToEcdsaValidator} from "@zerodev/ecdsa-validator";
...
// Find the embedded wallet and get its EIP1193 provider
const {wallets} = useWallets();
const embeddedWallet = wallets.find((wallet) => (wallet.walletClientType === 'privy'));
const provider = await embeddedWallet.getEthereumProvider();
// Use the EIP1193 `provider` from Privy to create a `SmartAccountSigner`
const smartAccountSigner = await providerToSmartAccountSigner(provider);
// Initialize a viem public client on your app's desired network
const publicClient = createPublicClient({
transport: http(sepolia.rpcUrls.default.http[0]),
})
// Create a ZeroDev ECDSA validator from the `smartAccountSigner` from above and your `publicClient`
const ecdsaValidator = await signerToEcdsaValidator(publicClient, {
signer: smartAccountSigner,
entryPoint: ENTRYPOINT_ADDRESS_V07,
})
// Create a Kernel account from the ECDSA validator
const account = await createKernelAccount(publicClient, {
plugins: {
sudo: ecdsaValidator,
},
entryPoint: ENTRYPOINT_ADDRESS_V07,
});
// Create a Kernel client to send user operations from the smart account
const kernelClient = createKernelAccountClient({
account,
chain: sepolia,
entryPoint: ENTRYPOINT_ADDRESS_V07,
bundlerTransport: http('insert-your-bundler-RPC-from-the-dashboard'),
middleware: {
// See https://docs.zerodev.app/sdk/core-api/sponsor-gas
sponsorUserOperation: async ({ userOperation }) => {
const zerodevPaymaster = createZeroDevPaymasterClient({
chain: sepolia,
entryPoint: ENTRYPOINT_ADDRESS_V07,
transport: http('insert-your-paymaster-RPC-from-the-dashboard'),
})
return zerodevPaymaster.sponsorUserOperation({
userOperation,
entryPoint: ENTRYPOINT_ADDRESS_V07,
})
}
}
})
Note: if your app uses React, we suggest that you store the user's kernelClient
in a React context that wraps your application. This allows you to easily access the smart account from your app's pages and components.
4. Send user operations (transactions) from the smart account
Now that your users have Kernel (ZeroDev) smart accounts, they can now send UserOperations from their smart account. This is the AA analog to sending a transaction.
To send a user operation from a user's smart account, use the Kernel client's sendTransaction
method.
tsx
const txHash = await kernelClient.sendTransaction({
to: 'TO_ADDRESS',
value: VALUE, // default to 0
data: '0xDATA', // default to 0x
});
This is a drop-in replacement for viem's sendTransaction
method, and will automatically apply any smart account configurations (e.g. gas sponsorship) you configure in the middleware
before sending the transaction.
That's it! You've configured your app to create smart wallets for all of your users, and can seamlessly add in AA features like gas sponsorship, batched transactions, and more. 🎉