ZeroDev is an embedded AA wallet, currently powering many AA wallets deployed on EVM chains. ZeroDev is known for its large AA feature set, including not just gas sponsoring, but also session keys, recovery, multisig, and more.
Installation
Install the required packages:
npm install @zerodev/sdk @zerodev/sdk/paymaster para-xyz viem
Connecting ZeroDev to a Para Signer
Required Imports
Start by importing all the necessary packages:
// ZeroDev imports
import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator";
import { createKernelAccount, createKernelAccountClient, createZeroDevPaymasterClient } from "@zerodev/sdk";
import { getEntryPoint, KERNEL_V3_1 } from "@zerodev/sdk/constants";
// Para imports
// NOTE: These imports are available on web via the @getpara/web-sdk and or @getpara/react-sdk, but for Node.js or server-side environments, use the @getpara/server-sdk.
import { Para as ParaServer, Environment, hexStringToBase64, SuccessfulSignatureRes } from "@getpara/server-sdk";
import { createParaAccount, createParaViemClient } from "@getpara/viem-v2-integration";
// Viem imports
import { http, encodeFunctionData, hashMessage, SignableMessage, Hash, LocalAccount, WalletClient } from "viem";
import { arbitrumSepolia } from "viem/chains";
Create a custom sign message function
When working with Para MPC and AA providers, we need a function to handle the V value in signatures so that transactions can be validated correctly. This is due to the way Para handles signatures in its multi-party computation (MPC) setup. Below is the implementation of the customSignMessage
function:
// Helper function to convert hex to base64
function hexStringToBase64(hexString: string): string {
const bytes = new Uint8Array(hexString.length / 2);
for (let i = 0; i < hexString.length; i += 2) {
bytes[i / 2] = parseInt(hexString.substring(i, i + 2), 16);
}
return btoa(String.fromCharCode.apply(null, Array.from(bytes)));
}
// Custom sign message function for Para MPC
async function customSignMessage(para: ParaServer, message: SignableMessage): Promise<Hash> {
const wallet = para.wallets ? Object.values(para.wallets)[0] : null;
const hashedMessage = hashMessage(message);
const messagePayload = hashedMessage.startsWith("0x") ? hashedMessage.substring(2) : hashedMessage;
const messageBase64 = hexStringToBase64(messagePayload);
const res = await para.signMessage({
walletId: wallet.id,
messageBase64: messageBase64,
});
let signature = res.signature;
// Adjust the last byte for the correct V value
const vHex = signature.slice(-2);
const v = parseInt(vHex, 16);
if (!isNaN(v) && v < 27) {
const adjustedVHex = (v + 27).toString(16).padStart(2, "0");
signature = signature.slice(0, -2) + adjustedVHex;
}
return `0x${signature}`;
}
Set up Para with Viem
Set up your Para account and Viem client:
// Create a Para Viem account
const viemParaAccount: LocalAccount = createParaAccount(para);
// Override the sign message function with our custom implementation
viemParaAccount.signMessage = async ({ message }) => customSignMessage(para, message);
// Create a Viem client with the Para account
const viemClient: WalletClient = createParaViemClient(para, {
account: viemParaAccount,
chain: arbitrumSepolia,
transport: http("https://sepolia-rollup.arbitrum.io/rpc"), // Replace with your RPC URL
});
Ensure you override the signMessage
method of the LocalAccount
instance with the customSignMessage
function. This is crucial for ensuring that the V value in the signature is handled correctly when sending transactions through ZeroDev.
Set up ZeroDev kernel and validator
Configure the ZeroDev kernel account with the ECDSA validator:
// Set up kernel and validator configurations
const entryPoint = getEntryPoint("0.7");
const kernelVersion = KERNEL_V3_1;
// Create ECDSA validator using your Para account
const ecdsaValidator = await signerToEcdsaValidator(viemClient, {
signer: viemParaAccount,
entryPoint,
kernelVersion,
});
// Create kernel account with the validator
const account = await createKernelAccount(viemClient, {
plugins: { sudo: ecdsaValidator },
entryPoint,
kernelVersion,
});
Set up ZeroDev paymaster and kernel client
Create the paymaster and kernel client to handle transactions:
For more information on setting up RPCs and managing your ZeroDev project, refer to the ZeroDev documentation.
// Replace these with your ZeroDev RPC URLs from the ZeroDev Dashboard
const bundlerRpc = "https://your-bundler-rpc-url.zerodev.app";
const paymasterRpc = "https://your-paymaster-rpc-url.zerodev.app";
// Set up ZeroDev paymaster
const zerodevPaymaster = createZeroDevPaymasterClient({
chain: arbitrumSepolia,
transport: http(paymasterRpc),
});
// Create kernel account client with the paymaster
const kernelClient = createKernelAccountClient({
account,
chain: arbitrumSepolia,
bundlerTransport: http(bundlerRpc),
paymaster: {
getPaymasterData: (userOperation) =>
zerodevPaymaster.sponsorUserOperation({ userOperation }),
},
});
Send transactions with the kernel client
Now you can use the kernel client to send user operations:
// Example of sending a transaction
const hash = await kernelClient.sendUserOperation({
userOperation: {
callData: account.encodeCallData({
to: "0xYourTargetAddress", // Replace with your target address
data: "0xYourFunctionData", // Replace with your function data
value: 0n // Replace with your value if needed
})
}
});
// Wait for the transaction to be mined
const transactionHash = await kernelClient.waitForUserOperationReceipt({ hash });
console.log("Transaction mined:", transactionHash);
The examples above assume that you have already initialized the Para object and created a wallet. For more details on Para initialization, refer to the Para SDK documentation.
Example
For an example of using ZeroDev with Para Signer, you can refer to the following GitHub repository: