> For the complete documentation index, see [llms.txt](https://blockchain-journal-hope-mabuza.gitbook.io/blockchain-journal-hope-mabuza-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://blockchain-journal-hope-mabuza.gitbook.io/blockchain-journal-hope-mabuza-docs/smart-contracts/week-9-aa-erc-4337-and-smart-wallets-useroperation.md).

# Week 9 : AA, ERC-4337 and Smart Wallets (UserOperation)

### Monday - 30 March

Today I worked on understanding how a **UserOperation (userOp)** is built in the context of ERC-4337 account abstraction.

Unlike normal transactions where a user sends directly to the blockchain, here the user submits a **userOp** to a bundler. The bundler then forwards it to the EntryPoint contract, which is responsible for both validation and execution.

So my main focus was simple:\
**correctly assemble every field of the userOp struct so the system works end-to-end.**

#### **Step 1 : Deploying the Infrastructure**

```
const accountFactory = await AccountFactory.deploy();
const entryPoint = await EntryPoint.deploy();
```

Before anything else, both contracts need to exist.

* **AccountFactory** → used to deploy smart accounts (using Create2)
* **EntryPoint** → handles validation, nonce tracking, and hashing

Without these two, nothing else in the flow works.

#### **Step 2 : Getting the Sender (Smart Account Address)**

```
const sender = await accountFactory.createAccount.staticCall(user1Address);
```

This part was interesting.

The `sender` is **not the user’s EOA**, it’s their **smart account address**.

Even if the account is not deployed yet, we can still know its address because:

* Create2 makes deployment **deterministic**
* The address is based on inputs like owner address and salt

The key thing here was using `staticCall`.

* A normal call → sends a transaction
* `staticCall` → simulates and returns the result (no gas, no state change)

So this gives me the address directly without deploying anything.

#### **Step 3 : Building the initCode**

```
const transaction = await accountFactory.createAccount(user1Address);
const calldata_ = transaction.data;
const initCode = accountFactoryAddress + calldata_.slice(2);
```

`initCode` tells EntryPoint how to deploy the account if it doesn’t exist.

Structure:

```
initCode = factoryAddress + calldata
```

Important detail:

* Remove `"0x"` using `.slice(2)`
* Otherwise the hex becomes invalid

So EntryPoint:

1. Takes first 20 bytes → factory address
2. Uses the rest → calldata to call `createAccount`

This step really connects Create2 with account abstraction.

#### **Step 4 : Building callData**

This is what the account will actually execute.

**Single call (`execute`)**

```
const callData = Account.interface.encodeFunctionData("execute", []);
```

* Produces the **function selector**
* No arguments → just 4 bytes

**Batch calls (`executeBatch`)**

```
const callData = Account.interface.encodeFunctionData("executeBatch", [
    [target1, target2, target3],
    [value1, value2, value3],
    [data1, data2, data3]
]);
```

Here I learned the real power of account abstraction.

Instead of multiple transactions:\
→ we bundle everything into **one userOp**

Structure:

* `dest[]` → where to call
* `value[]` → ETH per call
* `func[]` → encoded function calls

All arrays must match in length or it reverts.

So essentially:

> pointer (target) + value + encoded function

#### **Step 5 : Assembling the userOp**

```
let userOp = {
    sender: sender,
    nonce: await entryPoint.getNonce(sender, 0),
    initCode: initCode,
    callData: callData,
    callGasLimit: 10000,
    verificationGasLimit: 10000,
    preVerificationGas: 10000,
    maxFeePerGas: 10000,
    maxPriorityFeePerGas: 10000,
    paymasterAndData: "0x",
    signature: "0x"
}
```

Key things I noted:

* **nonce** → prevents replay (tracked by EntryPoint)
* **paymasterAndData = "0x"** → no gas sponsor (self-paying)
* **signature = "0x"** → placeholder (important for hashing step)

The struct must be complete **before signing**.

#### **Step 6 :** **Signing the userOpHash**

```
const hashedUserOp = await entryPoint.getUserOpHash(userOp);
userOp.signature = await user1.signMessage(ethers.getBytes(hashedUserOp));
```

We don’t sign the struct directly.

Why?

Because:

* userOp contains dynamic fields (arrays, bytes)
* no consistent encoding format

So EntryPoint:\
→ creates a **deterministic hash**

This hash includes:

* userOp data
* chainId
* EntryPoint address

This prevents:

* replay across chains
* replay across contracts

On-chain validation:

```
address recovered = ECDSA.recover(
    ECDSA.toEthSignedMessageHash(userOpHash),
    userOp.signature
);
```

If recovered address == owner → valid

***

### Wednesday - 01 April

From our workshop with Karabo today, something important clicked for all of us as a group.

At first, the ERC-4337 flow made sense in our heads as:

> **UserOp → Mempool → Bundler → EntryPoint → Bundler → Blockchain**

But when we actually worked on the **userOp code**, we noticed something different.

In the code, the **EntryPoint interacts directly with the userOp**, especially in steps like generating the `userOpHash` and verifying the signature. There isn’t a visible “bundler step” between the mempool and EntryPoint when you look at the contract logic itself.

That’s when it clicked for all of us:

* The **bundler is not part of the on-chain logic**
* The EntryPoint simply works with the userOp directly

The bundler’s role is much simpler than we initially thought, it **collects the userOps and takes them to the blockchain**.

Also, thinking about it logically, it wouldn’t make sense for bundlers to submit userOps that are not valid, because they would risk losing money.

#### **Next Step**

Our next step is to move beyond just constructing the userOp and actually send it on-chain. We are not going to implement our own bundler. Instead, we will learn how to use the **Alchemy bundler and paymaster**.

#### **Reflection**

This session helped all of us connect the theory with what is actually happening in the code. We’re starting to see that ERC-4337 is not just about smart contracts, it’s a full system that includes both on-chain logic and external infrastructure.

***

### Friday - 03 April

Today we attempted to implement the **Alchemy bundler** into our flow.

We were able to successfully get:

* the API details
* and the paymaster information from Alchemy

So that part is now set up and ready to be used.

#### **Next Step**

Our next step is to go deeper into the documentation and understand exactly how to integrate it properly into our **userOp flow**.

Right now, we have the pieces, but we still need to:

* connect everything correctly
* and make sure the userOp can actually be sent through the bundler

#### **Reflection**

This step feels like we are moving from just understanding the concept to actually working with real infrastructure.

It’s one thing to build the userOp locally, but now we are starting to:

* interact with external services
* and move closer to sending real operations on-chain

***
