Skip to main content

Try it Live

Run Authorization examples in the interactive playground

Authorization

EIP-7702 authorization implementation enabling EOA code delegation.
New to EIP-7702 authorizations? Start with Fundamentals to learn delegation mechanics, security considerations, and complete examples.

Overview

Authorizations (EIP-7702) allow Externally Owned Accounts (EOAs) to temporarily delegate code execution to smart contracts, enabling account abstraction features without migrating to contract wallets. Key Features:
  • Account abstraction for EOAs
  • Sponsored transactions (gas abstraction)
  • Batch operations
  • Social recovery
  • Custom validation logic
  • Temporary, per-transaction delegation

Quick Start

import {
  sign,
  validate,
  getGasCost,
  verify
} from 'tevm/Authorization';

// Create unsigned authorization
const unsigned = {
  chainId: 1n,
  address: contractAddress,
  nonce: 0n
};

// Sign it
const privateKey = Bytes32();
const auth = sign.call(unsigned, privateKey);

// Validate
validate.call(auth);

// Calculate gas cost
const gas = getGasCost.call(auth, true);
console.log(`Gas required: ${gas}`);

// Verify signer
const authority = verify.call(auth);
console.log(`Signed by: ${authority}`);

Core Types

Authorization.Item

Complete signed authorization.
type Item = {
  chainId: bigint;      // Chain ID where valid
  address: Address;     // Contract to delegate to
  nonce: bigint;        // Account nonce
  yParity: number;      // Signature Y parity (0 or 1)
  r: bigint;            // Signature r value
  s: bigint;            // Signature s value
};

Authorization.Unsigned

Authorization before signing.
type Unsigned = {
  chainId: bigint;
  address: Address;
  nonce: bigint;
};

Authorization.DelegationDesignation

Result of processing authorization.
type DelegationDesignation = {
  authority: Address;         // Signer (EOA granting permission)
  delegatedAddress: Address;  // Contract receiving delegation
};

Set Code Delegation

EIP-7702 allows EOA to set its code to point to contract’s code:
EOA Account
├── Balance: Original EOA balance
├── Nonce: Original EOA nonce
└── Code: → Points to delegated contract code
Important:
  • Delegation is per-transaction - resets after transaction
  • Original EOA retains ownership and keys
  • Delegated contract executes in EOA’s context
  • EOA’s storage remains separate

Workflow

  1. Create Unsigned Authorization
    const unsigned = { chainId, address, nonce };
    
  2. Sign Authorization
    const auth = Authorization.sign.call(unsigned, privateKey);
    
  3. Include in Transaction
    • Authorization list included in EIP-7702 transaction
    • Each authorization processed at transaction start
  4. Execution
    • EOA code set to delegated contract
    • Transaction executes with delegated logic
    • Code delegation reverts after transaction

Visual Guides & Examples

Workflows & Diagrams

Comprehensive visual explanations:
  • EIP-7702 Account Delegation Flow - EOA → delegate → execute → revert
  • Authorization Lifecycle - Creation, signing, processing, execution
  • Sponsored Transaction Flow - User signs auth, relayer pays gas
  • Batch Operations - Multiple actions in single transaction
  • Social Recovery - Guardian-based account recovery
  • Gas Cost Breakdown - Per-authorization and transaction costs
  • Comparison Tables - Traditional vs EIP-7702 approaches

Real-World Examples

Production-ready implementations:
  • Relay Network Integration - Gas-sponsoring relay service
  • Batch Operation Contract - Multi-action execution
  • Social Recovery Implementation - Guardian-based recovery
  • Smart Account Using Delegation - EOA-as-smart-account pattern
  • Gasless Transaction Service - End-to-end gasless UX
  • Production Deployment Checklist - Validation & testing

API Methods

Type Guards

  • Check if value is Authorization.Item or Authorization.Unsigned

Validation

  • validate - Validate authorization structure and signature components

Signing & Hashing

  • hash - Calculate signing hash
  • sign - Create signed authorization
  • verify - Recover authority from signature

Gas Calculations

Processing

Utilities

  • format - Format authorization to string
  • equals - Compare authorization equality
  • Helper functions for authorization manipulation

WASM

Constants

EIP-7702 Constants

Authorization.MAGIC_BYTE = 0x05;                    // Signing hash prefix
Authorization.PER_EMPTY_ACCOUNT_COST = 25000n;      // Gas for empty account
Authorization.PER_AUTH_BASE_COST = 12500n;          // Base gas per authorization

Signature Constants

Authorization.SECP256K1_N = 0xfff...n;              // Curve order
Authorization.SECP256K1_HALF_N = SECP256K1_N >> 1n; // N/2 for malleability check

Use Cases

Allow relayer to pay gas for user transactions:
// User creates authorization delegating to sponsor contract
const unsigned = {
  chainId: 1n,
  address: sponsorContractAddress,
  nonce: await getAccountNonce(userEOA)
};

const auth = Authorization.sign.call(unsigned, userPrivateKey);

// Relayer includes in transaction
transaction.authorizationList = [auth];
transaction.gasPrice = relayerGasPrice;

Batch Operations

Execute multiple operations in single transaction:
// Delegate to batch executor contract
const auth = Authorization.sign.call({
  chainId: 1n,
  address: batchExecutorAddress,
  nonce: currentNonce
}, privateKey);

// Batch executor can:
// - Approve multiple tokens
// - Swap on multiple DEXes
// - Transfer to multiple recipients

Social Recovery

Implement social recovery for EOAs:
// Guardian creates authorization
const guardianAuth = {
  chainId: 1n,
  address: recoveryModuleAddress,
  nonce: guardianNonce
};

const auth = Authorization.sign.call(guardianAuth, guardianPrivateKey);

// Recovery module can:
// - Verify guardian consensus
// - Execute recovery operations
// - Transfer assets to new account

Best Practices

1. Always Validate

// Good: Validate before processing
try {
  Authorization.validate.call(auth);
  processAuthorization(auth);
} catch (e) {
  handleInvalidAuth(e);
}

2. Check Nonce Consistency

// Good: Verify nonce matches account state
const currentNonce = await getAccountNonce(authority);
if (auth.nonce !== currentNonce) {
  throw new Error('Nonce mismatch');
}

3. Estimate Gas Accurately

// Good: Check if accounts are empty
const isEmpty = await isAccountEmpty(auth.address);
const gas = Authorization.getGasCost.call(auth, isEmpty);

4. Handle Failures Gracefully

// Good: Process with error handling
const results = authList.map(auth => {
  try {
    return { auth, result: Authorization.process.call(auth) };
  } catch (e) {
    return { auth, error: e };
  }
});

5. Use Type Guards

// Good: Verify types before processing
function handleAuth(data: unknown) {
  if (Authorization.isItem(data)) {
    Authorization.validate.call(data);
    return processAuth(data);
  }
  throw new Error('Invalid authorization data');
}

6. Prevent Signature Malleability

// Validation ensures s <= N/2
// Prevents second valid signature for same authorization
Authorization.validate.call(auth);  // Throws if s > N/2

7. Chain-Specific Authorizations

// Good: Explicit chain ID
const auth = {
  chainId: 1n,  // Mainnet only
  address: contractAddress,
  nonce: 0n
};

// Bad: Reusing cross-chain
// Authorization signed for chain 1 invalid on chain 137

Security Considerations

1. Signature Verification

Always verify signatures before executing delegated code:
const authority = Authorization.verify.call(auth);
// Verify authority is expected/authorized

2. Nonce Tracking

Prevent replay attacks by tracking nonces:
const usedNonces = new Set<string>();
const key = `${authority}-${auth.nonce}`;

if (usedNonces.has(key)) {
  throw new Error('Nonce already used');
}
usedNonces.add(key);

3. Chain ID Validation

Prevent cross-chain replay:
if (auth.chainId !== expectedChainId) {
  throw new Error('Wrong chain ID');
}

4. Address Validation

Ensure delegated address is trusted:
const trustedContracts = new Set([...]);

if (!trustedContracts.has(auth.address)) {
  throw new Error('Untrusted delegation target');
}

5. Gas Limits

Set appropriate gas limits to prevent DoS:
const maxGas = Authorization.calculateGasCost.call(authList, maxEmpty);
if (requiredGas > maxGas) {
  throw new Error('Gas limit exceeded');
}

Tree-Shaking

Import only what you need for optimal bundle size:
// Import specific methods
import { sign, verify, validate } from 'tevm/primitives/Authorization'

// Or import entire namespace
import * as Authorization from 'tevm/primitives/Authorization'

// Selective imports reduce bundle size
const auth = sign.call(unsigned, privateKey)
const valid = validate.call(auth)
Each function is independently exported, allowing bundlers to eliminate unused code. This is especially beneficial when only using a subset of Authorization functionality (e.g., only validation without signing).

Performance

Operation Complexity

OperationTimeNotes
isItemO(1)Type checking
isUnsignedO(1)Type checking
validateO(1)Constant checks
hashO(1)RLP + keccak256
signO(1)secp256k1 signing
verifyO(1)Signature recovery
calculateGasCostO(n)n = list length
processAllO(n)n = list length
formatO(1)String formatting
equalsO(1)Field comparison

Optimization Tips

  1. Batch validations - validate all before processing
  2. Cache gas calculations - if list doesn’t change
  3. Pre-compute hashes - reuse signing hashes when possible
  4. Limit list size - large lists increase gas costs significantly

See Also

References