Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
LABS
Guides

How to Architect a Smart Contract Wallet for Batch Operations

A developer guide for building a smart contract wallet that supports batched user operations, gas sponsorship with ERC-20 tokens, and session-based permissions.
Chainscore © 2026
introduction
DEVELOPER GUIDE

How to Architect a Smart Contract Wallet for Batch Operations

This guide explains the architectural patterns for building smart contract wallets that can execute multiple actions in a single transaction, a core feature for improving user experience and gas efficiency in Web3.

A smart contract wallet is a self-custodial account where the logic is defined by code, not a private key. Unlike Externally Owned Accounts (EOAs), these wallets can execute complex, conditional logic. The primary architectural advantage is the ability to perform batch operations—bundling multiple calls (e.g., token approvals, swaps, staking) into one on-chain transaction. This solves a critical UX pain point in Ethereum and EVM chains, where users often face a frustrating sequence of separate transactions and approvals.

The core of this architecture is the executeBatch function. A minimal implementation involves an array of Call structs, each specifying a target address, value, and calldata. The wallet contract loops through this array, making each call via a low-level call or delegatecall. Security is paramount: the contract must verify a valid signature for the entire batch, often using EIP-4337's UserOperation or a custom signature scheme, and implement reentrancy guards. Here's a simplified snippet:

solidity
function executeBatch(Call[] calldata calls) external payable onlyOwner {
    for (uint256 i = 0; i < calls.length; i++) {
        (bool success, ) = calls[i].target.call{value: calls[i].value}(calls[i].data);
        require(success, "Call failed");
    }
}

For production systems, architects must consider gas optimization and failure handling. A naive sequential revert-on-failure model can waste gas. Advanced designs use a pattern where failed calls don't revert the entire batch unless critical, logging errors instead. Integrating with EIP-4337 Account Abstraction is now a best practice, as it provides a standard interface, enables sponsored transactions via paymasters, and allows for signature aggregation. Wallets like Safe{Wallet} and ZeroDev leverage these patterns to offer robust batch transaction features.

Real-world use cases for batch operations are extensive. In DeFi, a user can approve, swap, and deposit into a vault in one click. For NFT mints, a wallet can mint and list an item on a marketplace simultaneously. For governance, voting on multiple proposals becomes a single action. This architecture also enables atomic multi-chain actions when combined with cross-chain messaging layers like LayerZero or CCIP, though this introduces additional security considerations for the validation of remote calls.

When architecting your wallet, key decisions include: choosing a signature validation method (EIP-1271 for contract signatures), managing upgradeability via proxies, and setting permissions for batched calls. Always conduct thorough audits, as the increased complexity and power of an executeBatch function expands the attack surface. The end goal is a wallet that is not just a key holder, but an autonomous agent capable of executing complex user intents efficiently and securely.

prerequisites
PREREQUISITES AND SETUP

How to Architect a Smart Contract Wallet for Batch Operations

This guide outlines the foundational knowledge and initial setup required to design a secure and efficient smart contract wallet capable of executing multiple transactions in a single call.

Before architecting a smart contract wallet for batch operations, you need a solid understanding of core Ethereum concepts. You must be proficient with Ethereum's account model, distinguishing between Externally Owned Accounts (EOAs) and smart contract accounts. A deep knowledge of the Ethereum Virtual Machine (EVM), transaction execution, and gas mechanics is essential. You should also be comfortable with Solidity development, including advanced patterns like delegatecall, function selectors, and contract storage layout. Familiarity with ERC-4337 (Account Abstraction) standards is highly recommended, as they define the modern framework for smart contract wallets.

Your development environment must be configured for robust testing and deployment. Set up a project using Hardhat or Foundry, as they provide comprehensive testing frameworks and local blockchain networks. You will need Node.js (v18+) and a package manager like npm or yarn. Install essential libraries: @openzeppelin/contracts for secure base contracts and @account-abstraction/contracts for ERC-4337 utilities. Configure a .env file to manage private keys and RPC URLs for networks like Sepolia or Goerli. A wallet like MetaMask is necessary for initial deployment and interaction with your contracts.

The architectural foundation begins with the wallet's core contract, which must implement a validation and execution flow. The contract needs a method, often called executeBatch, that accepts arrays of target addresses, values, and calldata. Crucially, it must verify the transaction's validity through a validateUserOp function, checking a cryptographic signature or other rules defined by the wallet's owners. This function is the security gatekeeper, preventing unauthorized batch executions. Use OpenZeppelin's libraries for signature verification (ECDSA) and access control (Ownable or multi-signature modules) to build this securely.

A critical design decision is how to handle gas sponsorship and transaction bundling. For a seamless user experience, the wallet should be compatible with Paymasters (ERC-4337) that can relay transactions and pay gas fees on behalf of users. Your architecture must correctly implement the validatePaymasterUserOp flow and manage gas calculations for composite operations. When testing, simulate batch calls that interact with multiple protocols—like approving a token on Uniswap and then swapping it—to ensure atomicity and gas efficiency. Failed sub-calls should be handled gracefully, with options for partial reversion or full rollback.

Finally, comprehensive testing is non-negotiable. Write unit tests in Solidity (with Foundry) or JavaScript/TypeScript (with Hardhat) covering all major scenarios: successful batch execution, signature failure, insufficient gas, and Paymaster integration. Use fuzzing tests to throw random data at your executeBatch function and ensure it remains secure. Before mainnet deployment, audit your contract's gas consumption for large batches and consider implementing gas limit checks per operation. The initial setup is complete when you have a deployable, tested contract that can reliably bundle actions from a non-custodial smart account.

core-architecture
SMART CONTRACT WALLET DESIGN

Core Architecture: EntryPoint, Account, and Paymaster

A smart contract wallet's ability to batch operations hinges on the interaction between three core contracts defined by ERC-4337. This guide explains the roles of the EntryPoint, Account, and Paymaster.

The EntryPoint is a singleton, audited contract that acts as the system's central orchestrator and security gatekeeper. All user operations are sent directly to it. Its primary functions are to validate each operation's signature and paymaster data, execute the bundled actions, and handle gas payment and refunds. By centralizing this logic, it ensures consistent security rules and simplifies the job of bundlers, who are off-chain actors that package operations and submit them to the EntryPoint.

The Account contract is the user's smart contract wallet itself. Its key method is validateUserOp, which the EntryPoint calls to verify the operation's signature and nonce. For batch operations, the Account's execute or executeBatch function is invoked, which can make multiple calls to external contracts in a single transaction. A common implementation is a minimal proxy pointing to a shared, immutable logic contract, which keeps deployment costs low while allowing for upgradability via the proxy's immutable logic address.

The Paymaster is an optional contract that can sponsor transaction fees, enabling gasless experiences or payments in ERC-20 tokens. It implements validatePaymasterUserOp for upfront validation and postOp for post-execution logic. For batch operations, a paymaster can sponsor the entire bundle's gas, which is crucial for complex multi-step interactions where upfront native token costs would be prohibitive. Popular paymaster strategies include verifying a DApp-sponsored signature or charging a stablecoin fee deposited in the Account.

The flow for a batched user operation begins when a bundler receives a UserOperation object. This object contains the target Account address, the calldata for the batch execution, paymaster data, and the signature. The bundler simulates the operation's validation by calling eth_call to the EntryPoint, ensuring it will pay for its gas. If successful, the bundler submits it in a real transaction.

Upon receiving the transaction, the EntryPoint executes a strict sequence: 1) Call Account.validateUserOp, 2) Call Paymaster.validatePaymasterUserOp (if present), 3) Call Account.executeBatch with the user's intended calls, 4) Call Paymaster.postOp. It tracks gas usage throughout, finally charging the Paymaster or the Account's deposit for the total cost. This atomic sequence ensures operations either fully succeed or revert, maintaining system integrity.

When architecting your Account contract for batching, key considerations include implementing efficient signature verification (like ECDSA or multisig), managing a nonce to prevent replay attacks, and designing an executeBatch function that loops through an array of target addresses and calldata. Testing against the official EntryPoint (0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 on mainnet) and using tools like the account-abstraction SDK from Stackup or Alchemy is essential for development.

key-concepts
ARCHITECTURE

Key Concepts for Batch Wallet Design

Designing a smart contract wallet for batch operations requires understanding core primitives for gas efficiency, security, and user experience. This guide covers the essential components.

04

Security: Signature Aggregation

Batch wallets must securely authorize multiple actions. Instead of individual ECDSA signatures, use:

  • EIP-1271: Standard for contract signature validation (isValidSignature).
  • Signature Aggregation: Schemes like BLS or Schnorr can combine approvals for multiple operations into one proof, reducing calldata size.
  • Session Keys: Grant limited permissions (e.g., a spending limit for 24 hours) to enable batch signing without repeated private key access. This balances security with usability.
05

State Management & Nonce Handling

Managing transaction ordering and state is complex in batch contexts.

  • Atomic Nonces: Use a single nonce for the entire batch to guarantee order and prevent replay attacks across chains.
  • Partial Failure: Design logic to handle scenarios where some calls in a batch revert. Options include rolling back the entire batch or committing successful states.
  • Storage Optimization: Batch operations often interact with multiple contracts; use transient storage (EIP-1153) or careful slot management to minimize gas costs from SSTORE operations.
ARCHITECTURE COMPARISON

Wallet Design: EOAs vs. ERC-4337 Smart Accounts

Key differences between traditional Externally Owned Accounts (EOAs) and ERC-4337 smart accounts for batch operation design.

Feature / MetricExternally Owned Account (EOA)ERC-4337 Smart Account

Account Abstraction

Batch Transaction Support

Gas Sponsorship (Paymaster)

Native Session Keys

Average Gas Cost for 5-op Batch

~1,000,000 gas

~500,000 gas

Transaction Replay Protection

Nonce-based

UserOperation hash-based

Account Recovery Mechanism

Seed phrase only

Social recovery, multi-sig

Signature Flexibility

ECDSA (secp256k1)

Any (ECDSA, BLS, MPC, etc.)

implementing-account-contract
ARCHITECTURE

Step 1: Implementing the Core Account Contract

This step establishes the foundational smart contract that defines your wallet's logic and state, enabling the execution of batch operations.

The core account contract is an ERC-4337-compliant smart contract wallet. Unlike an Externally Owned Account (EOA), it is a contract account, meaning its logic is defined by code you deploy. The primary responsibility of this contract is to validate and execute UserOperation bundles, which are the standard transaction objects in the Account Abstraction (AA) model. It must implement the core validateUserOp and execute functions as specified by the ERC-4337 IAccount interface.

A critical architectural decision is choosing between a singleton/proxy pattern and a minimal proxy (ERC-1167) pattern. For a scalable, gas-efficient wallet factory, deploying each user's wallet as a minimal proxy that points to a single, immutable implementation contract is standard. This separates logic from storage, allowing for wallet upgrades without migrating user assets. The implementation contract holds the validateUserOp logic, while each user's proxy contract stores their unique state, like nonces and signer addresses.

The validateUserOp function is the security gatekeeper. It must verify the operation's signature, paymaster sponsorship, and nonce. For batch operations, you implement signature aggregation here—instead of validating a signature for each individual action, you validate a single signature that authorizes the entire bundle. Common approaches include using EIP-1271 for contract signatures or BLS signature aggregation for advanced multi-party scenarios.

The execute or executeBatch function contains the business logic for processing transactions. For batch operations, you will call executeBatch(address[] targets, uint256[] values, bytes[] calldatas). This function iterates through the arrays and makes low-level call operations to each target contract. It's crucial to implement reentrancy guards (like OpenZeppelin's ReentrancyGuard) here, as batch execution interacts with multiple external contracts.

Finally, you must manage state correctly. Each wallet needs a nonce (sequential for security) to prevent replay attacks. For smart contract wallets, you typically use a non-sequential "key" nonce as defined by ERC-4337, which allows for more flexible transaction ordering. The contract must also track its entry point address and, if applicable, a list of authorized signers or modules that can initiate operations on behalf of the account.

integrating-paymaster
GAS ABSTRACTION

Step 2: Integrating a Paymaster for Gas Sponsorship

Enable users to pay transaction fees with ERC-20 tokens or have them sponsored entirely, removing a major UX barrier for smart contract wallet adoption.

A paymaster is a smart contract that can pay for a user's gas fees on their behalf, as defined by the ERC-4337 standard. This unlocks two primary sponsorship models: fee abstraction, where users pay with an ERC-20 token like USDC instead of the native chain token (e.g., ETH), and full sponsorship, where a dapp or service covers the cost entirely. The paymaster validates the user operation and, if the rules are met, deposits the required native currency to the EntryPoint contract to execute the transaction.

Architecting your smart contract wallet to work with a paymaster requires modifying the validateUserOp logic. Instead of the wallet itself verifying and paying for gas, it must delegate this check to the designated paymaster contract. Your wallet's validateUserOp function should extract the paymaster address from the UserOperation calldata, verify its validity, and then call the paymaster's validatePaymasterUserOp function. This function signature is function validatePaymasterUserOp(UserOperation calldata op, bytes32 userOpHash, uint256 maxCost) external returns (bytes memory context, uint256 validationData).

The paymaster's validation logic is where your business rules are enforced. For a token paymaster, it would check the user's ERC-20 balance and exchange rate. For a fully sponsored model, it might verify a signature from a whitelisted dapp backend. If validation passes, the paymaster must stake ETH in the EntryPoint's deposit (a paymasterStake) to guarantee it can cover the gas. The context bytes returned are passed back to the paymaster in a post-operation callback for final settlement, like deducting tokens from the user.

Here is a simplified code snippet showing how a smart contract wallet integrates a paymaster during validation:

solidity
function validateUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds)
    external override returns (uint256 validationData) {
    // ... existing signature & nonce validation ...

    address paymaster = userOp.paymaster;
    if (paymaster != address(0)) {
        // Delegate validation to the paymaster
        (bytes memory context, uint256 paymasterValidationData) = IPaymaster(paymaster).validatePaymasterUserOp(userOp, userOpHash, missingAccountFunds);
        // Store context for postOp if needed
        _paymasterContext = context;
        validationData = paymasterValidationData;
    }
    // ... handle native gas payment if no paymaster ...
}

After the user operation is executed, the EntryPoint calls the paymaster's postOp function. This is critical for the accounting and settlement of the sponsored transaction. For a token paymaster, this is where it would safely transfer the calculated token amount from the user's wallet to itself, using the context to recall the agreed-upon fee. This two-phase process (validate then postOp) ensures atomicity: either the entire operation and its payment succeed, or everything reverts. Always audit paymaster logic for reentrancy and ensure it properly handles the gas budget provided by the EntryPoint.

For production, you can integrate with existing, audited paymaster services like Stackup's, Biconomy's, or Pimlico's infrastructure, or deploy your own for custom rules. Using a service simplifies gas management, provides token price oracles, and handles staking. When choosing, evaluate their supported networks, fee models, and reliability. Integrating a paymaster transforms your smart account from a novelty to a practical tool, enabling seamless onboarding and complex transaction flows without requiring users to hold native gas tokens.

adding-session-keys
ARCHITECTURE

Step 3: Adding Session Keys for Improved UX

Implement session keys to enable gasless, batched transactions without compromising wallet security.

A session key is a temporary, limited-authority key that allows a dApp to perform specific actions on behalf of a user's smart contract wallet for a predetermined period. This solves a critical UX problem: requiring a signature for every single transaction in a batch is cumbersome. Instead, a user signs one meta-transaction to grant a session key permission to execute a defined set of operations, like swapping tokens on Uniswap or depositing into Aave, for the next 24 hours. The session key itself is typically a fresh Ethereum key pair generated by the dApp's frontend.

Architecturally, you must modify your wallet's validateUserOp function to accept operations signed by a registered session key. This involves adding a storage mapping, like mapping(address => SessionKey) public sessionKeys, where the key is the session key address. The SessionKey struct should define the permissions: an expiry timestamp, a spendingLimit in ETH or ERC-20 tokens, and allowed targetContracts and functionSelectors. The validation logic must check that the current block timestamp is before the expiry, the call is to a permitted contract/function, and any value transfer is under the spending limit.

Here is a simplified code snippet for the validation step within your wallet's entry point:

solidity
function _validateSessionKey(
    UserOperation calldata userOp,
    SessionKey memory sessionKey
) internal view returns (bool) {
    if (sessionKey.expiry < block.timestamp) revert SessionExpired();
    if (sessionKey.spendingLimit < userOp.callData.value) revert SpendingLimitExceeded();
    // Decode callData to verify target and selector are allowed
    (address target, uint256 value, bytes memory data) = abi.decode(userOp.callData[4:], (address, uint256, bytes));
    bytes4 selector = bytes4(data);
    if (!sessionKey.allowedContracts[target] || !sessionKey.allowedSelectors[selector]) revert NotPermitted();
    return true;
}

The UserOperation signature field would contain the session key's signature, and the sender would be the smart wallet address.

For security, session keys must be strictly scoped. Never grant a blanket CALL permission. Common patterns include limiting a key to: a specific DEX pool address and the swap function, a lending protocol's deposit function for one asset, or an NFT marketplace's fulfillOrder function. The private key for the session should never leave the user's browser session and must be cleared upon expiry. Frameworks like ZeroDev's Kernel and Biconomy have built-in, audited session key modules you can integrate instead of building from scratch.

From a user flow perspective, the dApp frontend triggers a standard wallet signature request to enable the session. This request signs the session key's parameters (the permissions and expiry), which are then registered on-chain via a wallet transaction. Once registered, the dApp can use the session key to submit gasless UserOperations directly to a bundler, dramatically improving interactivity. The user experiences seamless, batchable actions without repeated pop-ups, while maintaining the security model of a smart contract wallet because the session key's powers are explicitly bounded and temporary.

bundling-user-operations
EXECUTION LAYER

Step 4: Bundling and Submitting User Operations

This step covers the final stage of the ERC-4337 flow: aggregating multiple actions into a single transaction and sending it to the network via a Bundler.

A Bundler is a network participant, often a node operator, that collects UserOperation objects from the mempool, packages them into a single transaction, and submits this bundle to the EntryPoint contract on the destination chain. This is the core mechanism that enables gas sponsorship and atomic batch execution. The Bundler pays the gas fees for the bundle transaction and is reimbursed by the EntryPoint using the gas fees provided by the individual user operations. Popular Bundler implementations include the official eth-infinitism/bundler and services like Stackup and Alchemy's Account Kit.

The bundling process involves several critical steps for security and efficiency. First, the Bundler simulates each UserOperation using eth_call to the EntryPoint's simulateValidation and simulateHandleOp methods. This simulation verifies the operation's validity and the smart account's ability to pay for its share of gas, preventing the Bundler from losing funds on invalid ops. After successful simulation, the Bundler creates a bundle transaction. This transaction's calldata is a call to the EntryPoint's handleOps function, which takes an array of UserOperation structs and the Bundler's address as parameters.

Here is a simplified look at the core bundling logic a service might implement:

solidity
// Pseudocode for a Bundler's submission logic
UserOperation[] memory ops = ...; // Collected from mempool
address payable beneficiary = ...; // The Bundler's address for gas reimbursement

for (uint i = 0; i < ops.length; i++) {
    // 1. Simulate validation and execution
    IEntryPoint(entryPoint).simulateValidation(ops[i]);
    // 2. (Optional) Simulate the full handleOp
    // simulateHandleOp(ops[i]);
}
// 3. Submit the actual bundle transaction
IEntryPoint(entryPoint).handleOps(ops, beneficiary);

The beneficiary address receives the gas compensation from the EntryPoint after the operations are executed.

For developers building smart accounts, understanding the Paymaster integration at this stage is crucial. If a UserOperation specifies a Paymaster, the EntryPoint will interact with it twice: first during simulation to ensure it will pay for the op, and again during execution to actually withdraw its funds. Your account's validateUserOp function must correctly handle the paymasterAndData field. The atomic nature of handleOps means either all operations in the bundle succeed, or the entire transaction reverts, ensuring consistent state changes across multiple user actions.

After submission, the EntryPoint contract becomes the orchestrator. It iterates through each UserOperation, validates signatures and nonces via the smart account, executes the intended actions, manages gas accounting, and compensates the Bundler. The final outcome is that multiple independent user intents, potentially sponsored by different Paymasters, are executed in a single on-chain transaction. This architecture is what makes ERC-4337 wallets scalable and user-friendly, abstracting away gas management and enabling complex, batched interactions with the blockchain.

SMART CONTRACT WALLETS

Frequently Asked Questions

Common questions and solutions for developers architecting smart contract wallets with batch operations.

Optimizing gas for batch operations is critical for user adoption. Key strategies include:

  • Batching Calls: Group multiple user operations into a single handleOps transaction. This amortizes the fixed overhead (like signature verification) across all operations in the batch.
  • Signature Aggregation: Use schemes like BLS or Schnorr to combine multiple signatures into one, drastically reducing calldata costs. ERC-4337 Bundlers often handle this.
  • Storage Minimization: Design your wallet's storage layout to use packed variables and immutable data where possible. Avoid writing to new storage slots during batch execution.
  • Gas Token Abstraction: Implement paymasters to allow transaction fees to be paid in ERC-20 tokens, shielding users from ETH gas price volatility. Use validatePaymasterUserOp for efficient validation.

Example: A batch of 10 token transfers using a single aggregated signature can be up to 80% cheaper than 10 individual EOA transactions.

conclusion
ARCHITECTURE REVIEW

Conclusion and Next Steps

You have explored the core components for building a smart contract wallet optimized for batch operations. This section summarizes the key architectural decisions and provides resources for further development.

Architecting a smart contract wallet for batch operations fundamentally shifts the user experience from sequential, gas-intensive interactions to efficient, atomic bundles. The core components you've implemented—a Wallet contract with executeBatch, a Relayer for gas sponsorship, and a Bundler for transaction aggregation—create a system where users can approve, swap, and transfer multiple assets in a single on-chain transaction. This architecture directly addresses the pain points of high fees and poor UX in native wallet interactions, making complex DeFi strategies economically viable.

Security remains the paramount concern. Your implementation should enforce strict validation: checking msg.sender against the Relayer contract, verifying user signatures via EIP-712 structured data hashing, and implementing nonce replay protection across chains. Consider integrating account abstraction (ERC-4337) standards for future-proofing, as they provide a formalized framework for bundlers, paymasters, and signature aggregation. Auditing your custom executeBatch logic for reentrancy and ensuring the relayer cannot manipulate the order of operations are critical next steps before mainnet deployment.

To extend your wallet's capabilities, explore integrating with specialized bundler services like Stackup, Pimlico, or Alchemy's Gas Manager. These services handle the complexities of mempool management and gas estimation for UserOperations. Furthermore, you can add support for session keys to allow limited, automated actions without constant signature requests, and implement social recovery mechanisms as a safeguard. Testing your entire flow on a testnet like Sepolia or Goerli, using tools like Foundry for fork testing, is essential to simulate real-world conditions.

The final step is to build the client-side integration. Your frontend application must generate the EIP-712 signature, construct the UserOperation object (if using ERC-4337), and interface with your chosen bundler API. Libraries like viem and wagmi provide excellent utilities for this. By combining a robust smart contract backend with a seamless frontend, you create a powerful tool that abstracts blockchain complexity, enabling users to execute sophisticated transaction batches with the simplicity of a single click.

How to Architect a Smart Contract Wallet for Batch Operations | ChainScore Guides