Introduction

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

1

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";
2

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}`;
}
3

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.

4

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,
});
5

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 }),
  },
});
6

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: