LayerZeroFault
passkey recovery Updated:

ERC-4337 Session Key AA22 Reverts on Clock Drift and L2 Skew

VV

Written by

Updated:

Technical Guide

The ERC-4337 EntryPoint AA22 revert (‘AA22 expired or not due’) occurs during session key UserOperation validation when the packed validAfter and validUntil timestamps fail against block.timestamp on L2 rollups. Because rollup sequencers can exhibit up to 15 seconds of block timestamp clock drift relative to client system clocks, strict timestamp bounds cause premature invalidation. Adding a 60-second grace buffer to validAfter parameters prevents the revert.

Before executing state changes or signing transactions, verify your cryptographic parameters using our interactive ERC-4337 Calldata Decoder utility.


Architectural Breakdown: EntryPoint Validation Data Encoding

In the ERC-4337 specification (both EntryPoint v0.6 and v0.7), validateUserOp returns a packed 32-byte validationData integer:

// ERC-4337 EntryPoint specification
// uint256 validationData = authorizer (20 bytes) | validUntil (6 bytes) | validAfter (6 bytes)
function _intersectTimeRange(
    uint256 validationData, 
    uint256 paymasterValidationData
) internal view returns (uint256) {
    uint48 validUntil = uint48(validationData >> 160);
    uint48 validAfter = uint48(validationData >> 208);
    
    // CRITICAL VALIDATION CHECK
    if (block.timestamp < validAfter || block.timestamp >= validUntil) {
        revert("AA22 expired or not yet valid");
    }
}

The Three Timing Trap Vectors

  1. Client System Clock Skew: If a client device’s clock is running 8 seconds ahead of real-world UTC time and sets validAfter = Math.floor(Date.now() / 1000), the UserOperation reaches the node while the canonical L2 block.timestamp is still in the past.
  2. Sub-Second L2 Block Dynamics: On networks like Arbitrum One (which can mint multiple micro-blocks per second) or Base (2-second blocks), block timestamps can drift slightly relative to wall-clock NTP time due to sequencer batching lag.
  3. Mempool Staging Latency: If an algorithmic agent sets validUntil to a narrow window (e.g. 30 seconds for MEV protection), network congestion or bundler re-queuing can cause the simulation to execute when block.timestamp == validUntil, emitting AA22.

Step-by-Step Resolution Protocol

To resolve AA22 temporal rejections across automated trading agents and modular account implementations, apply this three-tier defensive strategy.

1. Fetch Canonical Block Timestamp from RPC

Never construct session policies using client-side Date.now(). Query the latest mined block’s timestamp directly from your RPC provider:

// src/utils/getCanonicalTimestamp.ts
import { type PublicClient } from 'viem';

export async function getCanonicalBlockTimestamp(publicClient: PublicClient): Promise<number> {
  try {
    const latestBlock = await publicClient.getBlock({ blockTag: 'latest' });
    return Number(latestBlock.timestamp);
  } catch (error) {
    console.warn('[SessionKeyGuard] Failed to fetch latest block, falling back to UTC wall time:', error);
    return Math.floor(Date.now() / 1000);
  }
}

2. Apply Defensive Retrospective Buffers in Session Policies

When initializing or signing an ERC-7579 or Kernel v3 session key, configure a 300-second retrospective buffer for validAfter and an adequate expiration margin for validUntil:

// src/sessionKeys/createSessionPolicy.ts
import { type PublicClient } from 'viem';
import { getCanonicalBlockTimestamp } from '../utils/getCanonicalTimestamp';

interface SessionTimeWindowOptions {
  durationSeconds?: number;
}

export async function calculateSafeSessionTimestamps(
  publicClient: PublicClient,
  options: SessionTimeWindowOptions = { durationSeconds: 86400 } // Default 24 hours
) {
  const currentBlockTime = await getCanonicalBlockTimestamp(publicClient);

  // CRITICAL FIX 1: Subtract 300 seconds (5 mins) to absorb clock drift & sequencer skew
  const validAfter = Math.max(0, currentBlockTime - 300);

  // CRITICAL FIX 2: Add 120s buffer to validUntil to prevent boundary race conditions
  const validUntil = currentBlockTime + options.durationSeconds! + 120;

  console.log(`[SessionKeyGuard] Policy Temporal Bounds:`);
  console.log(`- validAfter: ${validAfter} (BlockTime - 300s)`);
  console.log(`- validUntil: ${validUntil} (BlockTime + ${options.durationSeconds}s + 120s)`);

  return { validAfter, validUntil };
}

3. Bundler Pre-Flight Simulation Interceptor

In autonomous agents running transaction loops, simulate the UserOperation locally using publicClient.simulateUserOperation and detect temporal proximity prior to public broadcast:

// src/agents/safeUserOpBroadcaster.ts
import { type UserOperation } from 'permissionless';

export function assertTimeWindowSafety(validUntil: number, minRemainingSeconds = 60) {
  const now = Math.floor(Date.now() / 1000);
  const remaining = validUntil - now;

  if (remaining < minRemainingSeconds) {
    throw new Error(
      `SESSION_EXPIRED_RISK: Only ${remaining}s remaining before validUntil (${validUntil}). ` +
      `Aborting broadcast to prevent AA22 simulation revert.`
    );
  }
}

Temporal Parameter Safety Matrix

Configuration ScenariovalidAfter SettingvalidUntil SettingSimulation Outcome
Strict Wall-ClockDate.now() / 1000Date.now() / 1000 + 30sHigh Revert Risk (AA22) on L2 skew
Buffered On-Chainblock.timestamp - 300block.timestamp + 3600100% Reliable Execution
Zero-Value (0)0 (Always valid from genesis)Defined timestampPermitted for permanent session signers
Zero-Value Until (0)Defined timestamp0 (Never expires)Allowed by EntryPoint, but high risk if compromised

Frequently Asked Questions

Q: Why does the EntryPoint specification use uint48 for timestamps?

uint48 provides sufficient range to represent Unix timestamps in seconds up to the year 8,921,556 AD, while allowing two timestamps and an Ethereum authorizer address to fit compactly inside a single 32-byte EVM storage word (uint256).

Q: Can an attacker exploit a retrospective validAfter (e.g. block.timestamp - 300)?

No. Setting validAfter in the past merely indicates that the session key became mathematically authorized 5 minutes ago. It does not allow an attacker to alter the on-chain execution order of transactions, because transactions are still ordered and sequenced by the current block builder.

Q: Does AA22 consume gas fees from the smart account or paymaster?

No. AA22 occurs during the simulateValidation or estimateUserOperationGas RPC calls executed off-chain by the bundler. Bundlers drop transactions failing validation without broadcasting them on-chain, protecting accounts from gas drain.

ERC-4337 EntryPoint Error Code Reference Matrix

Cross-reference standard ERC-4337 EntryPoint revert codes across account abstraction and smart wallet layers:

Error CodeStagePrimary Failure MechanismDiagnostic Guide
AA13Factory DeploymentInitCode execution reverted or exceeded factory gasERC-4337 AA13 InitCode Failed Fix
AA21Prefund ValidationSender account balance or deposit below required maxCostERC-4337 AA21 Prefund Payment Fix
AA22Timestamp ValidityClock drift or expired session key validity windowERC-4337 AA22 Clock Drift Fix
AA23User VerificationP-256 passkey signature or WebCrypto auth failedERC-4337 AA23 Signature Revert Fix
AA33Paymaster PostOpPost-execution token transfer or context revertedERC-4337 AA33 Paymaster PostOp Fix
PaymasterGas SponsorshipPaymaster rejected UserOp or sponsorship signature invalidERC-4337 Paymaster Revert Resolution

[!TIP] Unpack packed UserOperations, calculate prefund requirements, and isolate simulation hazards in our air-gapped /tools/erc4337-calldata-decoder/.

Interactive Diagnostic Sandbox

Local WebAuthn & Smart Account Sandboxes

Debug WebAuthn credential requests, PRF extensions, and simulate ERC-4337 passkey account validation in client WebCrypto.

Companion Diagnostic Guide ERC-4337 Passkey P256 AA23 Signature Revert Fix →

Resolve ERC-4337 EntryPoint AA23 signature validation reverts using WebAuthn P256 keys. Fix authenticatorData UV flags, high-s malleability, and SHA-256 hashes.

Read Guide
Contextual Architecture

Related Diagnostic Guides

View All in Silo →

Related Inquiries

What does the AA22 error code mean in ERC-4337?

AA22 is returned by EntryPoint v0.6 and v0.7 contracts when validateUserOp fails time-range verification: the current block.timestamp is either earlier than validAfter ('not due') or later than validUntil ('expired').

Why does clock drift cause AA22 reverts on L2 networks?

L2 sequencers on Arbitrum, Optimism, and Base post batches with sequencer-assigned timestamps that can lag or lead local client UTC time by several seconds. If a client creates a session key with validAfter = Math.floor(Date.now() / 1000), the sequencer may execute it at a timestamp slightly in the past.

What is the recommended fix for session key timestamp reverts?

Set validAfter to Math.floor(Date.now() / 1000) - 60 (a 60-second buffer in the past) and ensure validUntil extends at least 5 minutes into the future to account for mempool congestion and sequencer delays.

How can I decode packed validationData timestamps?

Use our interactive ERC-4337 Calldata Decoder tool to unpack the 160-bit authorizer address, 48-bit validUntil, and 48-bit validAfter values from raw validation responses.