Protocol

Policies and session keys

The use case is simple: give a particular key small, bounded access to an app or action without handing over full account authority. Session keys are not a special transaction type: they are actors with the POLICY scope bit, a commitment, and exactly one allowed target. The protocol stays dumb; PolicyManager contracts stay expressive.

What a policy actor is

ConceptMeaning
POLICY bitGates the actor to its policy_manager. Not operational; cannot ERC-1271-sign raw hashes.
policy_commitmentkeccak256(params): signed and opaque. The protocol never interprets spend limits or allowlists.
policy_managerThe only contract the actor may call. Anything else → ActorPolicyViolation.
Not adminNever scope 0x00. Optional SELF_PAYER / NONCE. Optional actor expiry.

Why opaque commitments?

  • No protocol upgrades for app policy: games, payroll, trading limits evolve in contracts, not in the EIP.
  • Mempool stays simple: nodes do not need to understand ERC-20 selectors or weekly budgets to admit a tx; they check actor_config + later execution gates.
  • Stable invalidation: changing a policy means changing commitment / manager slots (or revoke), which is an explicit actor_config invalidator, not hidden logic inside wallet bytecode.

Why a single policy_manager target?

The account-level gate is one comparison: call.to == policy_manager. That is cheap, auditable, and forces session traffic through a contract that can enforce the commitment (spend, selectors, recipients).

Alternatives considered and rejected for the protocol layer:

  • Inline allowlists in actor_config: unbounded state, constant EIP churn, every node must understand every policy schema.
  • Wallet-only enforcement: returns to “simulate the account” for safety; apps can lie; mempool cannot help.
  • Multi-target protocol allowlists: more SLOADs and schema in the system contract; little gain vs one manager that dispatches.

Why never admin?

A session key that can rewrite actors (or lock the account) is not a session key; it is an owner. Admin is exactly scope == 0x00. Policy actors are never admin, so privilege separation holds without trusting the PolicyManager to “please not call config.”

Subscriptions and recurring pulls

Recurring payments are the canonical policy actor. Authorize a merchant's (or backend's) key with the POLICY scope, a spend commitment that encodes the cadence (for example 4.99 USDC per 30 days), and the single policy_manager target. The manager enforces the per-period limit at execution: an early or over-sized pull reverts, so the subscriber never grants more than the committed budget.

The important property is that there is nothing to coordinate. Grant the actor the NONCE scope and let it charge on a nonce-free (expiring) channel (or its own sequenced channel). The merchant does not read, reserve, or serialize a shared account nonce; when a charge is due it just fires the transaction. Multiple subscriptions on the same account run in parallel without contending for a single sequential counter, and a nonce-free charge is naturally idempotent, and safe to retry, within its expiry window.

  • No pre-auth dance: the budget lives in the committed policy, not in a per-charge approval.
  • Cancel is one change: revoke the actor or rotate the commitment. That is an explicit actor_config invalidator, which also drops any in-flight charge for that actor.
  • Gas stays separate: the subscription key signs sender_auth; a sponsor or ERC-8168 payer co-signs payer_auth, so the pull never touches account ETH.

Paying for gas with a session key

A policy actor can originate transactions, but gas still has to come from somewhere. There are four common paths:

ConceptMeaning
SponsorDefault for session keys: the account owner (or a payer service) co-signs payer_auth under a separate payer with SPONSOR_PAYER scope. The session key only signs sender_auth; it never touches the account’s ETH balance.
SELF_PAYERAuthorize the session key with POLICY | SELF_PAYER. It can then self-pay from the account’s ETH balance. The key still cannot move ETH except through gas, but a compromised or griefing key can burn the full balance on fees, so wallets should treat this as a deliberate opt-in.
Payer service (ERC-8168)A payer web service accepts off-chain payment (or a spend-limit budget) and co-signs payer_auth onchain. The session key stays POLICY-only; repayment or budget accounting lives in the service and/or a phase-0 settlement call.
Builder-native gasBuilders or sequencers MAY include transactions where gas is covered natively, e.g. an empty payer_auth with a recognized sentinel payer address. Chain- and builder-specific; not part of core 8130 validation.

Most session keys ship as POLICY only and rely on a sponsor. Full gas-abstraction paths (including dedicated gas keys and token repay) are in Gas & payers. See also Scopes, payers & phases · payer modes for how sender and payer auth compose.

Validation vs execution split

Other AA proposals run authentication, unstandardized authorization lookups, and an optional complex policy check all inside unpaid validation. EIP-8130 keeps validation to cryptographic authentication and known account-config authorization; the complex policy check is optional and happens at execution, where an actor gated by the policy system can only reach its policy target.
Other AA proposals push policy checks into unpaid validation. 8130 keeps validation to authentication plus known account-config authorization, and defers the policy gate to execution.

At mempool / auth time

Authorize actor, check restricted scope and expiry, confirm authenticator binding. Do not evaluate spend remaining.

At execution time

Account forces target = manager; manager checks commitment and executes (e.g. executeCall). Over-limit or wrong selector reverts inside the manager: user-visible failure, not a consensus surprise.