<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Decrypted Bytes]]></title><description><![CDATA[Decrypted Bytes]]></description><link>https://decryptedbytes.com</link><generator>RSS for Node</generator><lastBuildDate>Tue, 14 Apr 2026 01:52:29 GMT</lastBuildDate><atom:link href="https://decryptedbytes.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Developer’s Guide to ERC-4337 #1 | Developing Simple Account]]></title><description><![CDATA[Lately, I’ve been exploring ERC-4337, which introduces account abstraction with an alternative mempool. It’s a really exciting topic because it lets developers hide much of the complexity with wallet management and dapp interaction, making the user e...]]></description><link>https://decryptedbytes.com/developers-guide-to-erc-4337-1-developing-simple-account</link><guid isPermaLink="true">https://decryptedbytes.com/developers-guide-to-erc-4337-1-developing-simple-account</guid><category><![CDATA[Solidity]]></category><category><![CDATA[account abstraction]]></category><category><![CDATA[Ethereum]]></category><category><![CDATA[Blockchain]]></category><dc:creator><![CDATA[Nikhil Bhintade]]></dc:creator><pubDate>Fri, 04 Oct 2024 06:22:10 GMT</pubDate><content:encoded><![CDATA[<p>Lately, I’ve been exploring ERC-4337, which introduces account abstraction with an alternative mempool. It’s a really exciting topic because it lets developers hide much of the complexity with wallet management and dapp interaction, making the user experience more seamless.</p>
<p>I’m creating this series for developers who want to get hands-on with ERC-4337. This isn’t a series to learn about pros and cons of account abstraction. Instead, this series will focus on the ERC-4337 specification and walk through building account contracts using it.</p>
<p>Since we’ll be working with ERC-4337, I originally planned to explain concepts as we need them. But I realized it’s more helpful to start with an overview so you can understand the bigger picture upfront. So, let’s dive in and get familiar with ERC-4337!</p>
<h2 id="heading-understanding-erc-4337-what-it-is-and-how-it-works">Understanding ERC-4337: What It Is and How It Works</h2>
<p>ERC-4337 defines how account abstraction should work on Ethereum or any EVM-compatible chain without changing the consensus layer. It introduces two key ideas: the <strong>UserOperation</strong> and the <strong>Alt Mempool</strong>, which are higher-layer components that rely on existing blockchain infrastructure.</p>
<p>A <strong>UserOperation</strong> is a high-level, pseudo-transaction object that holds both intent and verification data. Unlike regular transactions that are sent to validators, UserOperations are sent to <strong>bundlers</strong> through public or private alt mempools. These bundlers collect multiple UserOperations, bundle them together into a single transaction, and submit them to get included on the blockchain.</p>
<p>When bundlers send these UserOperations, they interact with a special contract called <strong>EntryPoint</strong>. The EntryPoint contract is responsible for validating and executing UserOperations. However, it doesn’t handle verification itself. Instead, the verification logic is stored in the <strong>Account Contract</strong>, which is the user’s smart contract wallet.</p>
<p>The <strong>Account Contract</strong> contains both the validation and execution logic for UserOperations. The ERC-4337 specification defines a standard interface for these contracts, ensuring they follow a consistent structure. Here's the <code>IAccount</code> interface from the spec:</p>
<pre><code class="lang-solidity"><span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">IAccount</span> </span>{
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">validateUserOp</span>
      (<span class="hljs-params">PackedUserOperation <span class="hljs-keyword">calldata</span> userOp, <span class="hljs-keyword">bytes32</span> userOpHash, <span class="hljs-keyword">uint256</span> missingAccountFunds</span>)
      <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span> validationData</span>)</span>;
}
</code></pre>
<p>When a bundler submits a UserOperation to the EntryPoint contract, the EntryPoint first calls the <code>validateUserOp</code> function on the user’s account contract. If the UserOperation is valid, the function returns <code>SIG_VALIDATION_SUCCESS</code>. If validation is successful, the EntryPoint proceeds to execute the UserOperation's calldata on the account contract.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728022825170/b5bcaab7-627b-49d2-a1b0-525b78e13215.png" alt class="image--center mx-auto" /></p>
<p>That’s a high-level overview of account abstraction with ERC-4337. Next, we’ll dive into the code and break down the key components of ERC-4337 starting with account contract in more detail. Let’s get started!</p>
<h2 id="heading-setting-up-the-project">Setting Up the Project</h2>
<p>We'll be using <strong>Foundry</strong> to write and test our smart contracts. If you’re not familiar with Foundry, I recommend checking out the <a target="_blank" href="https://book.getfoundry.sh/">Foundry documentation</a> to get up to speed.</p>
<p>To create a new Foundry project, run the following commands in your terminal:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Creates a new directory named 'simple-account'</span>
mkdir simple-account

<span class="hljs-comment"># Navigates into the 'simple-account' directory</span>
<span class="hljs-built_in">cd</span> simple-account

<span class="hljs-comment"># Initializes a new Foundry project</span>
forge init
</code></pre>
<p>Next, we need to install some dependencies that will help us develop our account contract. The <strong>eth-infinitism</strong> team has created an implementation of ERC-4337, which we’ll use as a base. To add it to our project, run the following command:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Installs the ERC-4337 implementation from eth-infinitism</span>
forge install eth-infinitism/account-abstraction@v0.7.0 --no-commit
</code></pre>
<p>Lastly, clean up the default files by removing everything inside the <code>src</code>, <code>script</code>, and <code>test</code> folders. Then, create a new file named <code>SimpleAccount.sol</code> inside the <code>src</code> folder.</p>
<p>With this, your project setup is complete, and you’re ready to start developing the smart contract!</p>
<h2 id="heading-baseaccountsol">BaseAccount.sol</h2>
<p>Much like with ERC-20 tokens, we don’t need to build everything from scratch. We can use <code>BaseAccount.sol</code> from the <code>eth-infinitism/account-abstraction</code> package as a foundation. Here's the <code>BaseAccount.sol</code> implementation:</p>
<pre><code class="lang-solidity"><span class="hljs-comment">// SPDX-License-Identifier: GPL-3.0</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.23;</span>

<span class="hljs-comment">/* solhint-disable avoid-low-level-calls */</span>
<span class="hljs-comment">/* solhint-disable no-empty-blocks */</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"../interfaces/IAccount.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"../interfaces/IEntryPoint.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./UserOperationLib.sol"</span>;

<span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">BaseAccount</span> <span class="hljs-keyword">is</span> <span class="hljs-title">IAccount</span> </span>{
    <span class="hljs-keyword">using</span> <span class="hljs-title">UserOperationLib</span> <span class="hljs-title"><span class="hljs-keyword">for</span></span> <span class="hljs-title">PackedUserOperation</span>;

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getNonce</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">virtual</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
        <span class="hljs-keyword">return</span> entryPoint().getNonce(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), <span class="hljs-number">0</span>);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">entryPoint</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">virtual</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params">IEntryPoint</span>)</span>;

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">validateUserOp</span>(<span class="hljs-params">
        PackedUserOperation <span class="hljs-keyword">calldata</span> userOp,
        <span class="hljs-keyword">bytes32</span> userOpHash,
        <span class="hljs-keyword">uint256</span> missingAccountFunds
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">virtual</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span> validationData</span>) </span>{
        _requireFromEntryPoint();
        validationData <span class="hljs-operator">=</span> _validateSignature(userOp, userOpHash);
        _validateNonce(userOp.nonce);
        _payPrefund(missingAccountFunds);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_requireFromEntryPoint</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">virtual</span></span> </span>{
        <span class="hljs-built_in">require</span>(
            <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(entryPoint()),
            <span class="hljs-string">"account: not from EntryPoint"</span>
        );
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_validateSignature</span>(<span class="hljs-params">
        PackedUserOperation <span class="hljs-keyword">calldata</span> userOp,
        <span class="hljs-keyword">bytes32</span> userOpHash
    </span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">virtual</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span> validationData</span>)</span>;

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_validateNonce</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> nonce</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">virtual</span></span> </span>{
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_payPrefund</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> missingAccountFunds</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">virtual</span></span> </span>{
        <span class="hljs-keyword">if</span> (missingAccountFunds <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            (<span class="hljs-keyword">bool</span> success, ) <span class="hljs-operator">=</span> <span class="hljs-keyword">payable</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>).<span class="hljs-built_in">call</span>{
                <span class="hljs-built_in">value</span>: missingAccountFunds,
                <span class="hljs-built_in">gas</span>: <span class="hljs-keyword">type</span>(<span class="hljs-keyword">uint256</span>).<span class="hljs-built_in">max</span>
            }(<span class="hljs-string">""</span>);
            (success);
            <span class="hljs-comment">//ignore failure (its EntryPoint's job to verify, not account.)</span>
        }
    }
}
</code></pre>
<p>Before diving into the code, let’s first understand some key specifications that ERC-4337 defines for account contracts. These are important to understand before moving forward:</p>
<ul>
<li><p><strong>Trusted EntryPoint:</strong> The contract MUST verify that the caller is a trusted EntryPoint.</p>
</li>
<li><p><strong>Signature Validation:</strong> If the account doesn't support signature aggregation, the contract MUST ensure that the signature is valid for the <code>userOpHash</code>. On a signature mismatch, it SHOULD return <code>SIG_VALIDATION_FAILED</code> (instead of reverting). Any other errors MUST cause a revert.</p>
</li>
<li><p><strong>Paying EntryPoint:</strong> The contract MUST ensure the EntryPoint (the caller) is paid at least the <code>missingAccountFunds</code>, which may be zero if the account’s deposit is already sufficient.</p>
</li>
</ul>
<p>The provided <code>BaseAccount.sol</code> implementation satisfies all these requirements in the <code>validateUserOp</code> function.</p>
<pre><code class="lang-solidity"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">validateUserOp</span>(<span class="hljs-params">
    PackedUserOperation <span class="hljs-keyword">calldata</span> userOp,
    <span class="hljs-keyword">bytes32</span> userOpHash,
    <span class="hljs-keyword">uint256</span> missingAccountFunds
</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">virtual</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span> validationData</span>) </span>{
    _requireFromEntryPoint();
    validationData <span class="hljs-operator">=</span> _validateSignature(userOp, userOpHash);
    _validateNonce(userOp.nonce);
    _payPrefund(missingAccountFunds);
}
</code></pre>
<p>Let’s break down what happens in this function:</p>
<ol>
<li><p><strong>_requireFromEntryPoint:</strong> This checks that the caller is the trusted EntryPoint contract.</p>
</li>
<li><p><strong>_validateSignature:</strong> This function handles the signature validation logic, which we will implement in our contract.</p>
</li>
<li><p><strong>_payPrefund:</strong> This ensures that the EntryPoint is paid the <code>missingAccountFunds</code>, fulfilling the requirement of paying at least the minimum amount.</p>
</li>
<li><p><strong>_validateNonce:</strong> This checks the transaction nonce, preventing replay attacks.</p>
</li>
</ol>
<h3 id="heading-why-do-we-have-a-nonce">Why Do We Have a Nonce?</h3>
<p>EOAs (Externally Owned Accounts) have a nonce to prevent replay attacks, ensuring that each transaction is unique. Now that we are building a smart contract wallet, we need a nonce for the same purpose. While we could define our own nonce validation logic, EntryPoint already handles this in a similar way, so we don’t need to focus too much on it for now.</p>
<p>In summary, the <code>validateUserOp</code> function satisfies all the key requirements from the ERC-4337 specification.</p>
<h2 id="heading-simpleaccountsol">SimpleAccount.sol</h2>
<p>Now that we've met the core requirements, what’s left? From the <code>BaseAccount</code> contract, we see that when inheriting it, we need to implement two key functions: <code>_validateSignature</code> and <code>entryPoint</code>. These two functions are essential to fulfill the basic requirements.</p>
<p>In addition to these, we also need a function to allow interaction with other accounts and contracts. ERC-4337 doesn’t specify what this function should be named, and it’s not important since the <code>EntryPoint</code> contract will execute the calldata directly on the account contract.</p>
<p>Let’s begin by implementing the <code>entryPoint</code> function, which is the simplest. Our goal is to define the trusted <code>entryPoint</code> and return its address when the function is called.</p>
<h3 id="heading-entrypoint-function">EntryPoint Function</h3>
<p>We’ll start by developing <code>SimpleAccount.sol</code>. Begin by creating the <code>SimpleAccount</code> contract and importing <code>BaseAccount</code> from the <code>BaseAccount.sol</code> file. This contract will inherit from <code>BaseAccount</code>.</p>
<pre><code class="lang-solidity"><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> 0.8.24;</span>

<span class="hljs-keyword">import</span> {<span class="hljs-title">BaseAccount</span>} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"account-abstraction/core/BaseAccount.sol"</span>;

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">SimpleAccount</span> <span class="hljs-keyword">is</span> <span class="hljs-title">BaseAccount</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) </span>{}
}
</code></pre>
<p>Next, we need to define the trusted <code>EntryPoint</code> in the constructor. The <code>EntryPoint</code> address will be passed in as a parameter and stored in an immutable variable called <code>i_entryPoint</code>, which is of type <code>IEntryPoint</code>.</p>
<pre><code class="lang-solidity"><span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">SimpleAccount</span> <span class="hljs-keyword">is</span> <span class="hljs-title">BaseAccount</span> </span>{
    IEntryPoint <span class="hljs-keyword">private</span> <span class="hljs-keyword">immutable</span> i_entryPoint;

    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> entryPointAddress</span>) </span>{
        i_entryPoint <span class="hljs-operator">=</span> IEntryPoint(entryPointAddress);
    }
}
</code></pre>
<p>We'll also need to import the <code>IEntryPoint</code> interface from the <code>account-abstraction</code> module.</p>
<pre><code class="lang-solidity"><span class="hljs-keyword">import</span> {<span class="hljs-title">IEntryPoint</span>} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"account-abstraction/interfaces/IEntryPoint.sol"</span>;
</code></pre>
<p>Finally, we implement the <code>entryPoint</code> function, which will return the trusted <code>i_entryPoint</code>. This function will be a <code>view</code> function and will override the inherited function from <code>BaseAccount</code>, so we’ll add the <code>override</code> keyword.</p>
<pre><code class="lang-solidity"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">entryPoint</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params">IEntryPoint</span>) </span>{
    <span class="hljs-keyword">return</span> i_entryPoint;
}
</code></pre>
<p>At this point, your contract should look like this:</p>
<pre><code class="lang-solidity"><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> 0.8.24;</span>

<span class="hljs-keyword">import</span> {<span class="hljs-title">BaseAccount</span>} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"account-abstraction/core/BaseAccount.sol"</span>;
<span class="hljs-keyword">import</span> {<span class="hljs-title">IEntryPoint</span>} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"account-abstraction/interfaces/IEntryPoint.sol"</span>;

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">SimpleAccount</span> <span class="hljs-keyword">is</span> <span class="hljs-title">BaseAccount</span> </span>{
    IEntryPoint <span class="hljs-keyword">private</span> <span class="hljs-keyword">immutable</span> i_entryPoint;

    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> entryPointAddress</span>) </span>{
        i_entryPoint <span class="hljs-operator">=</span> IEntryPoint(entryPointAddress);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">entryPoint</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params">IEntryPoint</span>) </span>{
        <span class="hljs-keyword">return</span> i_entryPoint;
    }
}
</code></pre>
<p>With this, you’ve successfully set up the basic structure of the <code>SimpleAccount</code> contract, defining the trusted <code>EntryPoint</code>. The next step would be to implement the <code>_validateSignature</code> and the function that allows interaction with other contracts/accounts.</p>
<h3 id="heading-account-contract-owner">Account Contract Owner</h3>
<p>Developers can implement any method they prefer to validate a <code>UserOperation</code>, but for this series, we will use an ECDSA signature for validation. To perform this validation, we need to have the owner of the account contract. The owner's address will be provided in the constructor and stored in an immutable variable, <code>i_owner</code>.</p>
<p>Here’s how we define the owner in the constructor:</p>
<pre><code class="lang-solidity"><span class="hljs-comment">// Variable to store the owner's address</span>
<span class="hljs-keyword">address</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">immutable</span> i_owner;

<span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> entryPointAddress, <span class="hljs-keyword">address</span> owner</span>) </span>{
    i_entryPoint <span class="hljs-operator">=</span> IEntryPoint(entryPointAddress);
    <span class="hljs-comment">// Set the owner</span>
    i_owner <span class="hljs-operator">=</span> owner;
}
</code></pre>
<p>We also want to add a getter function, <code>getOwner</code>, which returns the owner's address:</p>
<pre><code class="lang-solidity"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getOwner</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">address</span></span>) </span>{
    <span class="hljs-keyword">return</span> i_owner;
}
</code></pre>
<p>Next, we’ll work on the <code>validateUserOp</code> function, but before diving into that, let's first understand the <code>UserOperation</code> object and the data it contains.</p>
<h3 id="heading-useroperation">UserOperation</h3>
<p>We've mentioned the <code>UserOperation</code> object several times, but what exactly does it contain?</p>
<p>The <code>UserOperation</code> consists of common fields like the sender, nonce, gas details, calldata, and more—similar to what you’d find in a typical transaction. In addition to these standard fields, it includes extra ones, like <code>accountFactory</code>, <code>paymaster</code>, and <code>signature</code>. Don’t worry about the extra fields just yet; we’ll explore them later in this series.</p>
<p>Once the <code>UserOperation</code> is created by the user, it is sent to a bundler, which consolidates the fields and compresses the operation into a more compact format called <code>PackedUserOperation</code>. This <code>PackedUserOperation</code> will be sent to <code>EntryPoint</code>. Here's what that looks like:</p>
<pre><code class="lang-solidity"><span class="hljs-keyword">struct</span> <span class="hljs-title">PackedUserOperation</span> {
    <span class="hljs-keyword">address</span> sender;
    <span class="hljs-keyword">uint256</span> nonce;
    <span class="hljs-keyword">bytes</span> initCode;
    <span class="hljs-keyword">bytes</span> callData;
    <span class="hljs-keyword">bytes32</span> accountGasLimits;
    <span class="hljs-keyword">uint256</span> preVerificationGas;
    <span class="hljs-keyword">bytes32</span> gasFees;
    <span class="hljs-keyword">bytes</span> paymasterAndData;
    <span class="hljs-keyword">bytes</span> signature;
}
</code></pre>
<p>At this point, we only need to focus on two fields: <code>calldata</code>, which is used for execution, and <code>signature</code>, which is used to validate the <code>UserOperation</code>.</p>
<h3 id="heading-validateuserop-function"><code>validateUserOp</code> Function</h3>
<p>The <code>validateUserOp</code> function checks the validity of a <code>UserOperation</code>. As we’ve seen in the <code>BaseAccount</code> contract part of this article, this function is already implemented. However, the portion that handles signature validation—via the <code>_validateSignature</code> function—needs to be implemented by the developer (in this case, us).</p>
<p>We’ll use an ECDSA signature to verify that the <code>signature</code> in the <code>UserOperation</code> is signed by the account contract’s owner. If the signer matches the owner, we return <code>SIG_VALIDATION_SUCCESS</code>. If not, we return <code>SIG_VALIDATION_FAILED</code>.</p>
<p>To verify the ECDSA signature, we can use the <code>ecrecover</code> precompile, but it’s more efficient to use the OpenZeppelin ECDSA library, which simplifies the process and eliminates a lot of manual steps. Let’s install OpenZeppelin:</p>
<pre><code class="lang-bash">forge install OpenZeppelin/openzeppelin-contracts@v5.0.2 --no-commit
</code></pre>
<p>If installed correctly, you should see a message like this:</p>
<pre><code class="lang-bash">Installed openzeppelin-contracts v5.0.2
</code></pre>
<p>Now that OpenZeppelin is installed, we can begin writing the <code>_validateSignature</code> function. The function signature is already defined in <code>BaseAccount</code>, so we will override it:</p>
<pre><code class="lang-solidity"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_validateSignature</span>(<span class="hljs-params">PackedUserOperation <span class="hljs-keyword">calldata</span> userOp, <span class="hljs-keyword">bytes32</span> userOpHash</span>)
    <span class="hljs-title"><span class="hljs-keyword">internal</span></span>
    <span class="hljs-title"><span class="hljs-keyword">view</span></span>
    <span class="hljs-title"><span class="hljs-keyword">override</span></span>
    <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{}
</code></pre>
<p>As this function uses the <code>PackedUserOperation</code> struct, we need to import it from the <code>account-abstraction</code> package. Additionally, we'll import the constants used for signature validation (<code>SIG_VALIDATION_SUCCESS</code> and <code>SIG_VALIDATION_FAILED</code>) from the same package.</p>
<pre><code class="lang-solidity"><span class="hljs-keyword">import</span> {<span class="hljs-title">PackedUserOperation</span>} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"account-abstraction/interfaces/PackedUserOperation.sol"</span>;
<span class="hljs-keyword">import</span> {<span class="hljs-title">SIG_VALIDATION_FAILED</span>, <span class="hljs-title">SIG_VALIDATION_SUCCESS</span>} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"account-abstraction/core/Helpers.sol"</span>;
</code></pre>
<p>Next, we need to convert the <code>userOpHash</code> into a structured message. We will use OpenZeppelin’s <code>MessageHashUtils.toEthSignedMessageHash</code> function to convert <code>userOpHash</code> into the appropriate Ethereum Signed Message format. To recover the signer's address from this structured message and the provided signature, we’ll use OpenZeppelin’s <code>ECDSA.recover</code> function. Let’s import these libraries before writing the function.</p>
<pre><code class="lang-solidity"><span class="hljs-keyword">import</span> {<span class="hljs-title">MessageHashUtils</span>} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"</span>;
<span class="hljs-keyword">import</span> {<span class="hljs-title">ECDSA</span>} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"@openzeppelin/contracts/utils/cryptography/ECDSA.sol"</span>;
</code></pre>
<p>Now, let’s use these libraries to retrieve the address of the signer and store it in the <code>messageSigner</code> variable.</p>
<pre><code class="lang-solidity"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_validateSignature</span>(<span class="hljs-params">PackedUserOperation <span class="hljs-keyword">calldata</span> userOp, <span class="hljs-keyword">bytes32</span> userOpHash</span>)
    <span class="hljs-title"><span class="hljs-keyword">internal</span></span>
    <span class="hljs-title"><span class="hljs-keyword">view</span></span>
    <span class="hljs-title"><span class="hljs-keyword">override</span></span>
    <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>)
</span>{
    <span class="hljs-keyword">bytes32</span> digest <span class="hljs-operator">=</span> MessageHashUtils.toEthSignedMessageHash(userOpHash);
    <span class="hljs-keyword">address</span> messageSigner <span class="hljs-operator">=</span> ECDSA.recover(digest, userOp.signature);
}
</code></pre>
<p>Now that we have the <code>messageSigner</code>, we can compare it with the <code>i_owner</code>. If they match, we return <code>SIG_VALIDATION_SUCCESS</code>; otherwise, we return <code>SIG_VALIDATION_FAILED</code>:</p>
<pre><code class="lang-solidity"><span class="hljs-keyword">if</span> (messageSigner <span class="hljs-operator">=</span><span class="hljs-operator">=</span> i_owner) {
    <span class="hljs-keyword">return</span> SIG_VALIDATION_SUCCESS;
} <span class="hljs-keyword">else</span> {
    <span class="hljs-keyword">return</span> SIG_VALIDATION_FAILED;
}
</code></pre>
<p>Here’s the complete <code>_validateSignature</code> function:</p>
<pre><code class="lang-solidity"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_validateSignature</span>(<span class="hljs-params">PackedUserOperation <span class="hljs-keyword">calldata</span> userOp, <span class="hljs-keyword">bytes32</span> userOpHash</span>)
    <span class="hljs-title"><span class="hljs-keyword">internal</span></span>
    <span class="hljs-title"><span class="hljs-keyword">view</span></span>
    <span class="hljs-title"><span class="hljs-keyword">override</span></span>
    <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>)
</span>{
    <span class="hljs-keyword">bytes32</span> digest <span class="hljs-operator">=</span> MessageHashUtils.toEthSignedMessageHash(userOpHash);
    <span class="hljs-keyword">address</span> messageSigner <span class="hljs-operator">=</span> ECDSA.recover(digest, userOp.signature);

    <span class="hljs-keyword">if</span> (messageSigner <span class="hljs-operator">=</span><span class="hljs-operator">=</span> i_owner) {
        <span class="hljs-keyword">return</span> SIG_VALIDATION_SUCCESS;
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">return</span> SIG_VALIDATION_FAILED;
    }
}
</code></pre>
<p>This completes the signature validation logic, ensuring the <code>UserOperation</code> is signed by the account owner. The next step is to implement functionality for interacting with external accounts or contracts.</p>
<h3 id="heading-execute-function"><code>execute</code> Function</h3>
<p>This function allows the account contract to interact with external contracts or accounts. While the function's name is arbitrary (since the <code>EntryPoint</code> contract directly executes the calldata), in this contract, we'll call it <code>execute</code>. The user or dapp constructing the <code>UserOperation</code> will need to construct calldata for execution.</p>
<p>The <code>execute</code> function will take three arguments:</p>
<ul>
<li><p><code>dest</code>: The address of the contract or account to be called.</p>
</li>
<li><p><code>value</code>: The amount of ETH to be sent with the call.</p>
</li>
<li><p><code>funcCallData</code>: The calldata to be executed on the target address.</p>
</li>
</ul>
<p>Here’s the basic function structure:</p>
<pre><code class="lang-solidity"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">execute</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> dest, <span class="hljs-keyword">uint256</span> value, <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> funcCallData</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{}
</code></pre>
<p>Since this function allows transferring assets and executing actions on behalf of the user (i.e., the owner of the account contract), it must be protected to prevent unauthorized access. To enforce access control, we'll use the <code>_requireFromEntryPoint</code> function provided by the <code>BaseAccount</code> contract, making sure that only entry points can call this function.</p>
<p>Next, the contract will perform the external call, and we’ll check if it was successful. If the call fails, we'll revert the transaction with a custom error, <code>SimpleAccount__CallFailed</code>, which we define at the top of the contract.</p>
<p>Here’s how the error is defined, and it should be added at the top of the contract:</p>
<pre><code class="lang-solidity"><span class="hljs-function"><span class="hljs-keyword">error</span> <span class="hljs-title">SimpleAccount__CallFailed</span>(<span class="hljs-params"></span>)</span>;
</code></pre>
<p>The complete <code>execute</code> function is as follows:</p>
<pre><code class="lang-solidity"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">execute</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> dest, <span class="hljs-keyword">uint256</span> value, <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> funcCallData</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
    _requireFromEntryPoint(); <span class="hljs-comment">// Restrict access to valid entry points</span>
    (<span class="hljs-keyword">bool</span> success, <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> result) <span class="hljs-operator">=</span> dest.<span class="hljs-built_in">call</span>{<span class="hljs-built_in">value</span>: value}(funcCallData);

    <span class="hljs-keyword">if</span> (<span class="hljs-operator">!</span>success) {
        <span class="hljs-keyword">revert</span> SimpleAccount__CallFailed(result); <span class="hljs-comment">// Revert if the call fails</span>
    }
}
</code></pre>
<p>With this function in place, we now have a simple account contract that can interact with other contracts and accounts, complying with ERC-4337 and compatible with the <code>EntryPoint</code> contract and bundlers.</p>
<p>Here is the completed code for SimpleAccount:</p>
<pre><code class="lang-solidity"><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> 0.8.24;</span>

<span class="hljs-keyword">import</span> {<span class="hljs-title">BaseAccount</span>} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"account-abstraction/core/BaseAccount.sol"</span>;
<span class="hljs-keyword">import</span> {<span class="hljs-title">IEntryPoint</span>} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"account-abstraction/interfaces/IEntryPoint.sol"</span>;
<span class="hljs-keyword">import</span> {<span class="hljs-title">PackedUserOperation</span>} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"account-abstraction/interfaces/PackedUserOperation.sol"</span>;
<span class="hljs-keyword">import</span> {<span class="hljs-title">SIG_VALIDATION_FAILED</span>, <span class="hljs-title">SIG_VALIDATION_SUCCESS</span>} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"account-abstraction/core/Helpers.sol"</span>;

<span class="hljs-keyword">import</span> {<span class="hljs-title">MessageHashUtils</span>} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"</span>;
<span class="hljs-keyword">import</span> {<span class="hljs-title">ECDSA</span>} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"@openzeppelin/contracts/utils/cryptography/ECDSA.sol"</span>;

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">SimpleAccount</span> <span class="hljs-keyword">is</span> <span class="hljs-title">BaseAccount</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">error</span> <span class="hljs-title">SimpleAccount__CallFailed</span>(<span class="hljs-params"></span>)</span>;

    IEntryPoint <span class="hljs-keyword">private</span> <span class="hljs-keyword">immutable</span> i_entryPoint;
    <span class="hljs-keyword">address</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">immutable</span> i_owner;

    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> entryPointAddress, <span class="hljs-keyword">address</span> owner</span>) </span>{
        i_entryPoint <span class="hljs-operator">=</span> IEntryPoint(entryPointAddress);
        i_owner <span class="hljs-operator">=</span> owner;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_validateSignature</span>(<span class="hljs-params">PackedUserOperation <span class="hljs-keyword">calldata</span> userOp, <span class="hljs-keyword">bytes32</span> userOpHash</span>)
        <span class="hljs-title"><span class="hljs-keyword">internal</span></span>
        <span class="hljs-title"><span class="hljs-keyword">view</span></span>
        <span class="hljs-title"><span class="hljs-keyword">override</span></span>
        <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>)
    </span>{
        <span class="hljs-keyword">bytes32</span> digest <span class="hljs-operator">=</span> MessageHashUtils.toEthSignedMessageHash(userOpHash);
        <span class="hljs-keyword">address</span> messageSigner <span class="hljs-operator">=</span> ECDSA.recover(digest, userOp.signature);

        <span class="hljs-keyword">if</span> (messageSigner <span class="hljs-operator">=</span><span class="hljs-operator">=</span> i_owner) {
            <span class="hljs-keyword">return</span> SIG_VALIDATION_SUCCESS;
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">return</span> SIG_VALIDATION_FAILED;
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">execute</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> dest, <span class="hljs-keyword">uint256</span> value, <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> funcCallData</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
        _requireFromEntryPoint();
        (<span class="hljs-keyword">bool</span> success,) <span class="hljs-operator">=</span> dest.<span class="hljs-built_in">call</span>{<span class="hljs-built_in">value</span>: value}(funcCallData);
        <span class="hljs-keyword">if</span> (<span class="hljs-operator">!</span>success) {
            <span class="hljs-keyword">revert</span> SimpleAccount__CallFailed();
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">entryPoint</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params">IEntryPoint</span>) </span>{
        <span class="hljs-keyword">return</span> i_entryPoint;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getOwner</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">address</span></span>) </span>{
        <span class="hljs-keyword">return</span> i_owner;
    }
}
</code></pre>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>In this article, we walked through the process of developing a simple account contract that meets the basic requirements of ERC-4337. We explored each function, along with the libraries we used, such as the <code>account-abstraction</code> package from eth-infinitism and OpenZeppelin’s ECDSA library.</p>
<p>We also touched on important ERC-4337 concepts like bundlers and the entry point, though we haven't fully explored them yet. We’ll cover them in more detail as we progress through this series.</p>
<h2 id="heading-next-steps">Next Steps</h2>
<p>In the next article, we’ll shift focus to testing this contract and to gain better grasp of <code>UserOperation</code>, including how to generate the <code>UserOperation</code> hash via the entry point. If you’re excited to dive in, you can start writing tests now, which will help improve your understanding and ensure that the contract works as expected.</p>
<p>I hope you enjoyed this article, and I hope you’ll follow along with the rest of the series!</p>
]]></content:encoded></item></channel></rss>