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()

Parameters:

Name
Type
Description

owner_

address

ECDSA owner address

guardian_

address

Optional first guardian (use address(0) to skip)

Usage:


Core Operations

execute

Executes a single call from the wallet.

Access: Owner or EntryPoint (via validated UserOperation)

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:


executeBatch

Executes multiple calls in a single transaction.

Access: Owner or EntryPoint

Parameters:

Name
Type
Description

calls

Call[]

Array of Call structs { address target, uint256 value, bytes data }

Usage:


Guardian Management

addGuardian

Adds a new guardian to the wallet.

Access: Owner only

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

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

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

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

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

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)

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

Events: RecoveryCancelled()


Owner Management

setOwner

Changes the wallet owner directly (without guardian recovery).

Access: Owner or EntryPoint

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)

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:


EntryPoint Integration

addDeposit

Deposits ETH into the EntryPoint for gas payment.

Access: Anyone (payable)

Usage:


withdrawDepositTo

Withdraws ETH from EntryPoint deposit.

Access: Owner or EntryPoint

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

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.


isGuardian

Checks if an address is a guardian.


guardianCount

Returns the number of guardians.


getGuardians

Returns all guardian addresses.


getGuardianAt

Returns the guardian at a specific index.


hasApprovedRecovery

Checks if a guardian has approved the pending recovery.


getPendingRecovery

Returns details of the pending recovery request.


getDeposit

Returns the wallet's EntryPoint deposit balance.


getNonce

Returns the current nonce from EntryPoint.


implementationVersion

Returns the implementation version number.


DOMAIN_SEPARATOR

Returns the EIP-712 domain separator.


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

Execute UserOperation

Guardian Recovery Flow

Last updated