When using account abstraction in your project to sponsor gas, batch transactions, or any other account abstraction features, you will likely want to get a ZeroDev kernel client to perform these operations.

You can use the @dynamic-labs/zerodev-extension package to create a ZeroDev kernel client for a wallet.

Here is how you can set up and create a kernel client.

The @dynamic-labs/zerodev-extension depends on the Viem Extension, so before going through this setup, make sure to have the Viem Extension set up and working. Viem Extension Setup

Setup

Install ZeroDevExtension

Install the @dynamic-labs/zerodev-extension package:

Shell
npx expo install @dynamic-labs/zerodev-extension@alpha

Resolve File Resolution Error (Optional)

When running the ZeroDevExtension in your React Native application, you might encounter an error where Metro cannot resolve the paymasterClient.js file. This issue occurs because Metro tries to load paymasterClient.js, but the actual file is named paymasterClient.ts.

To fix this, you need to customize Metro’s resolver in your metro.config.js file to handle TypeScript file extensions properly.

Generate metro.config.js for Expo Projects

If you don’t have a metro.config.js file in your project, you can generate one using the following command:

npx expo customize metro.config.js

Customize Metro Resolver

Add the following code to your metro.config.js file to instruct Metro to resolve .ts files when it cannot find the corresponding .js files:

// metro.config.js

/**
 * Custom resolver to handle ZeroDev imports
 */
config.resolver.resolveRequest = (context, moduleName, platform) => {
  try {
    return context.resolveRequest(context, moduleName, platform);
  } catch (error) {
    if (moduleName.endsWith(".js")) {
      const tsModuleName = moduleName.replace(/\.js$/, ".ts");
      return context.resolveRequest(context, tsModuleName, platform);
    }
    throw error;
  }
};

This modification allows Metro to attempt to resolve .ts files when it fails to find .js files, which fixes the resolution error for the paymasterClient file.

Events Polyfill

ZeroDevExtension requires a polyfill for the Node.js events module. You can install the events polyfill using one of the following commands:

Shell
npx expo install events

Integrate with your Dynamic client

To include the ZeroDev module in your Dynamic client, you need to extend the client with the ZeroDev extension:

import { createClient } from "@dynamic-labs/client";
import { ViemExtension } from "@dynamic-labs/viem-extension";
import { ZeroDevExtension } from "@dynamic-labs/zerodev-extension";

const dynamicClient = createClient({
  environmentId: "",
})
  .extend(ViemExtension())
  .extend(ZeroDevExtension());

Now your setup is complete, and you have the ZeroDev module available in your Dynamic client.

Usage

Now that you have the ZeroDev module in your Dynamic client, you can get the ZeroDev kernel client by using the dynamicClient.zeroDev.createKernelClient method. Here is an example:

// dynamicClient.ts
const dynamicClient = createClient({
  environmentId: "",
})
  .extend(ViemExtension())
  .extend(ZeroDevExtension());

// getPrimaryWalletKernelClient.ts
const getPrimaryWalletKernelClient = async () => {
  const primaryWallet = dynamicClient.wallets.primaryWallet;

  if (!primaryWallet) {
    throw new Error('No primary wallet');
  }

  const kernelClient = await dynamicClient.zeroDev.createKernelClient({
    wallet: primaryWallet
  });

  return kernelClient;
}

Examples

Batch transaction example

This example demonstrates how you can use a ZeroDev kernel client to perform a batched transaction:

import { encodeFunctionData } from 'viem';

const contractAddress = '0x123';

const contractABI = [
  {
    inputs: [{ internalType: 'address', name: '_to', type: 'address' }],
    name: 'mint',
    outputs: [],
    stateMutability: 'nonpayable',
    type: 'function',
  },
];

const sendBatchedTransactions = async () => {
  const primaryWallet = dynamicClient.wallets.primaryWallet;

  if (!primaryWallet) {
    throw new Error('No primary wallet');
  }

  const kernelClient = await client.zeroDev.createKernelClient({
    wallet: primaryWallet,
  });

  const { account } = kernelClient;

  const hash = await kernelClient.sendUserOperation({
    account,
    userOperation: {
      callData: await account.encodeCallData([
        {
          data: encodeFunctionData({
            abi: contractABI,
            args: [primaryWallet.address],
            functionName: 'mint',
          }),
          to: contractAddress,
          value: BigInt(0),
        },
        {
          data: encodeFunctionData({
            abi: contractABI,
            args: [primaryWallet.address],
            functionName: 'mint',
          }),
          to: contractAddress,
          value: BigInt(0),
        },
      ]),
    },
  });

  return hash;
}