Skip to main content

EIP-7702: EOA Code Delegation

EIP-7702 lets an externally owned account (EOA) temporarily delegate its execution to a smart contract. When someone calls the EOA, the EVM runs the delegate contract's code in the context of the EOA — same address, same balance, same storage. This gives EOAs smart account capabilities (batch calls, spending limits, social recovery) without deploying a separate contract.

The Simple Way
var authService = web3.Eth.GetEIP7022AuthorisationService();

// Delegate your EOA to a smart contract — gas overhead calculated automatically
var receipt = await authService
.AuthoriseRequestAndWaitForReceiptAsync("0xDelegateContract");

// Check if an address has a smart account (is delegated)
bool isDelegated = await authService.IsDelegatedAccountAsync(address);

// Get the delegate contract address
string delegateContract = await authService.GetDelegatedAccountAddressAsync(address);

// Revoke delegation — removes smart account from EOA
var receipt = await authService.RemoveAuthorisationRequestAndWaitForReceiptAsync();

For sponsored delegation (someone else pays gas), use EIP7022SponsorAuthorisationService.

The delegation is set by sending a Type 4 transaction that includes a signed authorization tuple. The EOA owner signs "I authorize contract X to act as my code" and the delegation takes effect when the transaction is mined.

When to Use EIP-7702

ScenarioWhy EIP-7702
Batch multiple calls in one transactionEOA delegates to a batch-call contract
Add spending limits or session keysEOA delegates to a smart account with policy enforcement
Sponsor gas for another userSponsor signs the Type 4 tx, sponsored user signs just the authorization
Upgrade EOA to smart accountNo new address — existing EOA gains contract capabilities
Account Abstraction without new addressERC-4337 UserOps work with the EOA's existing address

Prerequisites

dotnet add package Nethereum.Web3
dotnet add package Nethereum.Accounts

For low-level signing only (no RPC):

dotnet add package Nethereum.Model
dotnet add package Nethereum.Signer

Quick Start: Self-Delegation

The simplest workflow — delegate your own EOA to a contract using the high-level service:

using Nethereum.Web3;
using Nethereum.Web3.Accounts;

var account = new Account("0xYOUR_PRIVATE_KEY", Chain.MainNet);
var web3 = new Web3(account, "https://mainnet.infura.io/v3/YOUR_KEY");

var authorisationService = web3.Eth.GetEIP7022AuthorisationService();

// Delegate your EOA to a smart contract
var receipt = await authorisationService
.AuthoriseRequestAndWaitForReceiptAsync("0xDelegateContractAddress");

This sends a Type 4 transaction to yourself with the signed authorization. After mining, calling your EOA address executes the delegate contract's code.

Check Delegation State

// Is this address delegated?
bool isDelegated = await authorisationService.IsDelegatedAccountAsync(account.Address);

// Get the delegate contract address
string delegateAddress = await authorisationService
.GetDelegatedAccountAddressAsync(account.Address);

Under the hood, IsDelegatedAccountAsync checks if the account's code starts with 0xef0100 (the delegation prefix). The delegate address is the 20 bytes following the prefix.

Remove Delegation

var receipt = await authorisationService
.RemoveAuthorisationRequestAndWaitForReceiptAsync();

// Verify it's gone
var delegateAddress = await authorisationService
.GetDelegatedAccountAddressAsync(account.Address);
// delegateAddress is null

Universal vs Chain-Specific Authorization

When signing an authorization, you choose the chainId:

  • Chain-specific (default): The authorization only works on the chain matching the chainId. Safer — prevents replay on other chains.
  • Universal (chainId = 0): The authorization is valid on any chain. Useful for cross-chain delegation setups.
// Chain-specific (default — uses the account's chain ID)
await authorisationService.AuthoriseRequestAndWaitForReceiptAsync(
"0xDelegateContract", useUniversalZeroChainId: false);

// Universal (chainId = 0 — valid on any chain)
await authorisationService.AuthoriseRequestAndWaitForReceiptAsync(
"0xDelegateContract", useUniversalZeroChainId: true);

A sponsor (who has ETH for gas) can delegate another account's EOA on their behalf. The sponsored account signs only the authorization — they don't need ETH for gas.

using Nethereum.Accounts;
using Nethereum.Signer;

// Sponsor's web3 (has ETH, pays gas)
var sponsor = new Account("0xSPONSOR_KEY", Chain.MainNet);
var web3 = new Web3(sponsor, "https://mainnet.infura.io/v3/YOUR_KEY");

// Sponsored account's key (signs the authorization, doesn't need ETH)
var sponsoredKey = new EthECKey("0xSPONSORED_PRIVATE_KEY");

var sponsorService = new EIP7022SponsorAuthorisationService(
web3.TransactionManager, web3.Eth);

// Sponsor sends the Type 4 tx with the sponsored account's signed authorization
var receipt = await sponsorService
.AuthoriseSponsoredRequestAndWaitForReceiptAsync(
sponsoredKey,
"0xDelegateContractAddress",
useUniversalZeroChainId: true,
brandNewAccount: true); // skip nonce lookup for fresh accounts

Batch Sponsorship

Delegate multiple accounts in a single transaction:

var keys = new EthECKey[]
{
new EthECKey("0xKEY_1"),
new EthECKey("0xKEY_2"),
new EthECKey("0xKEY_3"),
};

var receipt = await sponsorService
.AuthoriseBatchSponsoredRequestAndWaitForReceiptAsync(
keys,
"0xDelegateContractAddress",
useUniversalZeroChainId: true,
brandNewAccount: true);

Each account in the batch gets its own authorization in the same Type 4 transaction.


Low-Level: Manual Authorization Signing

For full control over the authorization and transaction construction:

using Nethereum.Model;
using Nethereum.Signer;
using System.Collections.Generic;
using System.Numerics;

// 1. Create the authorization tuple
var authorisation = new Authorisation7702
{
ChainId = 1, // or 0 for universal
Address = "0xDelegateContractAddress", // contract to delegate to
Nonce = 0 // must match the EOA's current nonce
};

// 2. Sign the authorization with the EOA's key
var ecKey = new EthECKey("0xYOUR_PRIVATE_KEY");
var authSigner = new Authorisation7702Signer();
var signedAuth = authSigner.SignAuthorisation(ecKey, authorisation);

// 3. Build the Type 4 transaction
var tx = new Transaction7702(
chainId: 1,
nonce: 0,
maxPriorityFeePerGas: BigInteger.Parse("2000000000"), // 2 Gwei
maxFeePerGas: BigInteger.Parse("100000000000"), // 100 Gwei
gasLimit: 100000,
receiverAddress: ecKey.GetPublicAddress(), // send to self
amount: BigInteger.Zero,
data: null,
accessList: new List<AccessListItem>(),
authorisationList: new List<Authorisation7702Signed> { signedAuth });

// 4. Sign and broadcast
var txSigner = new Transaction7702Signer();
var signedTxHex = txSigner.SignTransaction(ecKey, tx);
await web3.Eth.Transactions.SendRawTransaction
.SendRequestAsync("0x" + signedTxHex);

Recover Signer from Authorization

using Nethereum.Signer;

var signerAddress = EthECKeyBuilderFromSignedAuthorisation
.RecoverSignerAddress(signedAuth);

Inline Authorization (with Contract Calls)

Instead of sending a separate delegation transaction, you can attach the authorization to the same transaction that calls the delegate contract. This delegates and executes in one step:

using Nethereum.RPC.Eth.DTOs;

// The function you want to call on the delegate contract
var executeFunction = new ExecuteFunction
{
Calls = batchCalls,
Gas = 1000000, // must set gas manually — estimates don't work with inline auth
AuthorisationList = new List<Authorisation>
{
new Authorisation { Address = "0xDelegateContractAddress" }
}
};

// The transaction manager signs the authorization automatically
var receipt = await contractService.ExecuteRequestAndWaitForReceiptAsync(executeFunction);

The unsigned Authorisation (with just the Address field) is signed automatically by the AccountSignerTransactionManager before sending.


Gas Costs

EIP-7702 adds extra gas on top of the base transaction cost:

CostAmountWhen
Base transaction21,000Every transaction
Per authorization12,500Each authorization tuple
Per account (new or existing)25,000Each account being delegated
using Nethereum.RPC.TransactionManagers;

// Calculate gas for authorization delegations
int extraGas = AuthorisationGasCalculator
.CalculateGasForAuthorisationDelegation(
numberOfNew: 3,
numberOfExisting: 1);
// = 4 * 25000 = 100,000 additional gas

The transaction manager adds authorization gas automatically when using the high-level services.


How Delegation Works On-Chain

When a Type 4 transaction is processed:

  1. Each authorization tuple is validated (correct nonce, matching chainId)
  2. The EOA's code is set to 0xef0100 + delegate address (23 bytes total)
  3. The EOA's nonce is incremented
  4. Any calls to the EOA now execute the delegate contract's code

The 0xef prefix is a banned opcode (EIP-3541), so this bytecode can never be deployed normally — it can only be set via EIP-7702 authorization.

Delegation Persists Across Transactions

Once set, the delegation stays until explicitly removed (by authorizing the zero address). It survives transaction failures — even if the Type 4 transaction reverts, the delegation still takes effect.

No Delegation Chaining

If account A delegates to account B, and account B also has a delegation, calling A executes B's code directly. The EVM does not follow B's delegation — only the first level is resolved.


Hardware Wallet & Cloud KMS Support

All external signers support Type 4 transactions:

// Ledger
var signer = new LedgerExternalSigner(ledgerManager);
await signer.SignAsync(transaction7702);

// AWS KMS
var signer = new AWSKeyManagementExternalSigner(kmsClient, keyId);
await signer.SignAsync(transaction7702);

The same IEthExternalSigner.SignAsync(Transaction7702) interface works for Ledger, Trezor, AWS KMS, and Azure Key Vault.


Account Abstraction Integration

EIP-7702 integrates with ERC-4337 account abstraction. An EOA delegates to a smart account contract, then sends UserOperations through the EntryPoint — all using the EOA's original address.

The Nethereum.AccountAbstraction package detects EIP-7702 UserOps by checking for a 0x7702 marker in the initCode field.

See the Account Abstraction section for full details on combining EIP-7702 with ERC-4337.


Next Steps

Package References