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
SmartWalletFactoryvia CREATE2 for deterministic addressesIntegrated with
SessionKeyManagerfor time/scope-limited agent keysWorks with
WhitelistPaymasterfor gas sponsorshipUsed 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
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
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
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
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:
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:
target
address
Target contract address
value
uint256
ETH value to send
data
bytes
Encoded function call
Security:
Protected by
nonReentrantmodifierRequires
msg.sender == ownerormsg.sender == ENTRY_POINT
Usage:
executeBatch
Executes multiple calls in a single transaction.
Access: Owner or EntryPoint
Parameters:
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:
guardian
address
Guardian address to add
Validation:
Guardian cannot be
address(0)Guardian cannot already exist
Cannot exceed
MAX_GUARDIANS(3)If
recoveryThresholdis 0, sets it to 1
Events: GuardianAdded(address indexed guardian)
removeGuardian
Removes a guardian from the wallet.
Access: Owner only
Parameters:
guardian
address
Guardian address to remove
Validation:
Guardian must exist
Auto-adjusts
recoveryThresholdif it exceeds remaining guardian countCancels 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:
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:
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:
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
RecoveryRequestwithexecuteAfter = block.timestamp + recoveryDelayAuto-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
recoveryThresholdapprovalsMust be after
executeAftertimestampMust be before
expiresAttimestampNew owner must not have become a guardian since initiation
Effects:
Changes
ownertopendingRecovery.newOwnerClears
pendingRecoveryNotifies 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:
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
ownertonewOwnerCancels 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:
hash
bytes32
Hash of the signed data
signature
bytes
ECDSA signature (65 bytes: r, s, v)
Returns:
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:
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:
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
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
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