Skip to main content
Simulates a ZeroDev user operation and returns a breakdown of expected asset changes, security validation results, and optionally an estimated gas fee — before the operation is submitted to the network. Returns the same result shape as simulateEvmTransaction, since user operations are ultimately EVM transactions. Powered by Blockaid for security validation.

Installation

npm install @dynamic-labs-sdk/zerodev

Usage

import { simulateZerodevUserOperation } from '@dynamic-labs-sdk/zerodev';
import { isEvmWalletAccount } from '@dynamic-labs-sdk/evm';
import { getPrimaryWalletAccount } from '@dynamic-labs-sdk/client';
import { parseEther, encodeFunctionData } from 'viem';

const walletAccount = getPrimaryWalletAccount();

if (walletAccount && isEvmWalletAccount(walletAccount)) {
  const result = await simulateZerodevUserOperation({
    walletAccount,
    userOperation: {
      sender: walletAccount.address,
      callData: encodeFunctionData({
        abi: erc20Abi,
        functionName: 'transfer',
        args: [recipientAddress, parseUnits('100', 18)],
      }),
    },
    networkId: '1',
    entryPoint: ENTRY_POINT_ADDRESS,
  });

  console.log('Assets going out:', result.outAssets);
  console.log('Security check:', result.validation?.result);
}

Parameters

ParameterTypeDescription
walletAccountEvmWalletAccountThe ZeroDev-enabled wallet account
userOperationPartial<UserOperation>The user operation to simulate
networkIdstringThe chain ID (e.g., '1' for Ethereum, '137' for Polygon)
entryPointstringThe EntryPoint contract address
includeFeesboolean (optional)Whether to include a gas fee estimate. Default: false
clientDynamicClient (optional)Only required when using multiple clients

Returns

Promise<EvmSimulationResult> — an object with the following fields:
FieldTypeDescription
inAssetsAssetDiff[]Assets the wallet will receive
outAssetsAssetDiff[]Assets the wallet will send
validationBlockaidValidation (optional)Security assessment from Blockaid
validation.result'benign' | 'warning' | 'malicious'Overall security verdict
validation.descriptionstring (optional)Human-readable description
validation.reasonstring (optional)Reason for the verdict
feeDataEvmTransactionFeeData (optional)Present only when includeFees: true
feeData.humanReadableAmountstringFee in ETH, formatted for display
feeData.nativeAmountbigintFee in wei (0n if the operation is sponsored)
feeData.usdAmountstring (optional)Fee in USD if price data is available
feeData.gasEstimatebigintEstimated gas units
priceDataPriceDataPrice information for the assets involved
counterpartiesstring[] (optional)Other addresses involved in the operation
When the operation is eligible for gas sponsorship, feeData.nativeAmount is 0n — indicating the user will not pay gas. Use canSponsorUserOperation to check sponsorship eligibility before simulating.

Examples

Token transfer

import { encodeFunctionData, parseUnits } from 'viem';

const result = await simulateZerodevUserOperation({
  walletAccount,
  userOperation: {
    sender: walletAccount.address,
    callData: encodeFunctionData({
      abi: erc20Abi,
      functionName: 'transfer',
      args: [recipientAddress, parseUnits('50', 18)],
    }),
  },
  networkId: '137',
  entryPoint: ENTRY_POINT_ADDRESS,
});

console.log('Tokens out:', result.outAssets);
console.log('Tokens in:', result.inAssets);

With gas fee estimation

const result = await simulateZerodevUserOperation({
  walletAccount,
  userOperation: {
    sender: walletAccount.address,
    callData,
  },
  networkId: '1',
  entryPoint: ENTRY_POINT_ADDRESS,
  includeFees: true,
});

if (result.feeData) {
  if (result.feeData.nativeAmount === 0n) {
    console.log('Operation is sponsored — no gas cost');
  } else {
    console.log(`Estimated gas fee: ${result.feeData.humanReadableAmount} ETH`);
  }
}

Security validation

const result = await simulateZerodevUserOperation({
  walletAccount,
  userOperation: { sender: walletAccount.address, callData },
  networkId: '1',
  entryPoint: ENTRY_POINT_ADDRESS,
});

if (result.validation?.result === 'malicious') {
  console.warn('Operation flagged as malicious. Blocking.');
  return;
}

Error handling

import { SimulationFailedError, FeeEstimationFailedError } from '@dynamic-labs-sdk/client';

try {
  const result = await simulateZerodevUserOperation({
    walletAccount,
    userOperation,
    networkId,
    entryPoint,
    includeFees: true,
  });
} catch (error) {
  if (error instanceof SimulationFailedError) {
    console.error('Simulation failed:', error.message);
  } else if (error instanceof FeeEstimationFailedError) {
    console.error('Fee estimation failed:', error.message);
  }
}