Documentation Index
Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt
Use this file to discover all available pages before exploring further.
UserOperation
UserOperation represents an ERC-4337 v0.6 user operation. User operations enable account abstraction by separating transaction validation from execution, allowing smart contract wallets with custom logic for signatures, gas payment, and access control.
Quick Start
import { UserOperation } from '@tevm/voltaire/primitives/UserOperation';
import { ENTRYPOINT_V06 } from '@tevm/voltaire/primitives/EntryPoint';
// Create user operation
const userOp = UserOperation.from({
sender: "0x742d35Cc6634C0532925a3b844Bc9e7595f251e3",
nonce: 0n,
initCode: "0x", // Empty if account exists
callData: "0x...", // Call to execute
callGasLimit: 100000n,
verificationGasLimit: 200000n,
preVerificationGas: 50000n,
maxFeePerGas: 1000000000n,
maxPriorityFeePerGas: 1000000000n,
paymasterAndData: "0x", // Empty if self-paying
signature: "0x", // Add after signing
});
// Hash for signing
const hash = UserOperation.hash(userOp, ENTRYPOINT_V06, 1);
// Pack to v0.7 format
const packed = UserOperation.pack(userOp);
Type Definition
export type UserOperationType = {
readonly sender: AddressType;
readonly nonce: Uint256Type;
readonly initCode: Uint8Array;
readonly callData: Uint8Array;
readonly callGasLimit: Uint256Type;
readonly verificationGasLimit: Uint256Type;
readonly preVerificationGas: Uint256Type;
readonly maxFeePerGas: Uint256Type;
readonly maxPriorityFeePerGas: Uint256Type;
readonly paymasterAndData: Uint8Array;
readonly signature: Uint8Array;
} & { readonly [brand]: "UserOperation" };
Fields
sender
Smart account address initiating the operation.
nonce
Anti-replay nonce. Often encoded as key || sequence where:
key: High 192 bits - nonce key for parallel operations
sequence: Low 64 bits - sequential counter per key
initCode
Account creation code (factory address + calldata). Empty if account already deployed.
callData
Calldata to execute on the account contract.
callGasLimit
Gas limit for the execution phase (after validation).
verificationGasLimit
Gas limit for the validation phase (signature verification).
preVerificationGas
Fixed gas overhead for bundler compensation (calldata costs, loop overhead).
maxFeePerGas
Maximum total fee per gas (EIP-1559 format).
maxPriorityFeePerGas
Maximum priority fee per gas (EIP-1559 tip).
paymasterAndData
Paymaster address (20 bytes) + paymaster-specific data. Empty if self-paying.
signature
Account signature over the userOpHash.
API Reference
from
Create UserOperation from parameters.
function from(params: {
sender: AddressType | string | number | bigint | Uint8Array;
nonce: Uint256Type | bigint | number | string;
initCode: Uint8Array | string;
callData: Uint8Array | string;
callGasLimit: Uint256Type | bigint | number | string;
verificationGasLimit: Uint256Type | bigint | number | string;
preVerificationGas: Uint256Type | bigint | number | string;
maxFeePerGas: Uint256Type | bigint | number | string;
maxPriorityFeePerGas: Uint256Type | bigint | number | string;
paymasterAndData: Uint8Array | string;
signature: Uint8Array | string;
}): UserOperationType
hash
Compute userOpHash for signing.
function hash(
userOp: UserOperationType,
entryPoint: AddressType | string | number | bigint | Uint8Array,
chainId: bigint | number
): Uint8Array
Formula: keccak256(abi.encode(userOp, entryPoint, chainId))
pack
Convert UserOperation (v0.6) to PackedUserOperation (v0.7).
function pack(userOp: UserOperationType): PackedUserOperationType
User Operation Lifecycle
- Creation: Wallet creates unsigned user operation
- Gas estimation: Bundler simulates and estimates gas
- Signing: Wallet signs userOpHash
- Submission: Submit to bundler mempool
- Bundling: Bundler aggregates operations
- Execution: EntryPoint validates and executes
Usage Patterns
Basic Operation
import { UserOperation } from '@tevm/voltaire/primitives/UserOperation';
import { ENTRYPOINT_V06 } from '@tevm/voltaire/primitives/EntryPoint';
const userOp = UserOperation.from({
sender: smartAccountAddress,
nonce: await account.getNonce(),
initCode: "0x",
callData: encodeFunctionData({
abi: accountAbi,
functionName: "execute",
args: [targetAddress, value, data],
}),
callGasLimit: 100000n,
verificationGasLimit: 200000n,
preVerificationGas: 50000n,
maxFeePerGas: baseFee + priorityFee,
maxPriorityFeePerGas: priorityFee,
paymasterAndData: "0x",
signature: "0x",
});
// Sign
const hash = UserOperation.hash(userOp, ENTRYPOINT_V06, chainId);
const signature = await account.signMessage(hash);
const signedUserOp = { ...userOp, signature };
// Submit to bundler
await bundler.request({
method: "eth_sendUserOperation",
params: [signedUserOp, ENTRYPOINT_V06],
});
With Paymaster
import { UserOperation } from '@tevm/voltaire/primitives/UserOperation';
import { Paymaster } from '@tevm/voltaire/primitives/Paymaster';
const paymaster = Paymaster.from(paymasterAddress);
const paymasterAndData = new Uint8Array([
...paymaster,
// ...paymasterData
]);
const userOp = UserOperation.from({
sender: smartAccountAddress,
// ... other fields
paymasterAndData,
signature: "0x",
});
Account Deployment
import { UserOperation } from '@tevm/voltaire/primitives/UserOperation';
// initCode = factoryAddress (20 bytes) + factoryCalldata
const factoryCalldata = encodeFunctionData({
abi: factoryAbi,
functionName: "createAccount",
args: [owner, salt],
});
const initCode = concat([factoryAddress, factoryCalldata]);
const userOp = UserOperation.from({
sender: predictedAccountAddress,
nonce: 0n, // First operation
initCode, // Deploy account
callData: "0x", // No execution yet
// ... gas fields
signature: "0x",
});
References