EIP.tools

EIP.tools

⚠️ DraftStandards Track: ERC

ERC-8103: Permissioned Authorization Object (PPO)

A signed, portable, revocable EIP-712 authorization struct enabling bounded pull-based token transfers.

Authors
Created2025-12-11
Discussion Linkhttps://ethereum-magicians.org/t/erc-8103-permissioned-authorization-object/27135
Requires
Referenced by

Markdown

https://raw.githubusercontent.com/ethereum/ERCs/re...
Pull Request#1401PR open

EIP-GPT summary

Contents
AbstractMotivationSpecificationEIP-712 Type DefinitionTYPEHASH (normative)Struct Hash (normative)Revocation ModelNonce SemanticsDomain BindingExecutor Verification RequirementsRationaleBackwards CompatibilitySecurity ConsiderationsCopyright

Abstract

This EIP defines a Permissioned Authorization Object (PPO): an EIP-712 typed struct describing a bounded, revocable permission to pull tokens from a grantor.

A PPO is:

  • signed off-chain by the grantor,
  • specifies the grantee, token, per-call cap, time window, and nonce,
  • portable across chains and executors that adopt the same type,
  • revocable prior to execution by invalidating the nonce.

It does not prescribe how transfers are executed; that is defined by a separate EIP ("Permissioned Pull", ERC-8102).

Motivation

Today, ERC-20 and related standards lack a canonical primitive for fine-grained pull permissions.

We have:

  • transfer — one-shot push
  • approve/transferFrom — persistent allowance, but:
    • often infinite,
    • not time-bounded,
    • not natively portable across chains,
    • approvals live in token contract state (harder to audit/rotate)
  • permit (EIP-2612) — signed approvals, but tightly coupled to token contracts

What is missing is a chain-agnostic, revocable authorization object that:

  • encodes bounded consent in a single signed payload,
  • can be verified by any compliant executor contract,
  • is easy for wallets and AA stacks to reason about,
  • can be revoked before use,
  • composes with any ERC-20 token without requiring token changes.

This EIP specifies such an authorization struct and its hashing rules, so that:

  • executors (ERC-8102) can implement a common verification model,
  • registries and wallets can expose and revoke PPOs consistently,
  • systems can build higher-level coordination (subscriptions, automation) on a shared authorization primitive.

Specification

EIP-712 Type Definition

The canonical type for a Permissioned Authorization Object is:

Authorization(
  address grantor,
  address grantee,
  address token,
  uint256 maxPerPull,
  uint256 validAfter,
  uint256 validBefore,
  bytes32 nonce
)

Where:

  • grantor: address providing consent (token owner).
  • grantee: address allowed to initiate pulls under this authorization.
  • token: ERC-20 token address.
  • maxPerPull: maximum number of token units that may be pulled per execution.
  • validAfter: unix timestamp (seconds) from which this authorization becomes valid (inclusive).
  • validBefore: unix timestamp (seconds) after which this authorization is no longer valid (exclusive).
  • nonce: unique value chosen by the grantor for replay protection.

TYPEHASH (normative)

The canonical AUTH_TYPEHASH MUST be:

bytes32 public constant AUTH_TYPEHASH = keccak256(
  "Authorization(address grantor,address grantee,address token,uint256 maxPerPull,uint256 validAfter,uint256 validBefore,bytes32 nonce)"
);

Struct Hash (normative)

Given:

struct Authorization {
    address grantor;
    address grantee;
    address token;
    uint256 maxPerPull;
    uint256 validAfter;
    uint256 validBefore;
    bytes32 nonce;
}

The struct hash MUST be computed as:

function _authStructHash(Authorization memory a) internal pure returns (bytes32) {
    return keccak256(
        abi.encode(
            AUTH_TYPEHASH,
            a.grantor,
            a.grantee,
            a.token,
            a.maxPerPull,
            a.validAfter,
            a.validBefore,
            a.nonce
        )
    );
}

The EIP-712 digest is then:

bytes32 digest = keccak256(
    abi.encodePacked(
        "\x19\x01",
        domainSeparator,
        _authStructHash(auth)
    )
);

where domainSeparator is as defined in EIP-712 by the verifying contract (typically the executor contract specified in ERC-8102).

Revocation Model

This EIP defines requirements on revocation semantics but does not prescribe storage layout. Implementations MAY use:

  • local per-executor storage, and/or
  • a shared registry contract, and/or
  • wallet-maintained revocation maps.

Normative requirements:

  1. Grantors MUST be able to revoke any unused nonce prior to execution.

  2. Executors MUST check revocation status at execution time according to the mechanism adopted by the executor (e.g., local cancellation and/or shared registry).

  3. Executors MUST reject revoked or canceled nonces.

Recommended events (names are illustrative):

event AuthorizationGranted(
    address indexed grantor,
    address indexed grantee,
    address indexed token,
    bytes32 nonce
);

event AuthorizationRevoked(
    address indexed grantor,
    bytes32 indexed nonce
);

Wallets and registries MAY emit these upon signing or revoking PPOs.

Nonce Semantics

  • nonce MUST be treated as single-use in the context of a given grantor and domain separator.
  • Implementations SHOULD treat (grantor, nonce) as a unique pair.
  • Nonce consumption MUST be race-safe: an executor MUST mark the nonce as used before or atomically with any external calls (e.g., token transfers).

Domain Binding

This EIP intentionally separates:

  • the authorization object (defined herein), and
  • the execution domain (ERC-8102, pull executor).

Domain binding is achieved in ERC-8102 by including the executor’s domainSeparator() in the EIP-712 digest.

This prevents signatures from being replayed across executors with different domains.

Executor Verification Requirements

Executors verifying PPOs MUST:

  • enforce validity windows (validAfter, validBefore) before execution,
  • enforce EIP-2 signature constraints (low-s, valid v),
  • bind verification to their own domain separator.

Rationale

  • Separation of concerns: This proposal defines “what is consent?” while ERC-8102 defines “how is consent executed?”. This keeps wallets, AA stacks, and registries decoupled from any specific executor implementation.
  • Stateless across chains: maxPerPull and time windows keep each execution stateless; cumulative limits can be built on top, but are not required at the primitive level.
  • Nonce as bytes32: Allows flexible schemes (per-grantee, per-token, per-policy) while remaining compatible with simple counter-based patterns.

Backwards Compatibility

  • Compatible with any ERC-20 token; no token changes required.
  • Orthogonal to EIP-2612; PPOs are a different authorization path (pull-by-consent vs “permit as approve”).

Security Considerations

The rules required for safe execution are specified normatively in the Specification section. This section discusses implications:

  • Revocation must be checked at execution time to avoid race conditions between revoke and pull.
  • Wallet UX should make nonce scope and domain binding clear to users.
  • Grantors should protect signing keys; leaked signatures remain valid until expiry or revocation.

Copyright and related rights waived via CC0.

EIP.tools

EIP.tools

Search, read, and map Ethereum improvement proposals, ERCs, RIPs, and CAIPs from one focused interface.

Farcaster
by @apoorveth