> For the complete documentation index, see [llms.txt](https://docs.greenowl.money/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.greenowl.money/smart-contracts/contracts/smartwallet.md).

# SmartWallet

**Gas-optimized ERC-4337 smart wallet with guardian social recovery and session keys**

## Overview

`SmartWallet` is a modular, upgradeable ERC-4337 account implementation designed for **AI agent autonomy**. It combines:

* **ERC-4337 account abstraction** — enables gasless transactions via bundlers and paymasters
* **Multi-signature recovery** — guardian-based social recovery with time-delayed execution
* **Signature validation** — supports ECDSA owner signatures and ERC-1271 contract signatures
* **Pull-based upgrades** — owner-initiated upgrades from factory (no centralized upgrade authority)
* **Gas optimizations** — immutable EntryPoint, packed structs, bitmap approvals, and EnumerableSet guardians

**Role in the Owl ecosystem:**

* Deployed by `SmartWalletFactory` via CREATE2 for deterministic addresses
* Integrated with `SessionKeyManager` for time/scope-limited agent keys
* Works with `WhitelistPaymaster` for gas sponsorship
* Used by **OpenClaw Agent** and **MCP servers** for autonomous DeFi operations

**Security model:**

* Owner has full control (can execute, add/remove guardians, upgrade)
* Guardians can only initiate/approve recovery (separation of duties)
* EntryPoint can execute UserOperations validated by owner signature
* Recovery has time delay + threshold approval + expiration window

***

## Key Architecture Features

| Feature                     | Implementation                        | Gas Savings               |
| --------------------------- | ------------------------------------- | ------------------------- |
| **Immutable EntryPoint**    | Set in constructor, never changes     | \~2100 gas per call       |
| **Packed RecoveryRequest**  | 32-byte struct fits in 1 storage slot | 1 SLOAD vs 5              |
| **Bitmap approvals**        | Single uint256 for guardian votes     | O(1) check                |
| **EnumerableSet guardians** | O(1) add/remove/contains              | No array iteration        |
| **Reentrancy guard**        | Protects execute/executeBatch/upgrade | Prevents callback attacks |

***

## Constants

| Name                  | Type      | Value     | Description                                                  |
| --------------------- | --------- | --------- | ------------------------------------------------------------ |
| `MAX_GUARDIANS`       | `uint256` | `3`       | Maximum number of guardians (sufficient for social recovery) |
| `MIN_RECOVERY_DELAY`  | `uint64`  | `1 hours` | Minimum delay before recovery execution                      |
| `MAX_RECOVERY_DELAY`  | `uint64`  | `30 days` | Maximum delay before recovery execution                      |
| `RECOVERY_EXPIRATION` | `uint64`  | `7 days`  | Time window after delay to execute recovery                  |

***

## State Variables

### Core State

| Variable      | Type          | Description                                                |
| ------------- | ------------- | ---------------------------------------------------------- |
| `owner`       | `address`     | ECDSA owner address (can execute, add guardians, upgrade)  |
| `factory`     | `address`     | Factory address for pull-based upgrades (not an authority) |
| `ENTRY_POINT` | `IEntryPoint` | Immutable ERC-4337 EntryPoint (set in constructor)         |

### Recovery Configuration

| Variable            | Type                       | Description                                 |
| ------------------- | -------------------------- | ------------------------------------------- |
| `recoveryDelay`     | `uint64`                   | Time delay before recovery can be executed  |
| `recoveryThreshold` | `uint256`                  | Minimum guardian approvals required         |
| `pendingRecovery`   | `RecoveryRequest`          | Current pending recovery request (if any)   |
| `_guardians`        | `EnumerableSet.AddressSet` | Set of guardian addresses (O(1) operations) |

***

## Initialization

### initialize

Initializes the wallet with an owner and optional first guardian.

**Access:** Called by factory during deployment via `deployWallet()`

```solidity
function initialize(address owner_, address guardian_) external initializer
```

**Parameters:**

| Name        | Type      | Description                                        |
| ----------- | --------- | -------------------------------------------------- |
| `owner_`    | `address` | ECDSA owner address                                |
| `guardian_` | `address` | Optional first guardian (use `address(0)` to skip) |

**Usage:**

```typescript
// MCP server creates wallet with 1 guardian
await walletFactory.deployWallet(ownerAddress, guardianAddress);
```

***

## Core Operations

### execute

Executes a single call from the wallet.

**Access:** Owner or EntryPoint (via validated UserOperation)

```solidity
function execute(address target, uint256 value, bytes calldata data) external nonReentrant
```

**Parameters:**

| Name     | Type      | Description             |
| -------- | --------- | ----------------------- |
| `target` | `address` | Target contract address |
| `value`  | `uint256` | ETH value to send       |
| `data`   | `bytes`   | Encoded function call   |

**Security:**

* Protected by `nonReentrant` modifier
* Requires `msg.sender == owner` or `msg.sender == ENTRY_POINT`

**Usage:**

```typescript
// AI agent executes ERC20 transfer via MCP
const callData = erc20.interface.encodeFunctionData("transfer", [recipient, amount]);
await wallet.execute(tokenAddress, 0, callData);
```

***

### executeBatch

Executes multiple calls in a single transaction.

**Access:** Owner or EntryPoint

```solidity
function executeBatch(Call[] calldata calls) external nonReentrant
```

**Parameters:**

| Name    | Type     | Description                                                             |
| ------- | -------- | ----------------------------------------------------------------------- |
| `calls` | `Call[]` | Array of `Call` structs `{ address target, uint256 value, bytes data }` |

**Usage:**

```typescript
// AI agent deposits and supplies to Morpho in one tx
const calls = [
  { target: wethAddress, value: ethAmount, data: "0x" }, // wrap ETH
  { target: wethAddress, value: 0, data: approveCalldata }, // approve
  { target: morphoAddress, value: 0, data: supplyCalldata } // supply
];
await wallet.executeBatch(calls);
```

***

## Guardian Management

### addGuardian

Adds a new guardian to the wallet.

**Access:** Owner only

```solidity
function addGuardian(address guardian) external onlyOwner
```

**Parameters:**

| Name       | Type      | Description             |
| ---------- | --------- | ----------------------- |
| `guardian` | `address` | Guardian address to add |

**Validation:**

* Guardian cannot be `address(0)`
* Guardian cannot already exist
* Cannot exceed `MAX_GUARDIANS` (3)
* If `recoveryThreshold` is 0, sets it to 1

**Events:** `GuardianAdded(address indexed guardian)`

***

### removeGuardian

Removes a guardian from the wallet.

**Access:** Owner only

```solidity
function removeGuardian(address guardian) external onlyOwner
```

**Parameters:**

| Name       | Type      | Description                |
| ---------- | --------- | -------------------------- |
| `guardian` | `address` | Guardian address to remove |

**Validation:**

* Guardian must exist
* Auto-adjusts `recoveryThreshold` if it exceeds remaining guardian count
* Cancels pending recovery if removed guardian had approved it

**Events:** `GuardianRemoved(address indexed guardian)`

***

### setRecoveryThreshold

Sets the minimum number of guardian approvals required for recovery.

**Access:** Owner only

```solidity
function setRecoveryThreshold(uint256 threshold_) external onlyOwner
```

**Parameters:**

| Name         | Type      | Description                              |
| ------------ | --------- | ---------------------------------------- |
| `threshold_` | `uint256` | New threshold (must be ≤ guardian count) |

**Events:** `RecoveryThresholdChanged(uint256 threshold)`

***

### setRecoveryDelay

Sets the time delay before recovery can be executed.

**Access:** Owner only

```solidity
function setRecoveryDelay(uint64 delay_) external onlyOwner
```

**Parameters:**

| Name     | Type     | Description                                                              |
| -------- | -------- | ------------------------------------------------------------------------ |
| `delay_` | `uint64` | Delay in seconds (between `MIN_RECOVERY_DELAY` and `MAX_RECOVERY_DELAY`) |

**Events:** `RecoveryDelayChanged(uint64 delay)`

***

## Social Recovery

### initiateRecovery

Initiates a recovery process to change the owner.

**Access:** Guardians only

```solidity
function initiateRecovery(address newOwner_) external onlyGuardian
```

**Parameters:**

| Name        | Type      | Description                |
| ----------- | --------- | -------------------------- |
| `newOwner_` | `address` | Proposed new owner address |

**Validation:**

* New owner cannot be `address(0)`
* New owner cannot be an existing guardian (separation of duties)
* New owner cannot be the current owner
* Recovery threshold must be > 0
* Cancels any existing pending recovery

**Effects:**

* Creates new `RecoveryRequest` with `executeAfter = block.timestamp + recoveryDelay`
* Auto-approves for initiating guardian
* Sets expiration to `executeAfter + RECOVERY_EXPIRATION`

**Events:** `RecoveryInitiated(address indexed newOwner, uint64 executeAfter)`

***

### approveRecovery

Approves the pending recovery request.

**Access:** Guardians only

```solidity
function approveRecovery() external onlyGuardian
```

**Validation:**

* Recovery must be active
* Guardian must not have already approved
* Guardian must still be in the guardian set

**Events:** `RecoveryApproved(address indexed guardian, uint8 approvalCount)`

***

### executeRecovery

Executes the recovery and changes the owner.

**Access:** Anyone (permissionless after threshold + delay met)

```solidity
function executeRecovery() external
```

**Validation:**

* Recovery must be active
* Must have reached `recoveryThreshold` approvals
* Must be after `executeAfter` timestamp
* Must be before `expiresAt` timestamp
* New owner must not have become a guardian since initiation

**Effects:**

* Changes `owner` to `pendingRecovery.newOwner`
* Clears `pendingRecovery`
* Notifies factory of ownership change

**Events:** `RecoveryExecuted(address indexed oldOwner, address indexed newOwner)`

***

### cancelRecovery

Cancels the pending recovery request.

**Access:** Owner only

```solidity
function cancelRecovery() external onlyOwner
```

**Events:** `RecoveryCancelled()`

***

## Owner Management

### setOwner

Changes the wallet owner directly (without guardian recovery).

**Access:** Owner or EntryPoint

```solidity
function setOwner(address newOwner) external
```

**Parameters:**

| Name       | Type      | Description       |
| ---------- | --------- | ----------------- |
| `newOwner` | `address` | New owner address |

**Validation:**

* New owner cannot be `address(0)`
* New owner cannot be an existing guardian (separation of duties)
* New owner cannot be the current owner

**Effects:**

* Changes `owner` to `newOwner`
* Cancels any pending recovery (prevents takeover after ownership transfer)
* Notifies factory of ownership change

**Events:** `OwnershipTransferred(address indexed previousOwner, address indexed newOwner)`

***

## Signature Validation

### isValidSignature

Validates a signature according to ERC-1271.

**Access:** View function (anyone can call)

```solidity
function isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4 magicValue)
```

**Parameters:**

| Name        | Type      | Description                         |
| ----------- | --------- | ----------------------------------- |
| `hash`      | `bytes32` | Hash of the signed data             |
| `signature` | `bytes`   | ECDSA signature (65 bytes: r, s, v) |

**Returns:**

| Name         | Type     | Description                                    |
| ------------ | -------- | ---------------------------------------------- |
| `magicValue` | `bytes4` | `0x1626ba7e` if valid, `0xffffffff` if invalid |

**Usage:**

```typescript
// Verify wallet signed a message
const messageHash = ethers.utils.hashMessage(message);
const isValid = await wallet.isValidSignature(messageHash, signature);
console.log(isValid === "0x1626ba7e"); // true if owner signed
```

***

## EntryPoint Integration

### addDeposit

Deposits ETH into the EntryPoint for gas payment.

**Access:** Anyone (payable)

```solidity
function addDeposit() external payable
```

**Usage:**

```typescript
// Fund wallet's EntryPoint deposit for UserOps
await wallet.addDeposit({ value: ethers.utils.parseEther("0.1") });
```

***

### withdrawDepositTo

Withdraws ETH from EntryPoint deposit.

**Access:** Owner or EntryPoint

```solidity
function withdrawDepositTo(address payable withdrawAddress, uint256 amount) external
```

**Parameters:**

| Name              | Type              | Description                 |
| ----------------- | ----------------- | --------------------------- |
| `withdrawAddress` | `address payable` | Recipient address           |
| `amount`          | `uint256`         | Amount to withdraw (in wei) |

***

## Upgrade Mechanism

### upgradeToLatest

Upgrades the wallet to the factory's current implementation.

**Access:** Owner only

```solidity
function upgradeToLatest(bytes calldata data) external nonReentrant
```

**Parameters:**

| Name   | Type    | Description                                                       |
| ------ | ------- | ----------------------------------------------------------------- |
| `data` | `bytes` | Optional calldata to execute during upgrade (e.g., reinitializer) |

**Validation:**

* Factory must be set
* Caller must be owner
* New implementation must have higher version number

**Security:**

* Owner initiates upgrades ("pull" model, not "push")
* Factory is not an upgrade authority (just provides current implementation)
* Uses ERC1967 proxy pattern
* Protected by `nonReentrant`

**Events:** `Upgraded(address indexed implementation)`

***

## View Functions

### entryPoint

Returns the EntryPoint contract.

```solidity
function entryPoint() public view returns (IEntryPoint)
```

***

### isGuardian

Checks if an address is a guardian.

```solidity
function isGuardian(address addr) public view returns (bool)
```

***

### guardianCount

Returns the number of guardians.

```solidity
function guardianCount() public view returns (uint256)
```

***

### getGuardians

Returns all guardian addresses.

```solidity
function getGuardians() public view returns (address[] memory)
```

***

### getGuardianAt

Returns the guardian at a specific index.

```solidity
function getGuardianAt(uint256 index) public view returns (address)
```

***

### hasApprovedRecovery

Checks if a guardian has approved the pending recovery.

```solidity
function hasApprovedRecovery(address guardian) public view returns (bool)
```

***

### getPendingRecovery

Returns details of the pending recovery request.

```solidity
function getPendingRecovery() public view returns (
    address newOwner,
    uint64 executeAfter,
    uint64 expiresAt,
    uint8 approvalCount,
    bool active
)
```

***

### getDeposit

Returns the wallet's EntryPoint deposit balance.

```solidity
function getDeposit() public view returns (uint256)
```

***

### getNonce

Returns the current nonce from EntryPoint.

```solidity
function getNonce() public view returns (uint256)
```

***

### implementationVersion

Returns the implementation version number.

```solidity
function implementationVersion() public pure virtual returns (uint256)
```

***

### DOMAIN\_SEPARATOR

Returns the EIP-712 domain separator.

```solidity
function DOMAIN_SEPARATOR() public view returns (bytes32)
```

***

## Events

| Event                      | Parameters                                                | Description                |
| -------------------------- | --------------------------------------------------------- | -------------------------- |
| `OwnershipTransferred`     | `address indexed previousOwner, address indexed newOwner` | Owner changed              |
| `GuardianAdded`            | `address indexed guardian`                                | Guardian added             |
| `GuardianRemoved`          | `address indexed guardian`                                | Guardian removed           |
| `RecoveryThresholdChanged` | `uint256 threshold`                                       | Recovery threshold updated |
| `RecoveryDelayChanged`     | `uint64 delay`                                            | Recovery delay updated     |
| `RecoveryInitiated`        | `address indexed newOwner, uint64 executeAfter`           | Recovery started           |
| `RecoveryApproved`         | `address indexed guardian, uint8 approvalCount`           | Guardian approved recovery |
| `RecoveryExecuted`         | `address indexed oldOwner, address indexed newOwner`      | Recovery completed         |
| `RecoveryCancelled`        |                                                           | Recovery cancelled         |
| `Upgraded`                 | `address indexed implementation`                          | Implementation upgraded    |

***

## Errors

| Error                     | Description                                    |
| ------------------------- | ---------------------------------------------- |
| `UnauthorizedCaller()`    | Caller is not authorized for this operation    |
| `InvalidGuardian()`       | Guardian address is invalid or already exists  |
| `GuardianNotFound()`      | Guardian does not exist                        |
| `MaxGuardiansReached()`   | Cannot exceed MAX\_GUARDIANS (3)               |
| `InvalidThreshold()`      | Threshold exceeds guardian count or is zero    |
| `InvalidRecoveryDelay()`  | Delay outside allowed range                    |
| `NoActiveRecovery()`      | No recovery is currently pending               |
| `AlreadyApproved()`       | Guardian already approved this recovery        |
| `RecoveryNotReady()`      | Recovery delay not passed or threshold not met |
| `RecoveryExpired()`       | Recovery window has closed                     |
| `OwnerCannotBeGuardian()` | Separation of duties violation                 |
| `InvalidOwner()`          | Owner address is zero or unchanged             |
| `FactoryNotSet()`         | Factory must be set to upgrade                 |

***

## Integration Examples

### Deploy via MCP

```typescript
// MCP tool: create-smart-wallet
const wallet = await factory.deployWallet(
  ownerAddress,
  guardianAddress // or ethers.constants.AddressZero
);
```

### Execute UserOperation

```typescript
// AI agent submits UserOp via bundler
const userOp = {
  sender: walletAddress,
  nonce: await wallet.getNonce(),
  callData: wallet.interface.encodeFunctionData("execute", [target, value, data]),
  // ... gas fields, paymaster, signature
};
await bundler.sendUserOperation(userOp);
```

### Guardian Recovery Flow

```typescript
// 1. Guardian initiates
await wallet.connect(guardian1).initiateRecovery(newOwner);

// 2. Other guardians approve
await wallet.connect(guardian2).approveRecovery();
await wallet.connect(guardian3).approveRecovery();

// 3. After delay + threshold met, anyone executes
await wallet.executeRecovery();
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.greenowl.money/smart-contracts/contracts/smartwallet.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
