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.