ERC-7579 standardizes a core module system for modular smart accounts: module types, the module lifecycle, installation, and account configuration. This proposal extends that system to EIP-8141 native account abstraction by defining the validation flow for frame transactions. It reuses ERC-7579’s module structure unchanged: a validator returns an approval mode, and the account applies it through EIP-8141’s APPROVE instruction during a VERIFY frame. Keeping the module system in ERC-7579 and layering account-abstraction-specific validation flows as extensions lets a single module ecosystem serve accounts across multiple account abstraction implementations, without forking modules or vendor lock-in.
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.
Relationship to ERC-7579
This standard is an extension to ERC-7579 and does not redefine its module system. A compliant account and its modules MUST conform to ERC-7579 for:
module types and their identifiers,
the module lifecycle interface (onInstall, onUninstall, isModuleType),
module installation and configuration (installModule, uninstallModule, isModuleInstalled), and
ERC-7579’s execution interface (execute, executeFromExecutor) is required only of an account that declares the contract-dispatched execution route, see Execution.
This standard adds only what EIP-8141 native account abstraction requires: the approval-mode validation flow and the interfaces defined below. It is the EIP-8141 analogue of ERC-7579’s own validation flow. Where ERC-7579 defines validateUserOp for its ERC-4337 target, this standard defines validateFrame.
Approval Mode
This standard defines the approval mode: a uint8 bearing the value of the EIP-8141APPROVE instruction’s scope operand. It is the result of validation as defined in this standard — a validator returns an approval mode to the account, and the account applies it by executing the APPROVE instruction with that value during a VERIFY frame.
The bit semantics are equivalent to those of the EIP-8141 scope operand. The two least significant bits form a bitmask:
bit 0 (payment): the account approves paying the transaction’s total gas cost.
bit 1 (execution): the account approves subsequent frames acting on its behalf as sender.
This yields the following modes, which an account MUST interpret consistently with EIP-8141’s APPROVE scope operand:
Mode
Name
Payment
Execution
0x0
APPROVE_NONE
no
no
0x1
APPROVE_PAYMENT
yes
no
0x2
APPROVE_EXECUTION
no
yes
0x3
APPROVE_EXECUTION_AND_PAYMENT
yes
yes
APPROVE_SCOPE_MASK is defined as 0x3. A frame’s allowed scope is frame.flags & APPROVE_SCOPE_MASK, equivalently FRAMEPARAM(frameIndex, 0x06).
Accounts are NOT REQUIRED to grant every mode. The account MUST NOT approve a mode outside the allowed scope of the executing VERIFY frame, and APPROVE_EXECUTION is only valid when the frame’s resolved target is tx.sender. If validation does not succeed, the account MUST NOT call APPROVE, which leaves the mode at APPROVE_NONE and invalidates the transaction.
An account MUST declare which approval modes it supports through supportsApprovalMode (see below), and MUST NOT call APPROVE with a mode it does not support.
EIP-8141 Frame Validator
ERC-7579 defines validateUserOp validators and assigns module type id 1 to them.
An EIP-8141 frame validator is a module that determines the approval mode a transaction is entitled to.
This standard assigns frame validators a new module type id: 11 .
A module MAY additionally be an ERC-7579 validator (type id 1) and serve both targets.
When the account’s code executes in a VERIFY frame, the account selects a frame validator and calls it. The validator returns an approval mode, which the account then applies via APPROVE.
In place of ERC-7579’s validateUserOp validation function, an EIP-8141 validator MUST implement ERC-7579’s IERC7579Module interface and the IFrameValidator interface below:
interfaceIFrameValidatorisIERC7579Module{/**
* @dev Validates the active VERIFY frame and returns the approval mode the
* validator authorizes for the transaction.
* @param data validator-specific calldata supplied by the account, e.g. a
* signature envelope or policy parameters.
*
* Transaction context — the canonical signing hash (`TXPARAM(0x08)`), the executing
* frame index (`TXPARAM(0x0A)`), the frame's allowed scope
* (`FRAMEPARAM(frameIndex, 0x06)`), and any frame contents needed for policy — MUST
* be obtained through EIP-8141 introspection instructions, not through function
* arguments. This prevents an account (or a caller) from overriding those values.
*
* MUST NOT modify state; the validator is invoked within the STATICCALL context of
* a VERIFY frame.
* MUST return APPROVE_NONE (0x0) if validation fails.
* MAY revert to indicate failures not related to the core validation logic directly (e.g. decoding errors).
* The returned approval mode SHOULD be a subset of the frame's allowed scope; the
* account is the final authority and MUST mask the result with `allowedScope`
* before approving.
*/functionvalidateFrame(bytescalldatadata)externalviewreturns(uint8approvalMode);}
The account’s behavior when running in a VERIFY frame targeting itself is:
Read the executing frame index frameIndex = TXPARAM(0x0A) and its allowed scope allowedScope = FRAMEPARAM(frameIndex, 0x06).
Select an installed validator. This standard does not dictate the selection mechanism (see Validator Selection). The account MUST verify the selected module is an installed frame validator (module type id 11 ).
Call validateFrame on the selected validator with the validator-specific calldata only, then mask the result: approvalMode = approvalMode & allowedScope.
If approvalMode has the execution bit (bit 1) set, the account MUST check the modes presented by the transaction’s SENDER frames (see SENDER Frame Execution Modes) against the execution modes it supports (supportsExecutionMode, inherited from IERC7579AccountConfig). If supportsExecutionMode returns false for any presented mode, the account MUST clear the execution bit, leaving approvalMode = approvalMode & APPROVE_PAYMENT.
If approvalMode still has the execution bit set and the account is contract-dispatched (see Execution), the account MUST check that every SENDER frame’s resolved target (FRAMEPARAM(i, 0x00)) is the account itself. If any SENDER frame targets another address, the account MUST clear the execution bit.
If the resulting approvalMode is APPROVE_NONE, the account MUST NOT call APPROVE; the frame reverts and the transaction is invalid.
Otherwise the account MUST call APPROVE(approvalMode).
The raw signature bytes of EIP-8141tx.signatures are intentionally not accessible from the EVM. A validator therefore obtains its authorization material either by reading the metadata of an already protocol-validated signature via the SIGPARAM instruction (for the natively supported SECP256K1 and P256 schemes), or by receiving a self-contained signature envelope in data and verifying it itself (for module-defined schemes such as passkeys, multisig, or BLS).
Because EIP-8141 permits only a frame’s resolved target (or code it DELEGATECALLs into) to call APPROVE, a validator reached via STATICCALL cannot approve on its own. The account remains the sole caller of APPROVE.
A validator is called by the account it validates for, so it identifies that account as msg.sender. Since it no longer takes the frame context as arguments, a validator SHOULD confirm that msg.sender is the transaction’s sender (TXPARAM(0x02)) and the executing frame’s resolved target before treating the frame it reads through introspection as its own; otherwise any contract running in the same transaction could STATICCALL it and consume validation state that was never meant for that call.
SENDER Frame Execution Modes
This standard defines four execution modes describing the shape of a transaction’s SENDER frames. They extend ERC-7579’s mode encoding with two new callType values; the execType values keep their ERC-7579 meanings, which apply to frames verbatim:
callType0x02 : the transaction has exactly one SENDER frame.
callType0x03 : the transaction has multiple SENDER frames.
execType0x00 (revert-all): the frame belongs to an atomic-batch group (all-or-nothing).
execType0x01 (try): the frame is not part of an atomic batch; a revert discards only that frame.
The mode constants, encoded per ERC-7579’s mode layout (callType in byte 0, execType in byte 1, all remaining bytes zero):
Constant
callType
execType
Meaning
FRAME_MODE_SINGLE
0x02
0x01
one standalone SENDER frame
FRAME_MODE_SINGLE_ATOMIC
0x02
0x00
one SENDER frame inside an atomic batch
FRAME_MODE_BATCH
0x03
0x01
multiple SENDER frames, reverting independently
FRAME_MODE_BATCH_ATOMIC
0x03
0x00
multiple SENDER frames in an atomic batch
A transaction presents a mode for each of its SENDER frames: callType is 0x02 if the transaction has exactly one SENDER frame and 0x03 otherwise; execType is 0x00 if the frame belongs to an atomic batch and 0x01 otherwise. A transaction may present several modes at once — for example, an atomic group alongside an ungrouped frame presents both FRAME_MODE_BATCH_ATOMIC and FRAME_MODE_BATCH.
The account supports a transaction’s SENDER frames only if supportsExecutionMode returns true for every presented mode.
These modes are declarations for supportsExecutionMode and the validation-time check in step 4 above; they are never passed to execute, and the new callType values define no execute dispatch behavior.
Account Interface
A compliant account MUST implement the following interface. It extends ERC-7579’s IERC7579AccountConfig and IERC7579ModuleConfig, so module installation and approval-mode capability are exposed through a single account interface alongside the EIP-8141 validation flow. It adds no execution interface; ERC-7579’s applies unchanged where the account dispatches execution (see Execution):
interfaceIERC8286FrameAccountisIERC7579AccountConfig,IERC7579ModuleConfig{/**
* @dev Validates the active VERIFY frame and approves the transaction.
* Intended to be the call encoded in a VERIFY frame's `data`. The account
* selects an installed validator, obtains an approval mode from it, and masks
* that mode with the frame's allowed scope. It then checks the result against the
* approval modes it supports (`supportsApprovalMode`) and, when the execution
* bit is set, checks the transaction's SENDER frames against the execution modes
* it supports (`supportsExecutionMode`), before calling APPROVE with the result
* (see "EIP-8141 Frame Validator").
* @param data validator selection and validator-specific calldata.
* @return approvalMode the approval mode granted (also applied via APPROVE).
*
* MUST revert if the executing frame's mode is not VERIFY.
* MUST NOT modify state other than via the APPROVE instruction; it executes within
* the STATICCALL context of a VERIFY frame.
* MUST NOT call APPROVE with a mode outside the frame's allowed scope.
* MUST clear the execution bit if the transaction's SENDER frames present an
* execution mode the account does not support (`supportsExecutionMode`, see
* "SENDER Frame Execution Modes").
* MUST clear the execution bit if the account is contract-dispatched and any SENDER
* frame of the transaction resolves to a target other than the account (see
* "Execution").
* MUST NOT call APPROVE if validation fails (leaving the mode at APPROVE_NONE).
*/functionverify(bytescalldatadata)externalreturns(uint8approvalMode);/**
* @dev Returns whether the account supports a given approval mode.
* @param approvalMode the approval mode (see "Approval Mode" above), a uint8
* bitmask in the range 0x0..0x3.
*
* MUST return true if the account is capable of granting this mode during
* validation and false otherwise.
*/functionsupportsApprovalMode(uint8approvalMode)externalviewreturns(bool);/**
* @dev Returns the execution route the account uses (see "Execution"):
* 0x00 (EXECUTION_ROUTE_PROTOCOL) — SENDER frames call their targets directly and
* the account takes no part in execution;
* 0x01 (EXECUTION_ROUTE_CONTRACT) — every SENDER frame targets the account, which
* dispatches the operations through ERC-7579's execute.
*
* An account returning EXECUTION_ROUTE_CONTRACT MUST implement ERC-7579's execution
* interface, and its `verify` MUST NOT approve execution for a transaction with a
* SENDER frame resolving to any other target.
*/functionexecutionRoute()externalviewreturns(uint8);}
Execution
An EIP-8141 frame transaction executes operations over two routes:
Protocol-dispatched: a SENDER frame calls its target directly with caller = tx.sender. No account code takes part in the call, so the account has no execution-time enforcement point. Any policy over SENDER frames MUST be enforced at validation time, by the validator inspecting them before approving (see Security Considerations).
Contract-dispatched: every SENDER frame targets the account itself, invoking ERC-7579’s execute or executeFromExecutor with caller = tx.sender. The account’s code performs the calls itself, and ERC-7579 execution semantics — execution modes, supportsExecutionMode, hooks — apply unchanged.
The protocol enforces no such distinction: any SENDER frame may target the account or any other address, and a single transaction may mix both. An account MUST therefore choose one route and declare it through executionRoute (see Account Interface):
Route
Name
Meaning
0x00
EXECUTION_ROUTE_PROTOCOL
SENDER frames call their targets directly
0x01
EXECUTION_ROUTE_CONTRACT
every SENDER frame targets the account, which dispatches
A protocol-dispatched account MAY omit the execute and executeFromExecutor functions entirely. A contract-dispatched account MUST implement ERC-7579’s execution interface, and its VERIFY frame MUST NOT approve execution unless every SENDER frame in the transaction resolves to the account (step 5 above). That check is what makes the route a guarantee rather than a convention: approving execution without it would let one SENDER frame call an arbitrary target with the account’s own authority (caller = tx.sender), past the account’s execution logic, while the rest of the transaction went through execute.
Because a contract-dispatched account is reached by a SENDER frame, the call arrives as a self-call and the account’s ordinary msg.sender == address(this) access control authorizes it. An account that instead accepts execute calls from DEFAULT frames MUST NOT authorize them by msg.sender == ENTRY_POINT alone. In EIP-8141 the ENTRY_POINT address as caller carries no authorization — unlike an ERC-4337 EntryPoint, which calls the account only after validating its user operation. The account MUST authenticate such a call itself, by confirming that its own VERIFY frame in this transaction has succeeded (FRAMEPARAM(i, 0x05)), or by applying its ordinary ERC-7579 access control.
This standard therefore defines no new execution interface: ERC-7579 already covers contract-dispatched execution, and no interface can cover protocol-dispatched SENDER frames.
Rationale
Extension to ERC-7579 rather than a standalone standard
The value of a modular account standard is a portable module ecosystem: a validator, executor, or hook written once should work across accounts and wallets. Defining an independent module system for EIP-8141 would fork that ecosystem, forcing modules and tooling to choose between account abstraction implementations. Instead, this standard reuses ERC-7579’s module system unchanged and adds only the EIP-8141 validation flow.
This follows the upgrade path ERC-7579 describes for itself: as modular accounts are built on account abstraction implementations other than its original ERC-4337 target, the implementation-specific validation flow is moved into a separate, optional extension. This proposal is that extension for EIP-8141. The intended end state is ERC-7579 as the implementation-agnostic core module system, with per-implementation validation extensions layered on top.
Approval mode as the validation result
ERC-7579 standardizes a compact encoding for the execution side (its execution mode), letting an account express execution behavior in a single value. EIP-8141 provides the protocol-native counterpart for the validation side: the APPROVE scope. This standard surfaces validation results as that scope so the account never has to translate between a foreign validation-data encoding and the protocol’s own approval semantics. supportsApprovalMode complements ERC-7579’s supportsExecutionMode: the former declares which approval scopes the account can grant, the latter which execution shapes it will honor.
One execution route per account
Because the protocol draws no line between the two routes, an account that leaves the choice open gets the weaker of them. A transaction could send some operations through execute and carry others past it as plain SENDER frames, and the account’s execution logic — modes, hooks, whatever ERC-7579 policy it installs — would cover only part of what the transaction does. Requiring the account to commit to a route, and checking the commitment during validation, turns the distinction into something a validator can rely on.
For a contract-dispatched account the check is cheap and complete: every SENDER frame resolves to the account, so every operation passes through execute, and the execution-time enforcement point ERC-7579 assumes is restored under EIP-8141. A protocol-dispatched account gives that up deliberately, in exchange for the protocol calling its targets directly, and pays for it by enforcing its whole policy during validation.
No new execution interface
ERC-7579’s execute is the point where an account enforces execution policy: it decodes the mode, checks the call type against supportsExecutionMode, and reverts if unsupported. Under EIP-8141 that flow can be reused on the contract-dispatched route: a SENDER frame targets the account and calls its execute, and the account performs and checks the calls exactly as it would under ERC-4337 — with the call arriving as a self-call from tx.sender, which the account authorizes without having to read anything into the ENTRY_POINT caller.
On the protocol-dispatched route there is nothing to attach an interface to: the protocol calls a SENDER frame’s target directly — no account code is involved in making the call — and the only gate is the transaction-scoped sender_approved flag set once by APPROVE_EXECUTION.
For protocol-dispatched SENDER frames the enforcement point therefore moves entirely to the validation frames: a validator inspects the transaction’s SENDER frames during the VERIFY frame and only approves if they satisfy policy. supportsExecutionMode accordingly answers two kinds of query: for ordinary ERC-7579 modes it reports what the account’s execute can process, and for the frame modes defined in “SENDER Frame Execution Modes” it declares which frame shapes the account’s validators are willing to authorize — a policy enforced at validation time, not a capability checked at execution time.
Frame modes are policy declarations
The frame modes cover only the single/batch and atomic/non-atomic dimensions because the other ERC-7579 mode dimensions have no SENDER-frame counterpart: a SENDER frame is a plain protocol-orchestrated CALL, so there is no delegatecall analogue (account-level delegation exists only via EIP-7702 at the frame level), no staticcall analogue (the STATICCALL context in EIP-8141 is the VERIFY frame, not execution), and no room for custom modeSelector/modePayload semantics (the protocol owns dispatch).
The frame modes also describe a policy choice rather than a capability. In ERC-4337 an account must implement code to handle a batch, so supportsExecutionMode reports whether that code exists. Under EIP-8141 the protocol performs batching, atomicity, and dispatch with no account code, so every account is structurally capable of all of them; what an account or validator declares and enforces is which shapes it is willing to authorize (for example, refusing to approve a multi-frame transaction), not which it is able to perform.
Validator Selection
One recommended approach for accounts to select the validator module, where EIP-8250 keyed nonces are available, is to derive the validator from the keyed nonce, for example by interpreting the validator’s address as encoded in TXPARAM(0x0B) (nonce_keys[0]); because keyed nonces are committed by the canonical signing hash (TXPARAM(0x08)), this binds validator selection to the signed transaction.
Security Considerations
Execution is unconditional once approved
Granting APPROVE_EXECUTION (or APPROVE_EXECUTION_AND_PAYMENT) is a one-shot, transaction-wide authorization. After a VERIFY frame sets sender_approved, every subsequent SENDER frame in the transaction executes with caller = tx.sender and is not checked again. EIP-8141 consults no allow-list, target restriction, or execution-mode support on the account, and no account code mediates those calls. A validator that returns APPROVE_EXECUTION therefore authorizes arbitrary calls (arbitrary targets, values, and calldata) on the account’s behalf.
Because EIP-8141 provides no execution-time fallback for SENDER frames, all execution policy MUST be enforced at validation time. This includes both the account’s declared supportsExecutionMode set and any validator-level constraint (allow-list, spend limit, target restriction). Before granting APPROVE_EXECUTION, the account and its validator MUST inspect the transaction’s SENDER frames (their resolved target, value, and calldata) and MUST NOT approve if any frame presents an unsupported execution mode or falls outside policy. Frame contents are available during validation through EIP-8141’s introspection instructions (FRAMEPARAM, FRAMEDATALOAD, FRAMEDATACOPY) and MUST be read that way rather than accepted as account-supplied inputs (see “EIP-8141 Frame Validator”). Validators SHOULD grant the narrowest sufficient approval mode rather than APPROVE_EXECUTION_AND_PAYMENT by default.
The contract-dispatched route narrows this: when every SENDER frame resolves to the account, execute remains the gate on what actually happens. It is worth only as much as the check that establishes it — one unchecked frame pointed elsewhere still executes as tx.sender, so an account that declares contract dispatch without enforcing the check has granted the unconditional authorization above while believing it has not.
DEFAULT frames and the ENTRY_POINT caller
An EIP-8141DEFAULT frame executes its target with caller = ENTRY_POINT and requires no prior approval, so any frame transaction can invoke the account’s code with that caller. EIP-8141 gates APPROVE on the executing frame’s resolved target and flags, not on its mode; only a VERIFY frame, however, runs under STATICCALL restrictions and invalidates the whole transaction when it reverts. An account MUST NOT take msg.sender == ENTRY_POINT as evidence that it is executing in a VERIFY frame: it MUST check the frame’s mode via FRAMEPARAM before approving, as required of verify (see “Account Interface”). Without that check, a DEFAULT frame targeting the account runs the validation flow without STATICCALL protection, and a failed validation reverts only that frame while the rest of the transaction proceeds.
The same caution applies to execution entrypoints: a DEFAULT frame can call the account’s execute with caller = ENTRY_POINT in a transaction the account never validated, so execute MUST NOT be authorized by caller identity alone (see Execution).
Validator trust
An installed validator is fully trusted with respect to the account: because approval is unconditional, a malicious or buggy validator that approves execution can drain or take over the account. Accounts MUST apply the same authorization control to installing a validator as to any other account-critical operation.