[Draft] App-Scoped Nonce Lanes for ERC-4337 Accounts

Abstract

This ERC standardizes how ERC-4337 smart accounts assign, expose, authorize, and revoke app-scoped nonce lanes.

ERC-4337 already defines a semi-abstracted nonce where the high 192 bits are a key and the low 64 bits are a sequence. This ERC gives that key an interoperable application-level meaning:

nonce key = application / delegate / wallet / agent execution lane

Each app-scoped lane has its own monotonic nonce sequence through the ERC-4337 EntryPoint. This allows multiple delegates to operate on behalf of the same account without blocking or invalidating one another’s pending operations.

Motivation

Many wallets, MPC systems, dApps, subscriptions, trading agents, and AI agents need to execute on behalf of a user account. Today, they often compete for one account-level nonce. If the user or another wallet sends a transaction first, the delegate’s locally tracked nonce may become stale.

Subaccounts solve this by creating separate accounts, but they fragment assets and introduce funding or allowance management. App-scoped nonce lanes solve a narrower problem:

same account
same asset pool
multiple independent execution queues
per-lane permissions

This ERC does not replace ERC-4337. It standardizes a convention on top of ERC-4337’s existing 192-bit nonce key.

Reference: ERC-4337 semi-abstracted nonce support:

Design Goals

This ERC is designed for the following use case:

A user keeps assets in one smart account.
Multiple wallets, apps, agents, or MPC services may operate that account.
Each operator has its own nonce sequence.
User activity and other operators' activity do not make a lane's nonce stale.
The account enforces what each operator is allowed to do.

The core goal is nonce ownership without asset fragmentation:

one account address
one shared asset pool
many app/delegate-specific nonce lanes
many app/delegate-specific permission scopes

Non-Goals

This ERC does not attempt to standardize:

1. Subaccount creation or hierarchical account ownership.
2. Cross-chain atomic execution.
3. Balance reservation between lanes.
4. A universal permission language for every possible action.
5. A new EntryPoint or replacement for ERC-4337 nonce handling.
6. A protocol-level transaction type.
7. A mandatory account storage layout or module architecture.

Two lanes may still race for the same account balance. This ERC prevents nonce contention, not balance contention.

Terminology

Account:
  An ERC-4337 smart account.

Delegate:
  A wallet, dApp session, MPC service, agent, validator, signer group, or other authority authorized to use a lane.

Delegate ID:
  A bytes32 identifier for the delegate authority. This MAY be derived from an address, public key, validator configuration, MPC policy, or delegation context.

Validator:
  An address that implements or routes the validation logic for the delegate. For address-based delegates, the validator MAY equal the delegate address.

App ID:
  A bytes32 identifier for the application or delegate domain.

Lane:
  A registered nonce namespace bound to an account, EntryPoint, appId, delegateId, validator, and permission policy.

Nonce Key:
  The uint192 high bits of ERC-4337 UserOperation.nonce.

Nonce Sequence:
  The uint64 low bits of ERC-4337 UserOperation.nonce.

Lane Permission:
  The policy that determines what the delegate may do through the lane.

EntryPoint:
  The ERC-4337 EntryPoint whose nonce manager tracks the lane sequence.

Core Model

ERC-4337 encodes nonce as:

userOp.nonce = (uint256(nonceKey) << 64) | nonceSeq

This ERC defines nonceKey as an app-scoped execution lane:

lane = (account, registry, entryPoint, appId, delegateId, validator, nonceKey, assignmentMode, laneSalt, permissionHash, laneConfigHash, status)

The ERC-4337 EntryPoint remains responsible for nonce sequence validation and incrementation. The account remains responsible for validating that the caller, signature, or delegate authority is authorized to use the lane.

This ERC does not require lane data to be stored directly in the account contract. Lane data MAY be stored in:

1. The account contract.
2. An account module.
3. A lane registry contract trusted by the account.
4. A modular-account registry used by ERC-6900 or ERC-7579 style accounts.

Regardless of storage location, the account’s validateUserOp MUST enforce the active lane state, delegate authorization, and permission scope for UserOperations that use registered app-scoped lanes.

An account MAY continue using ERC-4337 nonce keys outside this ERC for account-specific logic. This ERC governs only nonce keys that the account identifies as app-scoped lane keys according to Nonce Key Namespace. Unknown non-app-lane nonce keys MAY be handled by account-specific validation logic.

Specification

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119 and RFC 8174.

Lane Semantics

A compliant account MUST enforce the following:

1. A registered lane has exactly one nonceKey.
2. The same `(account, entryPoint, nonceKey)` MUST NOT be assigned to more than one lane record across ACTIVE, SUSPENDED, or REVOKED lane states. Lifecycle transitions of the same lane record are allowed only as specified in [Lane State Machine](#lane-state-machine).
3. A UserOperation using a lane nonceKey MUST be authorized by that lane's permission policy.
4. A delegate authority MUST NOT be able to use another delegate authority's lane unless explicitly authorized.
5. Revoking a lane MUST prevent future UserOperations from validating through that lane.
6. Revoking a lane MUST NOT affect other lanes.
7. Nonce sequence is read from ERC-4337 EntryPoint.getNonce(account, nonceKey).
8. A lane is bound to exactly one EntryPoint.

This ERC does not require a specific permission system. A lane may be backed by native account logic, ERC-6900 modules, ERC-7579 modules, ERC-7710 delegation, session keys, MPC signatures, or custom validation.

nonceKey = 0 SHOULD be reserved for the account’s default or classic ERC-4337 lane. App-scoped lanes SHOULD use non-zero nonce keys unless the account explicitly assigns key 0 to a lane.

Nonce Key Namespace

This ERC does not claim all ERC-4337 nonce keys.

An account MAY use ERC-4337 nonce keys for other account-specific purposes, including admin operations, recovery operations, module routing, or compatibility with other standards. Such keys are outside the scope of this ERC unless explicitly registered as app-scoped lanes.

An app-scoped lane key is any nonceKey that satisfies at least one of the following:

1. It is returned by a compliant lane registry as ACTIVE, SUSPENDED, or REVOKED for the account and EntryPoint.
2. It is explicitly recognized by the account as an app-scoped lane key during validateUserOp.
3. It is derived using the domain-separated derivation in this ERC and then registered by the account or its authoritative registry.

Accounts MUST NOT reject unknown nonce keys solely because they are not registered app-scoped lanes, unless the account intentionally chooses to support only app-scoped lanes. Unknown non-lane keys MAY be handled by account-specific ERC-4337 validation logic.

The recommended derivation domain string is:

ERCXXXX_APP_SCOPED_NONCE_LANE_V1

Wallets and apps SHOULD use this domain when requesting app-scoped lanes to reduce collision risk with account-specific nonce key schemes.

App-scoped lane classification is account- or registry-defined. It is not inferable from nonceKey bits alone. Bundlers, wallets, and indexers MUST NOT assume a nonceKey is app-scoped solely from its numeric range or prefix.

Lane State Machine

A lane has exactly one of the following states:

NONE:
  The nonceKey has never been registered by the account.

ACTIVE:
  The lane may validate UserOperations if all authorization and permission checks pass.

SUSPENDED:
  The lane is temporarily disabled. It may be reactivated only with the same lane identity.

REVOKED:
  The lane no longer validates UserOperations.

For REVOKED records, implementations SHOULD preserve the original lane identity and the last known permissionHash, laneConfigHash, and metadataHash where available. The minimum requirement is preserving enough state to enforce that the same (account, entryPoint, nonceKey) remains burned and cannot be registered again.

If complete historical configuration is unavailable, getLaneByNonceKey for a burned nonceKey MUST return at least account, entryPoint, nonceKey, and status = REVOKED. Unknown fields MAY be returned as zero or implementation-defined defaults, provided callers can distinguish the record from NONE. Discovery by laneId is REQUIRED for REVOKED records only when the original laneId is preserved.

The following transitions are valid:

NONE -> ACTIVE:
  registerLane()

ACTIVE -> ACTIVE:
  updateLane()

ACTIVE -> SUSPENDED:
  suspendLane()

SUSPENDED -> ACTIVE:
  reactivateLane()

SUSPENDED -> REVOKED:
  revokeLane()

ACTIVE -> REVOKED:
  revokeLane()

The following transitions MUST be rejected:

REVOKED -> ACTIVE
REVOKED -> SUSPENDED
REVOKED -> NONE
NONE -> REVOKED
NONE -> SUSPENDED

A lane identity consists of entryPoint, laneId, appId, delegateId, validator, nonceKey, assignmentMode, and laneSalt.

A revoked nonceKey is permanently burned for the account and EntryPoint pair. The account MUST NOT register the same (account, entryPoint, nonceKey) again after revocation, even with identical lane configuration. This avoids ambiguity because the ERC-4337 EntryPoint nonce sequence for that nonceKey cannot be reset.

Suspension is intended for temporary disabling. A suspended lane MAY be reactivated, but only with the same lane identity. Reactivation MAY update validity bounds, permissionHash, and metadata fields.

Lane Lifecycle

An app-scoped lane has the following lifecycle:

1. Request:
   A delegate, app, wallet, or agent requests a lane for an account.

2. Approval:
   The account owner, account policy, or wallet approves the lane and its permission scope.

3. Registration:
   The account records entryPoint, appId, delegateId, validator, nonceKey, assignmentMode, laneSalt, validity, and permissionHash.

4. Use:
   The delegate submits UserOperations with nonce = (nonceKey << 64) | nonceSeq.

5. Nonce Read:
   The delegate reads the next sequence from EntryPoint.getNonce(account, nonceKey).

6. Update:
   The account may update permissionHash, validity, or metadata for the lane.

7. Revocation:
   The account marks the lane as revoked. Future UserOperations using that lane fail validation.

Registration and revocation are account-authorized operations. A delegate MUST NOT be able to unilaterally create, expand, revoke, or revive a lane unless the account’s existing policy explicitly permits it.

Registration Authorization

The functions that register, update, or revoke lanes MUST be protected by account authorization.

Compliant implementations MAY expose these functions directly on the account, through a module, or through an account execution call. Regardless of implementation shape, the effective authorization MUST be equivalent to one of the following:

1. Owner-authorized account execution.
2. Account module authorized by the owner.
3. Existing account policy that explicitly grants lane-management rights.
4. A prior delegation whose permission scope includes lane management.

Lane managers MUST reject lane registration if:

1. The caller is not authorized to manage lanes.
2. The same `(account, entryPoint, nonceKey)` is already ACTIVE, SUSPENDED, or REVOKED.
3. The requested permissionHash is empty when the account requires explicit permission commitments.
4. validUntil is non-zero and validUntil <= validAfter.
5. The nonceKey is reserved by the account implementation.
6. The requested EntryPoint is not trusted by the account.
7. The validator is address(0) unless the account defines address(0) as a valid native validator.
8. laneId does not match the canonical derivation for the supplied laneSalt.
9. assignmentMode is DERIVED and nonceKey does not match the canonical derivation for the supplied laneSalt.
10. assignmentMode is EXPLICIT and account policy does not permit explicit nonceKey assignment.

Lane managers MUST reject lane revocation, suspension, or reactivation if the caller is not authorized to perform that operation.

permissionHash == bytes32(0) MAY be accepted only if account policy explicitly defines it as a native, unrestricted, or otherwise well-defined permission scope. Implementations MUST NOT treat an empty permission hash as implicitly unrestricted unless that behavior is part of account policy.

updateLane() MUST NOT change entryPoint, laneId, appId, delegateId, validator, nonceKey, assignmentMode, laneSalt, or status. It MAY only update permissionHash, laneConfigHash, validity bounds, and metadata fields. If an implementation needs to change lane identity or nonce key assignment, it MUST revoke the old lane and register a new lane with a new nonceKey.

Lane managers MUST reject updateLane() unless the lane is ACTIVE. Lane managers MUST reject reactivateLane() unless the lane is SUSPENDED. reactivateLane() MUST NOT change entryPoint, laneId, appId, delegateId, validator, nonceKey, assignmentMode, or laneSalt. Lane managers MUST reject suspendLane() unless the lane is ACTIVE. Lane managers MUST reject revokeLane() unless the lane is ACTIVE or SUSPENDED.

Interface

// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.20;

interface IERC165 {
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

interface IEntryPoint {
    function getNonce(address sender, uint192 key) external view returns (uint256 nonce);
}

interface IERCXXXXLaneRegistryProvider is IERC165 {
    event ExecutionLaneRegistryChanged(
        address indexed account,
        address indexed oldRegistry,
        address indexed newRegistry
    );

    function executionLaneRegistry() external view returns (address registry);
}

interface IERCXXXXLaneRegistry is IERC165 {
    enum LaneStatus {
        NONE,
        ACTIVE,
        SUSPENDED,
        REVOKED
    }

    enum NonceKeyAssignment {
        DERIVED,
        EXPLICIT
    }

    struct LaneConfig {
        address account;
        address registry;
        address entryPoint;
        bytes32 laneId;
        bytes32 appId;
        bytes32 delegateId;
        address validator;
        uint192 nonceKey;
        NonceKeyAssignment assignmentMode;
        bytes32 laneSalt;
        LaneStatus status;
        bytes32 permissionHash;
        bytes32 laneConfigHash;
        uint48 validAfter;
        uint48 validUntil;
        bytes32 metadataHash;
    }

    struct LaneAuthContext {
        bytes32 userOpHash;
        uint256 nonce;
        bytes32 callDataHash;
        bytes32 permissionContextHash;
        bytes delegateProof;
    }

    event LaneRegistered(
        address indexed account,
        address indexed entryPoint,
        bytes32 indexed laneId,
        bytes32 appId,
        bytes32 delegateId,
        address validator,
        uint192 nonceKey,
        NonceKeyAssignment assignmentMode,
        bytes32 laneSalt,
        bytes32 permissionHash,
        bytes32 laneConfigHash,
        bytes32 metadataHash
    );

    event LaneRevoked(
        address indexed account,
        address indexed entryPoint,
        uint192 indexed nonceKey,
        bytes32 laneId,
        bytes32 appId,
        bytes32 delegateId,
        address validator
    );

    event LaneSuspended(
        address indexed account,
        address indexed entryPoint,
        uint192 indexed nonceKey,
        bytes32 laneId,
        bytes32 appId,
        bytes32 delegateId,
        address validator
    );

    event LaneReactivated(
        address indexed account,
        address indexed entryPoint,
        uint192 indexed nonceKey,
        bytes32 laneId,
        bytes32 appId,
        bytes32 delegateId,
        address validator,
        NonceKeyAssignment assignmentMode,
        bytes32 laneSalt,
        bytes32 permissionHash,
        bytes32 laneConfigHash,
        bytes32 metadataHash
    );

    event LaneUpdated(
        address indexed account,
        address indexed entryPoint,
        uint192 indexed nonceKey,
        bytes32 laneId,
        bytes32 appId,
        bytes32 delegateId,
        address validator,
        NonceKeyAssignment assignmentMode,
        bytes32 laneSalt,
        bytes32 permissionHash,
        bytes32 laneConfigHash,
        bytes32 metadataHash
    );

    function executionLaneVersion() external view returns (uint256);

    function isTrustedExecutionLaneEntryPoint(
        address account,
        address entryPoint
    ) external view returns (bool);

    function getLane(
        address account,
        address entryPoint,
        bytes32 laneId
    ) external view returns (LaneConfig memory lane);

    function getLaneId(
        address account,
        address entryPoint,
        bytes32 appId,
        bytes32 delegateId,
        address validator,
        bytes32 laneSalt
    ) external view returns (bytes32 laneId);

    function getNonceKey(
        address account,
        address entryPoint,
        bytes32 appId,
        bytes32 delegateId,
        address validator,
        bytes32 laneSalt
    ) external view returns (uint192 nonceKey);

    function getLaneByNonceKey(
        address account,
        address entryPoint,
        uint192 nonceKey
    ) external view returns (LaneConfig memory lane);

    function isLaneActiveForContext(
        address account,
        address entryPoint,
        uint192 nonceKey,
        bytes32 delegateId,
        LaneAuthContext calldata context
    ) external view returns (bool);

    function getLaneNonce(
        address account,
        address entryPoint,
        uint192 nonceKey
    ) external view returns (uint256 nonce);
}

interface IERCXXXXLaneManager is IERCXXXXLaneRegistry {
    function registerLane(
        address account,
        address entryPoint,
        bytes32 laneId,
        bytes32 appId,
        bytes32 delegateId,
        address validator,
        uint192 nonceKey,
        NonceKeyAssignment assignmentMode,
        bytes32 laneSalt,
        bytes32 permissionHash,
        bytes32 laneConfigHash,
        uint48 validAfter,
        uint48 validUntil,
        bytes32 metadataHash
    ) external returns (LaneConfig memory lane);

    function updateLane(
        address account,
        address entryPoint,
        uint192 nonceKey,
        bytes32 permissionHash,
        bytes32 laneConfigHash,
        uint48 validAfter,
        uint48 validUntil,
        bytes32 metadataHash
    ) external returns (LaneConfig memory lane);

    function suspendLane(
        address account,
        address entryPoint,
        uint192 nonceKey
    ) external;

    function reactivateLane(
        address account,
        address entryPoint,
        uint192 nonceKey,
        bytes32 permissionHash,
        bytes32 laneConfigHash,
        uint48 validAfter,
        uint48 validUntil,
        bytes32 metadataHash
    ) external returns (LaneConfig memory lane);

    function revokeLane(
        address account,
        address entryPoint,
        uint192 nonceKey
    ) external;
}

The isLaneActiveForContext function is a registry/account introspection helper. It MAY be used by wallets and apps for preflight checks, but the authoritative validation result for a UserOperation remains ERC-4337 validation through validateUserOp / bundler simulation.

executionLaneVersion() MUST return 1 for implementations of this ERC version.

Lifecycle events index different identifiers based on common query paths. LaneRegistered indexes laneId for discovery. Other lifecycle events index nonceKey; indexers tracking a lane across its full lifecycle SHOULD correlate events by (account, entryPoint, nonceKey) and decode laneId from event data when present. Events are synchronization hints; the authoritative lane state is the account or current authoritative registry returned by executionLaneRegistry().

Accounts that store lane data directly, and lane registry contracts that store lane data for accounts, MUST implement ERC-165 and return true for the interface ID of IERCXXXXLaneRegistry.

Accounts that use an external registry for ERC-compliant app-scoped lanes MUST implement IERCXXXXLaneRegistryProvider. Wallets and indexers MUST treat events from an external registry as authoritative for an account only if the account currently returns that registry from executionLaneRegistry().

If an account changes its authoritative external registry, it MUST define one of the following behaviors:

1. Migration:
   The new registry preserves laneId, nonceKey, assignmentMode, laneSalt, status, appId, delegateId, validator, permissionHash, validity, and metadata for migrated lanes, computes a new laneConfigHash using the new registry address, and emits LaneRegistered for each migrated lane.

2. Invalidation:
   Lanes stored only in the previous registry are no longer authoritative after the registry change.

Invalidation MUST NOT erase revoked nonceKey history. The account MUST preserve revoked (account, entryPoint, nonceKey) burn state across registry changes, or the new authoritative registry MUST be initialized with that burn state before it can register new lanes for the account.

An account MUST NOT switch to an external registry that can register lanes for the account unless revoked nonceKey burn history is preserved by the account itself or enforced by the new registry before any new lane registration is accepted.

Where feasible, the current authoritative account or registry SHOULD expose burned nonceKeys as REVOKED through getLaneByNonceKey. If complete historical lane configuration is unavailable after registry invalidation, the implementation MUST still provide enough account or registry state to reject re-registration of burned (account, entryPoint, nonceKey) values.

Wallets and apps MUST refresh lane state after ExecutionLaneRegistryChanged. Pending UserOperations whose validation depends on lane state from the previous registry may fail validation unless the lane was migrated before submission.

Implementing IERCXXXXLaneManager is OPTIONAL. Accounts MAY manage lanes through their native execution interface or through a module instead of exposing mutable lane-management functions directly.

getLaneId(...) MUST return the canonical laneId for the supplied inputs. It does not prove that the returned laneId is registered or active.

getNonceKey(...) MUST compute the canonical DERIVED laneId from the supplied inputs and then return the canonical DERIVED nonceKey for that laneId. It does not prove that the returned nonceKey is registered or active. Wallets and apps MUST use getLaneByNonceKey or isLaneActiveForContext to inspect live lane state.

getLaneNonce(account, entryPoint, nonceKey) SHOULD return:

IEntryPoint(entryPoint).getNonce(account, nonceKey)

The returned value is the ERC-4337 nonce value for the lane. Its high 192 bits are nonceKey and its low 64 bits are the next valid sequence.

getLaneNonce reports only the EntryPoint nonce sequence. It does not imply that the lane is ACTIVE or usable. Callers MUST check lane status and authorization separately before submitting a UserOperation.

This ERC does not standardize assignment-mode capability discovery. Wallets and account services MAY expose policy-specific discovery APIs, such as supported assignment modes for an account and EntryPoint, but callers MUST treat the lane registry and ERC-7715 response or rejection as the interoperable source of truth.

Nonce Key Derivation

Accounts MAY allow explicit nonce key registration. Wallets and apps SHOULD support deterministic key derivation.

This ERC recognizes two nonce key assignment modes:

DERIVED:
  laneId and nonceKey are computed using the canonical derivation in this section.

EXPLICIT:
  laneId is computed using the canonical derivation in this section, but nonceKey is explicitly assigned by account policy.

An implementation MAY support either or both modes. Both modes use the canonical laneId derivation. The difference is nonceKey assignment: DERIVED lanes compute nonceKey using the canonical derivation, while EXPLICIT lanes assign nonceKey by account policy. A lane using EXPLICIT assignment MUST NOT claim that its nonceKey was derived by this ERC unless the nonceKey equals the canonical derivation result. Wallets and apps MUST treat derivation conformance and lane registration as separate facts: a lane can be validly registered without using the recommended nonceKey derivation, but it is not a DERIVED lane unless the standard nonceKey formula matches.

assignmentMode and laneSalt are part of the lane’s discoverable configuration. For DERIVED lanes, laneSalt MUST be the salt used to compute laneId and nonceKey, and registries MUST expose it through LaneConfig. For EXPLICIT lanes, laneSalt SHOULD be bytes32(0) unless account policy uses it as additional metadata; callers MUST NOT use laneSalt to infer canonical nonceKey derivation for EXPLICIT lanes.

Recommended laneId derivation:

laneId = keccak256(abi.encode(
  "ERCXXXX_APP_SCOPED_LANE_ID_V1",
  account,
  entryPoint,
  appId,
  delegateId,
  validator,
  laneSalt
))

Recommended nonceKey derivation:

nonceKey = uint192(bytes24(keccak256(
  abi.encode(
    "ERCXXXX_APP_SCOPED_NONCE_LANE_V1",
    account,
    entryPoint,
    laneId,
    appId,
    delegateId,
    validator,
    laneSalt
  )
)))

bytes24(keccak256(...)) means the leftmost, high-order 24 bytes of the 32-byte keccak256 digest, interpreted as a big-endian uint192. Implementations MUST NOT instead use the rightmost, low-order 24 bytes.

chainId is intentionally excluded by default so the same account, EntryPoint, app, and delegate configuration can use the same nonceKey across chains, while each chain still maintains an independent sequence.

If a lane must be chain-specific, the appId or laneSalt SHOULD include chainId. If the EntryPoint address differs across chains and the application still wants the same nonceKey, the application SHOULD use a stable logical EntryPoint identifier in laneSalt and explicitly register the resulting nonceKey on each chain.

App ID

appId SHOULD be stable and collision-resistant.

If an application uses a textual identifier for appId, it SHOULD convert that identifier to bytes32 as keccak256(bytes(identifier)). Implementations MUST NOT rely on UTF-8 padding, ABI string encoding, or other non-hash string-to-bytes32 conversions unless the application explicitly defines and documents that app-specific scheme.

For cross-chain applications, the same appId SHOULD be reused across chains unless the app intentionally wants chain-specific lanes.

Permission and Config Hashes

permissionHash commits to the permission scope enforced for the lane.

The canonical permission hash is:

permissionHash = keccak256(abi.encode(
  LANE_PERMISSION_TYPEHASH,
  appId,
  validator,
  permissionSchema,
  permissionDataHash
))

Where:

LANE_PERMISSION_TYPEHASH =
  keccak256("LanePermission(bytes32 appId,address validator,bytes32 permissionSchema,bytes32 permissionDataHash)")

permissionDataHash = keccak256(permissionData)

permissionSchema identifies the deterministic encoding and semantics of permissionData. It SHOULD be derived from a stable schema identifier or typed-data type string. If the schema source is textual, it MUST be converted to bytes32 as keccak256(bytes(schemaIdentifier)) or keccak256(bytes(typeString)). Implementations MUST NOT use UTF-8 padding, ABI string encoding, or other non-hash conversions for textual schema identifiers.

permissionData is application- or module-specific encoded permission data. Implementations SHOULD use ABI encoding or EIP-712 typed data for permissionData. If an implementation uses a module-specific encoding, that encoding MUST be deterministic and MUST be identified by permissionSchema.

Wallets and apps MUST NOT assume that two lanes have the same effective permissions merely because their human-readable permission descriptions are similar. Equality is determined by permissionHash.

validator is included in permissionHash because the validator determines how permission data is interpreted and enforced. The same permissionData under two validators may have different semantics and MUST NOT be treated as the same permission.

permissionHash intentionally does not include delegateId or lane identity. It describes reusable permission semantics under a specific app, validator, and schema. laneConfigHash binds those permissions to a concrete account, registry, EntryPoint, delegate, lane, nonceKey, nonce key assignment mode, lane salt, and validity window.

laneConfigHash commits to the lane binding and validity bounds:

laneConfigHash = keccak256(abi.encode(
  LANE_CONFIG_TYPEHASH,
  account,
  registry,
  entryPoint,
  laneId,
  appId,
  delegateId,
  validator,
  nonceKey,
  assignmentMode,
  laneSalt,
  permissionHash,
  validAfter,
  validUntil,
  metadataHash
))

Where:

LANE_CONFIG_TYPEHASH =
  keccak256("LaneConfig(address account,address registry,address entryPoint,bytes32 laneId,bytes32 appId,bytes32 delegateId,address validator,uint192 nonceKey,uint8 assignmentMode,bytes32 laneSalt,bytes32 permissionHash,uint48 validAfter,uint48 validUntil,bytes32 metadataHash)")

assignmentMode MUST be encoded as the uint8 value of NonceKeyAssignment: 0 for DERIVED and 1 for EXPLICIT.

LaneConfig.laneConfigHash stores the canonical hash of the other lane configuration fields. It is not included recursively in the laneConfigHash computation.

registry is the authoritative lane registry for the account. If lane data is stored directly in the account, registry MUST be the account address. If lane data is stored in an external registry, registry MUST be the registry address that is authoritative for the account.

Updating validity bounds or metadata changes laneConfigHash. Updating permissionData changes both permissionHash and laneConfigHash.

Wallets and apps MUST refresh lane state after LaneUpdated or LaneReactivated. Pending UserOperations authorized under an older permissionHash or laneConfigHash may fail validation after an update.

UserOperation Requirements

A UserOperation using this ERC MUST set:

sender = account
nonce  = (uint256(nonceKey) << 64) | nonceSeq

Where:

nonceKey = uint192(userOp.nonce >> 64)
nonceSeq = uint64(userOp.nonce)

Apps MUST obtain the next sequence from EntryPoint.getNonce(account, nonceKey) and use its low 64 bits as nonceSeq. Sequence overflow behavior is inherited from ERC-4337 EntryPoint nonce handling; apps MUST NOT construct a UserOperation whose low 64-bit sequence differs from the EntryPoint’s current sequence for the lane.

During validateUserOp, the account MUST:

1. Extract nonceKey = uint192(userOp.nonce >> 64)
2. Load lane config for the active EntryPoint and nonceKey
3. Reject if lane is not ACTIVE
4. Reject if the current EntryPoint does not match lane.entryPoint
5. Reject if validAfter / validUntil constraints fail
6. Validate delegate authorization
7. Validate permission scope against userOp.callData
8. Return ERC-4337 validationData

The EntryPoint handles sequence correctness. The account handles lane authorization.

Accounts SHOULD encode lane validAfter and validUntil into ERC-4337 validationData when the account’s validation model can represent those bounds. If delegate signatures, session keys, modules, or other validation components also impose validity bounds, the returned validationData SHOULD encode the intersection of all applicable bounds. If the account cannot or does not encode the lane bounds in validationData, it MUST fail validation outside the lane validity window. This keeps bundler simulation aligned with the lane’s time bounds.

All authorization checks that determine whether a delegate may use a lane MUST be enforced during validateUserOp. Execution-time permission checks MAY be used as an additional defense, but MUST NOT be the only enforcement mechanism for lane authorization.

Validation-time checks MUST comply with ERC-4337 validation rules. In particular, lane validation SHOULD be deterministic, bounded in gas, and compatible with bundler simulation. Permission checks used by unstaked accounts or unstaked validation entities SHOULD avoid depending on volatile external state. Permission schemes that require mutable quotas, daily spending limits, oracle state, or external contract reads MAY require staked validation entities, account-specific storage access patterns accepted by bundlers, or a split design where validation authorizes the lane and execution enforces additional stateful limits.

If a stateful permission can fail only during execution, wallets and apps MUST treat the UserOperation as potentially executable but not guaranteed to succeed. Such execution-time checks do not replace the requirement that lane identity and delegate authorization be validated in validateUserOp.

Delegate Authorization

Delegate authorization is the account-level proof that the UserOperation is being submitted under the correct lane authority.

This ERC does not mandate a single signature format. Accounts MAY use any of the following:

1. ECDSA or ERC-1271 signature by the delegate.
2. Session key signature bound to the lane.
3. MPC or threshold signature verified by a validator module.
4. ERC-6900 or ERC-7579 validation module.
5. ERC-7710 delegation redemption.
6. Custom account-native validation.

Regardless of mechanism, validation MUST bind the authorization to:

account
EntryPoint
chainId
nonceKey
nonceSeq
delegateId
validator
permissionHash or equivalent permission context
UserOperation hash or equivalent execution digest

This binding prevents a signature or permission granted for one lane from being replayed on another lane, another account, another chain, or another EntryPoint.

For non-address signers, such as passkeys, WebAuthn keys, MPC key shares, or aggregated signature schemes, delegateId identifies the logical delegate authority and validator identifies the contract or account component responsible for validation. The exact signer material SHOULD be committed by permissionHash or the account’s module configuration.

Permission Binding

A lane MUST bind nonce usage to permission scope.

permissionHash SHOULD commit to the full permission object, even if the account stores or interprets permissions through another module.

ERC-7715 Integration

ERC-7715 defines wallet permission requests. Implementations that claim support for ERC-7715 integration with this ERC MUST implement the permission type defined in this section.

This section is OPTIONAL for implementations that do not claim ERC-7715 support.

Permission Type

The permission type string is:

app-scoped-nonce-lane

A request using this permission type MUST include:

{
  "type": "app-scoped-nonce-lane",
  "isAdjustmentAllowed": true,
  "data": {
    "account": "0x...",
    "entryPoint": "0x...",
    "appId": "0x...",
    "delegateId": "0x...",
    "validator": "0x...",
    "validAfter": "0x...",
    "validUntil": "0x...",
    "permissionSchema": "0x...",
    "permissionData": "0x...",
    "permissionDataHash": "0x..."
  }
}

JSON numeric fields in this section, including validAfter and validUntil, MUST be hex-encoded unsigned integer strings. validAfter and validUntil MUST fit within the Solidity uint48 range.

The request MAY include:

{
  "data": {
    "allowedAssignmentModes": ["DERIVED"],
    "laneId": "0x...",
    "laneSalt": "0x...",
    "requestedNonceKey": "0x...",
    "permissionHash": "0x...",
    "laneConfigHash": "0x...",
    "metadataHash": "0x..."
  }
}

allowedAssignmentModes is optional. Values MUST be the strings "DERIVED" or "EXPLICIT". If omitted, the wallet MAY grant either DERIVED or EXPLICIT assignment according to account policy and the rest of the request. If present, the wallet MUST grant only a mode listed by the requester or reject the request.

When allowedAssignmentModes is omitted and both DERIVED and EXPLICIT assignment are available, wallets SHOULD prefer DERIVED assignment unless account policy, collision avoidance, or user approval selects EXPLICIT assignment. The wallet response MUST always return the granted assignmentMode.

requestedNonceKey is advisory unless isAdjustmentAllowed is false. If isAdjustmentAllowed is false and requestedNonceKey is present, the wallet MUST either grant the requested nonceKey or reject the request.

laneSalt is advisory when isAdjustmentAllowed is true; the wallet MAY choose a different salt to avoid collisions or satisfy account policy. If isAdjustmentAllowed is false and laneSalt is present, the wallet MUST either use the requested laneSalt and derived laneId or reject the request. The wallet response MUST include the final laneId, nonceKey, assignmentMode, and laneSalt.

If isAdjustmentAllowed is false, the request MUST include enough data to identify the exact assignment requested. A DERIVED exact request MUST include laneSalt. An EXPLICIT exact request MUST include requestedNonceKey. If the request permits both DERIVED and EXPLICIT, it MUST include laneSalt, requestedNonceKey, or both. If neither is provided, the wallet MUST reject the request because it cannot satisfy an exact lane request.

If isAdjustmentAllowed is false and the request includes requestedNonceKey but omits laneSalt, the wallet MUST NOT attempt to find a salt whose derived nonceKey equals requestedNonceKey. Under DERIVED assignment this is a keccak preimage search and is not a feasible interoperability requirement. The wallet MAY accept the request only if the account supports EXPLICIT nonceKey assignment, account policy permits the requested nonceKey, and allowedAssignmentModes is omitted or includes EXPLICIT. Otherwise the wallet MUST reject the request.

If isAdjustmentAllowed is false and the request includes both laneSalt and requestedNonceKey, a DERIVED lane MUST use the canonical nonceKey computed from that laneSalt. If the computed nonceKey does not equal requestedNonceKey, the wallet MUST reject the request unless it is intentionally granting an EXPLICIT lane and allowedAssignmentModes is omitted or includes EXPLICIT.

For this ERC-7715 permission type, permissionData is hex-encoded bytes and permissionDataHash MUST equal keccak256(permissionData). permissionHash and laneConfigHash MUST be computed as defined in Permission and Config Hashes, using permissionSchema as part of the permission hash. Wallets MAY omit permissionHash or laneConfigHash in the request if they expect the wallet or account to compute them, but any returned value MUST match the canonical computation.

A request-provided laneConfigHash is an exact constraint over the final granted lane, including the final account, registry, EntryPoint, laneId, appId, delegateId, validator, nonceKey, assignmentMode, laneSalt, permissionHash, validity bounds, and metadataHash. If a wallet would grant a lane whose canonical laneConfigHash differs from the request-provided value, it MUST reject the request. If the requester wants the wallet to adjust any field that affects laneConfigHash, it MUST omit laneConfigHash from the request.

The wallet response MUST include:

{
  "account": "0x...",
  "entryPoint": "0x...",
  "laneId": "0x...",
  "appId": "0x...",
  "delegateId": "0x...",
  "validator": "0x...",
  "nonceKey": "0x...",
  "assignmentMode": "DERIVED",
  "laneSalt": "0x...",
  "permissionHash": "0x...",
  "laneConfigHash": "0x...",
  "validAfter": "0x...",
  "validUntil": "0x...",
  "metadataHash": "0x..."
}

Wallets MUST reject malformed requests where:

1. account is not controlled or managed by the wallet.
2. entryPoint is not supported by the account.
3. delegateId is zero and the permission type requires a specific delegate.
4. validator is address(0) and the account does not support native validator resolution.
5. requestedNonceKey is present and the same `(account, entryPoint, requestedNonceKey)` is ACTIVE, SUSPENDED, or REVOKED.
6. validUntil is non-zero and validUntil <= validAfter.
7. laneId is present but does not match the canonical laneId derivation accepted by the wallet/account.
8. permissionDataHash, permissionHash, or laneConfigHash is present but does not match the canonical computation.
9. allowedAssignmentModes is present but the wallet cannot grant any listed assignment mode under account policy.
10. the wallet would grant DERIVED assignment but laneSalt, laneId, or nonceKey does not match the canonical derivation.
11. the wallet would grant EXPLICIT assignment for an exact request, but requestedNonceKey is omitted.

ERC-7715 reference:

Relationship to ERC-7582

ERC-7582 uses the ERC-4337 nonce key as a validator or plugin pointer. This ERC is different.

ERC-7582:
  nonceKey identifies validation plugin

This ERC:
  nonceKey identifies app/delegate execution lane

The two can be compatible. A lane may route validation to a plugin, but the standard meaning of the lane is the delegate’s execution namespace, not merely the validator address.

ERC-7582 reference:

Relationship to Subaccounts

This ERC is not a subaccount standard.

Subaccounts:

separate address
separate nonce
separate balance or allowance
asset fragmentation

App-scoped nonce lanes:

same address
same account
same asset pool
separate nonce lane
permission-scoped execution

ERC-7895 standardizes wallet APIs for hierarchical accounts and subaccounts. This ERC addresses the lighter-weight case where a separate account is unnecessary.

ERC-7895 reference:

Mempool and Bundler Considerations

Bundlers already need to track ERC-4337 nonce keys. This ERC does not require new bundler RPC methods. Wallets, account services, or indexing services MAY improve UX by exposing lane-aware APIs, but such APIs are optional and not consensus rules.

A bundler SHOULD treat two UserOperations as conflicting if they share:

(sender, nonceKey, nonceSeq)

Different nonce keys from the same sender SHOULD NOT conflict solely because they share the same sender.

Cross-Chain Behavior

A lane MAY exist on multiple chains with the same:

account
entryPoint
appId
delegateId
validator
nonceKey

But nonce sequences are chain-local:

EntryPoint(chain A).getNonce(account, nonceKey)
EntryPoint(chain B).getNonce(account, nonceKey)

These values are independent.

Applications MUST bind signatures to chainId, EntryPoint, and account address through ERC-4337’s UserOperation hash. Cross-chain lane identity does not imply cross-chain replayability.

Security Considerations

Lane Squatting

Accounts SHOULD require explicit registration or owner approval before a nonceKey becomes active.

Lane Collision

Lane managers MUST reject registration if the same (account, entryPoint, nonceKey) is already ACTIVE, SUSPENDED, or REVOKED. A revoked nonceKey remains burned even if a later registration would use identical lane configuration.

Namespace Collisions

Accounts may use ERC-4337 nonce keys for purposes outside this ERC. Wallets and apps MUST NOT assume that every non-zero nonceKey is an app-scoped lane. Accounts SHOULD reserve a domain-derived key space for app-scoped lanes and SHOULD document or expose how app-scoped lane keys are distinguished from other account-specific nonce keys.

Unauthorized Lane Usage

The account MUST verify that the delegate authority represented by delegateId and validator is authorized for the nonceKey. ERC-4337 nonce validation alone is insufficient.

Permission Bypass

The account MUST validate callData against the lane permission. A lane must not become an unrestricted account key unless explicitly intended.

Revocation Race

Revocation affects future validation. Already included UserOperations cannot be undone. Pending UserOperations may remain in external mempools but should fail validation after revocation.

Shared Balance Risk

This ERC isolates nonce queues, not account balance. Two lanes may still race for the same token balance. Spending limits and reservation mechanisms are out of scope but recommended.

Metadata Privacy

Public lane events may reveal app usage. Wallets MAY store metadata off-chain and emit only hashes or content-addressed metadata URIs.

Registry Authenticity

Anyone can deploy a registry and emit lane-like events for any account. Wallets, apps, and indexers MUST verify that a registry is authoritative for an account before relying on its state or events. If the account implements IERCXXXXLaneRegistryProvider, the authoritative external registry is the address returned by executionLaneRegistry().

Rationale

ERC-4337 already has the right primitive: key + sequence. The missing layer is interoperability. Without a standard, each wallet may derive keys differently, each app may request lanes differently, and users cannot inspect or revoke delegate lanes consistently.

This ERC standardizes the smallest useful surface:

register lane
query lane
bind lane to delegateId and validator
read lane nonce
revoke lane

It avoids creating subaccounts and avoids changing ERC-4337.

Backwards Compatibility

This ERC is fully backward compatible with ERC-4337 accounts.

Accounts that do not implement this ERC continue to work normally. Apps MAY fall back to ordinary ERC-4337 nonce usage or wallet-specific session key systems.

Test Vectors

Final submission of this ERC SHOULD include test vectors for the following canonical computations:

1. laneId derivation
2. nonceKey derivation
3. permissionDataHash
4. permissionHash
5. laneConfigHash
6. assignmentMode encoding
7. ERC-7715 request hash validation
8. ERC-4337 UserOperation nonce packing and unpacking

Test vectors SHOULD specify:

account
registry
entryPoint
appId
delegateId
validator
laneSalt
laneId
nonceKey
assignmentMode
validAfter
validUntil
permissionSchema
permissionData
permissionDataHash
permissionHash
metadataHash
laneConfigHash

All test vectors MUST use Solidity abi.encode semantics for hashes defined in this ERC. Hex strings in JSON test vectors MUST be decoded to bytes before hashing.

Reference Implementation

A reference implementation SHOULD include:

1. A minimal IERCXXXXLaneRegistry implementation.
2. An optional IERCXXXXLaneManager implementation.
3. An ERC-4337 account or account module that enforces lane validation in validateUserOp.
4. Unit tests for lane registration, suspension, reactivation, revocation, and permanent nonceKey burn.
5. Unit tests for validation failure on unknown app-scoped lane keys, suspended lanes, revoked lanes, or unauthorized lanes.
6. Test vectors for laneId, nonceKey, assignmentMode, permissionHash, and laneConfigHash.

Reference implementations MUST NOT be treated as the only valid architecture. Accounts may store lane data directly, through modules, or through an external registry, provided they satisfy this ERC’s conformance requirements.

Conformance

An ERC-4337 account is compliant with this ERC if it satisfies all of the following:

1. It validates UserOperations using app-scoped lanes according to this ERC.
2. It rejects UserOperations using nonce keys classified as app-scoped lanes unless the corresponding lane is ACTIVE and authorized.
3. It binds delegate authorization to nonceKey and the UserOperation execution digest.
4. It enforces lane identity and delegate authorization during UserOperation validation.
5. It exposes lane data by implementing IERCXXXXLaneRegistry directly or, if it uses an external registry for ERC-compliant lanes, by implementing IERCXXXXLaneRegistryProvider and returning a registry that implements IERCXXXXLaneRegistry.
6. It prevents duplicate `(account, entryPoint, nonceKey)` registration across ACTIVE, SUSPENDED, and REVOKED lane states, either directly or through its lane registry.
7. It does not reject unrelated account-specific nonce keys solely because they are not registered app-scoped lanes, unless the account intentionally supports only app-scoped lanes.
8. It validates every lane's `laneId` against the canonical laneId derivation, and validates DERIVED lane `nonceKey` against the canonical nonceKey derivation, or relies on an authoritative registry or manager that performed those checks at registration.

A lane registry is compliant if it satisfies all of the following:

1. It implements IERCXXXXLaneRegistry and ERC-165.
2. It exposes lane discovery for account + entryPoint + laneId and for account + entryPoint + nonceKey. For incomplete REVOKED records whose original laneId is unavailable, nonceKey discovery is sufficient.
3. It exposes the current lane nonce through EntryPoint.getNonce(account, nonceKey) or an equivalent wrapper.
4. It reports ACTIVE, SUSPENDED, REVOKED, and NONE consistently with the lane state machine.
5. It emits lane registration, update, suspension, reactivation, and revocation events when it manages mutable lane state.
6. If it is external to the account, the account exposes it through IERCXXXXLaneRegistryProvider.
7. It reports permissionHash and laneConfigHash values supplied by the authoritative account, module, or manager. If the registry manages lane state directly, it MUST verify these values match the canonical computations in this ERC.
8. It exposes assignmentMode and laneSalt in LaneConfig, and DERIVED lanes expose the laneSalt needed to verify canonical laneId and nonceKey derivation.

A lane manager is compliant if it satisfies all lane registry requirements and:

1. It implements IERCXXXXLaneManager and ERC-165.
2. It prevents unauthorized registration, update, suspension, reactivation, and revocation of lanes.
3. It rejects registration for `(account, entryPoint, nonceKey)` values that are ACTIVE, SUSPENDED, or REVOKED.
4. It prevents updateLane and reactivateLane from changing lane identity fields: entryPoint, laneId, appId, delegateId, validator, nonceKey, assignmentMode, and laneSalt.
5. It enforces state-specific mutation preconditions: update only ACTIVE lanes, suspend only ACTIVE lanes, reactivate only SUSPENDED lanes, and revoke only ACTIVE or SUSPENDED lanes.
6. It permanently burns revoked nonceKeys.
7. It rejects caller-provided permissionHash or laneConfigHash values that do not match the canonical computations.
8. It rejects lane registration if laneId does not match the canonical derivation for the supplied laneSalt.
9. It rejects DERIVED lane registration if nonceKey does not match the canonical derivation for the supplied laneSalt.
10. It rejects EXPLICIT lane registration unless account policy permits explicit nonceKey assignment.

A wallet or app integration is compliant if it satisfies all of the following:

1. It obtains an account-approved lane before submitting UserOperations through that lane.
2. It reads the next nonce from the account's EntryPoint using the lane nonceKey.
3. It constructs UserOperation.nonce as (nonceKey << 64) | nonceSeq.
4. It does not reuse a lane assigned to another appId, delegateId, or validator.
5. It handles revocation or permission updates by refreshing lane state before submission.
6. It verifies assignmentMode and, for DERIVED lanes, verifies laneId and nonceKey against the exposed laneSalt when it relies on deterministic lane derivation.

Summary

The core proposal is:

Use ERC-4337 nonceKey as a standardized app/delegate execution lane.
Use EntryPoint for nonce sequencing.
Use the account for permission enforcement.
Use wallet APIs for lane request, discovery, and revocation.
Do not create subaccounts unless asset/address isolation is required.

This gives a clear standards position: permissioned nonce lanes for smart accounts.

Editorial Notes

Before formal ERC submission, authors SHOULD consider moving the following material into appendices:

1. ERC-7715 integration
2. Operational guidance for wallets, indexers, and bundlers
3. Test vectors

The core normative ERC should remain focused on interfaces, lane state, nonce key namespace, authorization requirements, hash computation, registry authority, and conformance.