ERC-2771
ERC-2771¶
Overview¶
ERC-2771 (Secure Protocol for Native Meta Transactions) is Ethereum's meta-transaction security protocol standard, defining a contract-level protocol that enables recipient contracts to securely accept meta transactions through a trusted Forwarder contract.
The core idea of meta transactions is to have third parties pay Gas fees on behalf of users, thereby improving the user experience. ERC-2771 standardizes this process, ensuring security and interoperability.
What Are Meta Transactions¶
Pain Points of Traditional Transactions In traditional Ethereum transactions: - Users must hold ETH to pay Gas fees - New users need to purchase ETH before they can use a DApp - Each operation requires the user to manually confirm and pay - This creates a high barrier for Web2 users
The Meta Transaction Solution Meta transactions allow: - Users to sign their transaction intent (off-chain) - A third party (Gas Relay) to submit and pay Gas on behalf of the user - Users to use DApps without holding ETH - A user experience similar to Web2
Roles in ERC-2771¶
Transaction Signer - The user who actually wants to perform the operation - Signs the transaction intent - Does not need to hold ETH
Gas Relay - Receives the user's signed message - Submits the transaction to the blockchain on behalf of the user - Pays the Gas fees - Can be compensated through other means
Forwarder Contract - Verifies the validity of the user's signature - Forwards the user's request to the target contract - Ensures it cannot be tampered with or forged - Trusted by the recipient contract
Recipient Contract - The contract that actually executes the business logic - Trusts a specific Forwarder contract - Obtains the real user address via _msgSender() - Obtains the real transaction data via _msgData()
How It Works¶
Meta Transaction Flow
-
User Signs
-
Submit to Relay
-
Relay Validates and Forwards
-
Execute Business Logic
Core Interfaces¶
Forwarder Contract
interface IForwarder {
struct ForwardRequest {
address from; // Transaction signer
address to; // Target contract
uint256 value; // ETH amount
uint256 gas; // Gas limit
uint256 nonce; // Anti-replay
bytes data; // Call data
}
function execute(
ForwardRequest calldata req,
bytes calldata signature
) external payable returns (bool, bytes memory);
}
Recipient Contract
abstract contract ERC2771Context {
address private immutable _trustedForwarder;
constructor(address trustedForwarder) {
_trustedForwarder = trustedForwarder;
}
// Get the real message sender
function _msgSender() internal view virtual override returns (address sender) {
if (isTrustedForwarder(msg.sender)) {
// Extract the real sender from the last 20 bytes of calldata
assembly {
sender := shr(96, calldataload(sub(calldatasize(), 20)))
}
} else {
sender = msg.sender;
}
}
// Get the real call data
function _msgData() internal view virtual override returns (bytes calldata) {
if (isTrustedForwarder(msg.sender)) {
// Remove the last 20 bytes (sender address)
return msg.data[:msg.data.length - 20];
} else {
return msg.data;
}
}
function isTrustedForwarder(address forwarder) public view returns (bool) {
return forwarder == _trustedForwarder;
}
}
Security Considerations¶
Forwarder Trust Issue A malicious forwarder could: - Forge the address returned by _msgSender() - Make transactions appear to come from any address - Fully control the recipient contract
Defense Measures: - Recipient contracts only trust specific forwarders - Forwarder addresses are typically set as immutable at deployment time - Permissions to modify the trusted forwarder must be strictly limited - It is recommended that the forwarder list be immutable, or only modifiable by the contract owner
Replay Attacks The forwarder must implement a nonce mechanism: - Maintain an incrementing nonce for each user - Ensure each signature can only be used once - Prevent relays from resubmitting the same request
Signature Verification The forwarder must correctly verify signatures: - Use EIP-712 structured signing - Verify the signer address - Check the validity of the request
Practical Application Examples¶
Deploying a Forwarder
// Using OpenZeppelin's implementation
import "@openzeppelin/contracts/metatx/MinimalForwarder.sol";
contract MyForwarder is MinimalForwarder {}
Creating a Recipient Contract
import "@openzeppelin/contracts/metatx/ERC2771Context.sol";
contract MyContract is ERC2771Context {
constructor(address trustedForwarder)
ERC2771Context(trustedForwarder)
{}
function doSomething() public {
// _msgSender() returns the real user address
address user = _msgSender();
// Execute business logic
// ...
}
}
Frontend Initiating a Meta Transaction
// 1. Construct forward request
const forwardRequest = {
from: userAddress,
to: contractAddress,
value: 0,
gas: 100000,
nonce: await forwarder.getNonce(userAddress),
data: contract.interface.encodeFunctionData('doSomething', [])
}
// 2. User signs (EIP-712)
const domain = {
name: 'MinimalForwarder',
version: '0.0.1',
chainId: await provider.getNetwork().chainId,
verifyingContract: forwarder.address
}
const types = {
ForwardRequest: [
{ name: 'from', type: 'address' },
{ name: 'to', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'gas', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'data', type: 'bytes' }
]
}
const signature = await signer._signTypedData(domain, types, forwardRequest)
// 3. Send to relay server
await fetch('https://relay.example.com/execute', {
method: 'POST',
body: JSON.stringify({ forwardRequest, signature })
})
// 4. Relay server executes
// relayServer.execute(forwardRequest, signature)
Use Cases¶
Web3 Gaming - Players can play without holding ETH - Game developers pay Gas fees - Lowers the barrier for new users - Provides a smooth gaming experience
*NFT* Marketplaces** - Users can mint NFTs without ETH - Platforms subsidize Gas fees - Improves user conversion rates - Simplifies the purchase process
*DeFi* Protocols** - New users do not need to purchase ETH first - Protocols deduct Gas from transaction fees - Increases protocol adoption - Improves user experience
Enterprise Applications - Employees operate using company wallets - Company pays Gas fees centrally - Simplifies financial management - Improves operational efficiency
Notable Implementations¶
*OpenZeppelin* Provides a standard ERC-2771 implementation: - ERC2771Context: Recipient contract base class - MinimalForwarder: Simple forwarder implementation
Biconomy Professional meta-transaction infrastructure: - Provides a forwarder network - Supports the ERC-2771 standard - Developer-friendly SDK
Gelato Automation and relay service: - Supports meta transactions - Provides Gas sponsorship service - On-chain automation execution
Relationship with ERC-4337¶
Limitations of ERC-2771 - Contracts must explicitly support it (inherit ERC2771Context) - Forwarder selection and trust management is complex - Only supports EOA accounts
Advantages of ERC-4337 Account Abstraction (AA) provides a more comprehensive solution: - Native smart contract wallet support - More flexible Gas payment methods - Unified entry point contract - More powerful features (social recovery, batch operations, etc.)
Transition Trend Many ERC-2771 solutions are migrating to ERC-4337: - ERC-4337 is more universal and powerful - Better ecosystem support - A better choice in the long run - But ERC-2771 is still suitable for simple scenarios
Best Practices¶
Forwarder Management - Set the forwarder address as immutable - If it must be mutable, restrict modification to the contract owner only - Use multi-sig to manage forwarder updates - Record forwarder change history
Signature Security - Use EIP-712 structured signing - Include a domain separator to prevent cross-chain replay - Implement a nonce mechanism - Set reasonable expiration times
Gas Management - Estimate reasonable Gas limits - Prevent Gas exhaustion attacks - Implement a Gas fee compensation mechanism - Monitor relay costs
Contract Design - Use _msgSender() instead of msg.sender in all contract functions - Use _msgData() instead of msg.data - Consider backward compatibility - Test both forwarded and direct call scenarios
Future Outlook¶
Although ERC-4337 is becoming mainstream, ERC-2771 still has its value: - Simpler implementation - Lower Gas cost - Suitable for specific scenarios - Can coexist with ERC-4337
Related Links¶
- ERC-2771 Official Specification
- OpenZeppelin ERC-2771 Implementation
- Biconomy Meta Transactions
- What Are Meta Transactions (Alchemy)