5/5
## Understanding the PackedUserOperation Struct for Account Abstraction The `PackedUserOperation` struct is a cornerstone of ERC-4337, the standard for account abstraction in Ethereum. It encapsulates all the necessary information for a user's smart contract account to execute an operation. This lesson will break down the structure of `PackedUserOperation`, detailing each field and its significance in enabling smart contract wallets to initiate and validate transactions. ### Context: The `validateUserOp` Function Before diving into `PackedUserOperation`, it's helpful to understand where it's used. In a typical ERC-4337 setup, a smart contract account (like the `MinimalAccount.sol` example often used in demonstrations) will have a `validateUserOp` function. This function receives a `PackedUserOperation` and is responsible for verifying its authenticity and intent before execution. ```solidity // src/ethereum/MinimalAccount.sol // SPDX-License-Identifier: MIT pragma solidity 0.8.24; import {IAccount} from "lib/account-abstraction/contracts/interfaces/IAccount.sol"; import {PackedUserOperation} from "lib/account-abstraction/contracts/interfaces/PackedUserOperation.sol"; contract MinimalAccount is IAccount { function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds) external returns (uint256 validationData) { // This function will be implemented to validate the user operation. // For now, it returns a placeholder value. return missingAccountFunds; } } ``` The primary goal is to implement the logic within `validateUserOp` to correctly process the incoming `PackedUserOperation`. ### Deep Dive into the `PackedUserOperation` Struct Let's examine the fields of the `PackedUserOperation` struct, as defined in `lib/account-abstraction/contracts/interfaces/PackedUserOperation.sol`. ```solidity /** * @param callData - The method call to execute on this account. * @param accountGasLimits - Packed gas limits for validateUserOp and gas limit passed to the callData method call. * @param preVerificationGas - Gas not calculated by the handleOps method, but added to the gas paid. Covers batch overhead. * @param gasFees - packed gas fields maxPriorityFeePerGas and maxFeePerGas - Same as EIP-1559 gas parameters. * @param paymasterAndData - If set, this field holds the paymaster address, verification gas limit, postOp gas limit. The paymaster will pay for the transaction instead of the sender. * @param signature - Sender-verified signature over the entire request, the EntryPoint address and the chainid. */ struct PackedUserOperation { // 20 bytes address sender; uint256 nonce; bytes initCode; bytes callData; bytes32 accountGasLimits; uint256 preVerificationGas; bytes32 gasFees; bytes paymasterAndData; bytes signature; } ``` Below is a detailed explanation of each field: 1. **`address sender`** * **Purpose:** This field specifies the address of the smart contract account that intends to execute the operation. It is the account being controlled by this `UserOperation`. * **Significance:** In the context of account abstraction, this `sender` is not an Externally Owned Account (EOA) but rather a smart contract wallet, such as our `MinimalAccount` example. This is the account that will ultimately perform the desired action. 2. **`uint256 nonce`** * **Purpose:** The `nonce` is a unique, sequential number used by the `sender` account to prevent replay attacks. Each `UserOperation` must have a unique nonce to ensure it's processed only once. * **Significance:** Similar to the nonce used by EOAs to order transactions, this field ensures that malicious actors cannot resubmit a previously executed `UserOperation`. It acts as a sequence number for operations originating from the smart contract account, ensuring ordered and unique execution. 3. **`bytes initCode`** * **Purpose:** This field contains the bytecode necessary to deploy the `sender` smart contract account if it does not already exist. It typically includes the factory contract address and the constructor arguments for the new account. * **Significance:** If the `sender` account already exists on the blockchain, `initCode` will be empty. This mechanism allows for counterfactual account deployment, where an account address can be determined and funded before its actual deployment. For scenarios dealing with already deployed accounts, this field can often be set to empty bytes. 4. **`bytes callData`** * **Purpose:** This is the core of the `UserOperation`, containing the actual instruction set for the `sender` account to execute. It usually consists of a function selector and ABI-encoded arguments for a function call. * **Significance:** This field dictates what action the smart contract account will perform. For example, it could specify a call to the `approve` function on a token contract for a certain number of tokens, a `transfer` of assets, or any other interaction with the blockchain. This is effectively the "payload" or "intent" of the transaction. 5. **`bytes32 accountGasLimits`** * **Purpose:** This field contains packed gas limits relevant to the execution of the `UserOperation` by the account. It typically bundles `verificationGasLimit` (gas allocated for the `validateUserOp` function) and `callGasLimit` (gas allocated for executing the `callData`). * **Significance:** Proper gas limit specification is crucial for ensuring the `UserOperation` can be processed without running out of gas during its validation or execution phases. These are the gas limits directly associated with the smart contract account's operations. 6. **`uint256 preVerificationGas`** * **Purpose:** This value represents the gas cost incurred *before* the `validateUserOp` function is called by the EntryPoint contract. It covers overheads like hashing the `UserOperation`, SLOADs from storage to fetch account nonces or check for existing deployments, and other preparatory steps performed by the bundler or EntryPoint. * **Significance:** It ensures that the bundler (the entity submitting the `UserOperation` to the EntryPoint) is compensated for these preliminary gas expenses, which are not part of the `validateUserOp` or the main execution call. 7. **`bytes32 gasFees`** * **Purpose:** This field holds packed gas fee parameters, specifically `maxFeePerGas` and `maxPriorityFeePerGas`. These are analogous to the EIP-1559 gas parameters for standard Ethereum transactions. * **Significance:** It allows the user to specify their willingness to pay for gas, influencing how quickly their `UserOperation` is picked up by bundlers and included in a block. These parameters manage the different gas fees associated with the transaction. 8. **`bytes paymasterAndData`** * **Purpose:** If a Paymaster is sponsoring the transaction (i.e., paying the gas fees on behalf of the user), this field contains the Paymaster's contract address and any additional data the Paymaster requires for its own validation logic (e.g., a signature from the user authorizing the Paymaster). * **Significance:** This field is key to enabling gas abstraction. By default, the `sender` account must have sufficient funds to cover gas costs. However, with a Paymaster, a third party can cover these fees, meaning the user's smart contract account might not need to hold native currency. If no Paymaster is used, this field remains empty. 9. **`bytes signature`** * **Purpose:** This field contains the cryptographic signature that authenticates the `UserOperation`. The `sender` account's `validateUserOp` function is responsible for verifying this signature against a `userOpHash`. The `userOpHash` is a hash of the `PackedUserOperation`'s fields, the EntryPoint contract's address, and the current chain ID. * **Significance:** This is a critical security component. It proves that the owner of the `sender` account has authorized this specific operation. Account abstraction allows for flexible signature schemes beyond the standard ECDSA used by EOAs. The `validateUserOp` function in the smart contract account will implement custom logic to determine what constitutes a valid signature (e.g., multi-sig, social recovery mechanisms, passkeys, etc.). The inclusion of the EntryPoint address and chain ID in the signed data is crucial for preventing replay attacks across different chains or different EntryPoint contract implementations. By understanding each field within `PackedUserOperation`, developers can effectively build and interact with ERC-4337 compliant smart contract accounts, leveraging the full power of account abstraction. This structure provides a standardized way to define user intent, manage gas, and ensure secure, flexible transaction execution through programmable smart contracts acting as wallets.
A deep-dive exploration to Understanding the PackedUserOperation Struct for Account Abstraction - Journey through each field of ERC-4337's `PackedUserOperation`, from `sender` to `signature`, and grasp their individual roles. Understand how this vital struct underpins account abstraction by enabling secure and flexible transaction handling by smart contract wallets.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Course Overview
About the course
Advanced smart contract development
How to develop a stablecoin
How to develop a DeFi protocol
How to develop a DAO
Advanced smart contracts testing
Fuzz testing
Manual verification
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
Security researcher
$49,999 - $120,000 (avg. salary)
Web3 engineer, educator, and Cyfrin co-founder. Patrick's smart contract development and security courses have helped hundreds of thousands of engineers kickstarting their careers into web3.
Guest lecturers:
Last updated on June 6, 2025
Duration: 36min
Duration: 3h 06min
Duration: 5h 02min
Duration: 6h 02min
Duration: 2h 47min
Duration: 1h 23min
Duration: 4h 28min
Duration: 1h 19min
Duration: 1h 10min
Course Overview
About the course
Advanced smart contract development
How to develop a stablecoin
How to develop a DeFi protocol
How to develop a DAO
Advanced smart contracts testing
Fuzz testing
Manual verification
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
Security researcher
$49,999 - $120,000 (avg. salary)
Web3 engineer, educator, and Cyfrin co-founder. Patrick's smart contract development and security courses have helped hundreds of thousands of engineers kickstarting their careers into web3.
Guest lecturers:
Last updated on June 6, 2025