Skip to content

Examples

Basic authorization

example.ts
import { walletClient } from './client'
 
const ACCOUNT_IMPLEMENTATION_ADDRESS = '0x…';
 
const authorization = await walletClient.signAuthorization({
  contractAddress: ACCOUNT_IMPLEMENTATION_ADDRESS,
})
 
const hash = await walletClient.sendTransaction({
  authorizationList: [authorization],
  data: '0x',
  to: walletClient.account.address,
})

Smart account initialization

Set code to a ZeroDev Kernel V3 implementation and initialize the smart account to make the EOA the owner of the account.

example.ts
import { concat, encodeFunctionData } from 'viem'
 
import kernelV3Abi from './abi'
import { walletClient } from './client'
 
const ZERODEV_KERNEL_V3_IMPLEMENTATION_ADDRESS = '0x…'
const ZERODEV_ECDSA_VALIDATOR_ADDRESS = '0x…'
const ROOT_VALIDATOR_ID = concat(['0x01', ZERODEV_ECDSA_VALIDATOR_ADDRESS])
 
const authorization = await walletClient.signAuthorization({
  contractAddress: ZERODEV_KERNEL_V3_IMPLEMENTATION_ADDRESS,
})
 
const hash = await walletClient.sendTransaction({
  authorizationList: [authorization],
  data: encodeFunctionData({
    abi: kernelV3Abi,
    functionName: 'initialize',
    args: [
      ROOT_VALIDATOR_ID,
      zeroAddress,
      walletClient.account.address,
      '0x',
      []
    ],
  }),
  to: walletClient.account.address,
})

Sending a user operation

Signs and sends a paymaster-sponsored UserOp using the ERC-4337 flow. Assumes the account is already initialized, and the EOA is the owner of the account.

Note that the process is exactly the same as when using a regular smart account. To minimize code surface, you can use a convenience library like permissionless.js.

example.ts
import { concat, encodeFunctionData, encodeAbiParameters, keccak256, padHex } from 'viem'
import { readContract } from 'viem/actions';
import { sendUserOperation, entryPoint07Abi, entryPoint07Address } from 'viem/account-abstraction';
import { mainnet } from 'viem/chains'
 
import kernelV3ImplementationAbi from './abi'
import { publicClient, walletClient, bundlerClient, paymasterClient } from './clients'
 
const executions = [
  {
    target: "0x0000000000000000000000000000000000000001",
    value: 0n,
    callData: "0xdeadbeef",
  },
  {
    target: "0x0000000000000000000000000000000000000002",
    value: 0n,
    callData: "0xbeefdead",
  },
]
 
const execMode =
  "0x0100000000000000000000000000000000000000000000000000000000000000";
const executionCalldata = encodeAbiParameters(
  [
    {
      type: "tuple[]",
      components: [
        {
          type: "address",
          name: "target",
        },
        {
          type: "uint256",
          name: "value",
        },
        {
          type: "bytes",
          name: "callData",
        },
      ],
    },
  ],
  [executions]
);
 
const callData = encodeFunctionData({
  abi: kernelV3ImplementationAbi,
  functionName: "execute",
  args: [execMode, executionCalldata],
});
 
const nonce = await readContract(publicClient, {
  address: entryPoint07Address,
  abi: entryPoint07Abi,
  functionName: "getNonce",
  args: [walletClient.account.address, nonceKey],
});
 
const { maxFeePerGas, maxPriorityFeePerGas } =
  await publicClient.estimateFeesPerGas();
 
const userOperationGasResult = await bundlerClient.estimateUserOperationGas({
    entryPointAddress: entryPoint07Address,
    callData,
    maxFeePerGas,
    maxPriorityFeePerGas,
    nonce,
    sender: ownerAddress,
    signature: stubSignature,
  });
 
const callGasLimit = userOperationGasResult.callGasLimit;
const verificationGasLimit = userOperationGasResult.verificationGasLimit;
const preVerificationGas = userOperationGasResult.preVerificationGas;
 
const { paymasterPostOpGasLimit, paymasterVerificationGasLimit } =
  await paymasterClient.getPaymasterStubData({
    chainId: odysseyTestnet.id,
    entryPointAddress: entryPoint07Address,
    callData,
    callGasLimit,
    verificationGasLimit,
    preVerificationGas,
    maxFeePerGas,
    maxPriorityFeePerGas,
    nonce: actualNonce,
    sender: ownerAddress,
  });
 
const { paymaster, paymasterData } = await paymasterClient.getPaymasterData({
  chainId: odysseyTestnet.id,
  entryPointAddress: entryPoint07Address,
  callData,
  callGasLimit,
  verificationGasLimit,
  preVerificationGas,
  maxFeePerGas,
  maxPriorityFeePerGas,
  paymasterPostOpGasLimit,
  paymasterVerificationGasLimit,
  nonce: actualNonce,
  sender: ownerAddress,
});
 
const sender = walletClient.account.address;
const initCode = '0x';
const accountGasLimits = concat([
  padHex(verificationGasLimit.toString(16), { size: 16 }),
  padHex(callGasLimit.toString(16), { size: 16 }),
]);
const gasFees = concat([
  padHex(maxPriorityFeePerGas.toString(16), { size: 16 }),
  padHex(maxFeePerGas.toString(16), { size: 16 }),
]);
const paymasterAndData = concat([
  padHex(paymaster.toLowerCase(), { size: 20 }),
  padHex(paymasterVerificationGasLimit.toString(16), {
    size: 16,
  }),
  padHex(paymasterPostOpGasLimit.toString(16), { size: 16 }),
  paymasterData,
]);
 
const hashedInitCode = keccak256(initCode);
const hashedCallData = keccak256(callData);
const hashedPaymasterAndData = keccak256(paymasterAndData);
const packedUserOp = encodeAbiParameters(
  [
    { type: "address" },
    { type: "uint256" },
    { type: "bytes32" },
    { type: "bytes32" },
    { type: "bytes32" },
    { type: "uint256" },
    { type: "bytes32" },
    { type: "bytes32" },
  ],
  [
    sender,
    nonce,
    hashedInitCode,
    hashedCallData,
    accountGasLimits,
    preVerificationGas,
    gasFees,
    hashedPaymasterAndData,
  ]
);
const opHash = encodeAbiParameters(
  [{ type: "bytes32" }, { type: "address" }, { type: "uint256" }],
  [keccak256(packedUserOp), entryPoint07Address, BigInt(mainnet.id)]
);
 
const signature = await walletClient.signMessage({
  message: {
    raw: opHash,
  }
});
 
await sendUserOperation(bundlerClient, {
  entryPointAddress: entryPoint07Address,
  sender,
  nonce,
  callData,
  callGasLimit,
  verificationGasLimit,
  preVerificationGas,
  maxPriorityFeePerGas,
  maxFeePerGas,
  paymaster,
  paymasterData,
  paymasterPostOpGasLimit,
  paymasterVerificationGasLimit,
  signature,
});