Secure Smart Contract library providing battle-tested implementations of industry-standard Solidity contracts including ERC20, ERC721, ERC1155 tokens, access control mechanisms, proxy patterns, and governance systems for Ethereum and EVM-compatible blockchains.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
OpenZeppelin Contracts provides ERC-2771 meta-transaction support enabling gasless transactions and improved user experience in decentralized applications by allowing third parties to pay gas fees on behalf of users.
Import meta-transaction contracts using Solidity import statements:
import "@openzeppelin/contracts/metatx/ERC2771Context.sol";
import "@openzeppelin/contracts/metatx/MinimalForwarder.sol";Context variant that supports meta-transactions by extracting the actual sender from the call data when transactions are forwarded through trusted forwarder contracts.
abstract contract ERC2771Context is Context {
constructor(address trustedForwarder);
function isTrustedForwarder(address forwarder) public view virtual returns (bool);
function _msgSender() internal view virtual override returns (address sender);
function _msgData() internal view virtual override returns (bytes calldata);
}pragma solidity ^0.8.0;
import "@openzeppelin/contracts/metatx/ERC2771Context.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MetaToken is ERC20, ERC2771Context {
constructor(
string memory name,
string memory symbol,
address trustedForwarder
) ERC20(name, symbol) ERC2771Context(trustedForwarder) {
_mint(msg.sender, 1000000 * 10**18);
}
function transfer(address to, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender(); // Gets actual sender, even in meta-tx
_transfer(owner, to, amount);
return true;
}
function _msgSender() internal view virtual override(Context, ERC2771Context) returns (address) {
return ERC2771Context._msgSender();
}
function _msgData() internal view virtual override(Context, ERC2771Context) returns (bytes calldata) {
return ERC2771Context._msgData();
}
}Simple implementation of a meta-transaction forwarder that verifies signatures and executes calls on behalf of users.
contract MinimalForwarder is EIP712 {
struct ForwardRequest {
address from;
address to;
uint256 value;
uint256 gas;
uint256 nonce;
bytes data;
}
function getNonce(address from) public view returns (uint256);
function verify(ForwardRequest calldata req, bytes calldata signature) public view returns (bool);
function execute(ForwardRequest calldata req, bytes calldata signature) public payable returns (bool, bytes memory);
}event ExecutedForwardRequest(address indexed from, uint256 nonce, bool success);pragma solidity ^0.8.0;
import "@openzeppelin/contracts/metatx/MinimalForwarder.sol";
// Deploy the forwarder
contract MetaTxSetup {
MinimalForwarder public forwarder;
MetaToken public token;
constructor() {
forwarder = new MinimalForwarder();
token = new MetaToken("MetaToken", "META", address(forwarder));
}
function executeMetaTransaction(
MinimalForwarder.ForwardRequest calldata req,
bytes calldata signature
) external {
require(forwarder.verify(req, signature), "Invalid signature");
forwarder.execute(req, signature);
}
}// Client-side code for creating meta-transactions
const ethers = require('ethers');
class MetaTransactionClient {
constructor(forwarderAddress, forwarderAbi, signer) {
this.forwarder = new ethers.Contract(forwarderAddress, forwarderAbi, signer);
this.signer = signer;
}
async createMetaTransaction(to, data, value = 0, gas = 100000) {
const from = await this.signer.getAddress();
const nonce = await this.forwarder.getNonce(from);
const request = {
from,
to,
value,
gas,
nonce,
data
};
// Create EIP-712 typed data
const domain = {
name: 'MinimalForwarder',
version: '0.0.1',
chainId: await this.signer.getChainId(),
verifyingContract: this.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' }
]
};
// Sign the meta-transaction
const signature = await this.signer._signTypedData(domain, types, request);
return { request, signature };
}
async executeMetaTransaction(request, signature) {
return await this.forwarder.execute(request, signature);
}
}
// Usage
async function sendMetaTransaction() {
const client = new MetaTransactionClient(forwarderAddress, forwarderAbi, userSigner);
// Create a token transfer call
const tokenInterface = new ethers.utils.Interface(['function transfer(address,uint256)']);
const data = tokenInterface.encodeFunctionData('transfer', [recipient, amount]);
const { request, signature } = await client.createMetaTransaction(
tokenAddress,
data
);
// Send to relayer or execute directly
await client.executeMetaTransaction(request, signature);
}pragma solidity ^0.8.0;
import "@openzeppelin/contracts/metatx/MinimalForwarder.sol";
contract BatchForwarder is MinimalForwarder {
struct BatchRequest {
ForwardRequest[] requests;
uint256 deadline;
}
function executeBatch(
BatchRequest calldata batchReq,
bytes[] calldata signatures
) external returns (bool[] memory successes, bytes[] memory results) {
require(block.timestamp <= batchReq.deadline, "Batch expired");
require(batchReq.requests.length == signatures.length, "Length mismatch");
successes = new bool[](batchReq.requests.length);
results = new bytes[](batchReq.requests.length);
for (uint256 i = 0; i < batchReq.requests.length; i++) {
require(verify(batchReq.requests[i], signatures[i]), "Invalid signature");
(successes[i], results[i]) = execute(batchReq.requests[i], signatures[i]);
}
}
}pragma solidity ^0.8.0;
import "@openzeppelin/contracts/metatx/ERC2771Context.sol";
contract ConditionalMetaTx is ERC2771Context {
mapping(address => uint256) public balances;
mapping(bytes32 => bool) public executedConditions;
struct ConditionalTransfer {
address from;
address to;
uint256 amount;
uint256 minBalance;
uint256 deadline;
bytes32 conditionHash;
}
constructor(address trustedForwarder) ERC2771Context(trustedForwarder) {}
function executeConditionalTransfer(
ConditionalTransfer calldata transfer
) external {
require(block.timestamp <= transfer.deadline, "Transfer expired");
require(!executedConditions[transfer.conditionHash], "Already executed");
require(balances[transfer.from] >= transfer.minBalance, "Condition not met");
require(_msgSender() == transfer.from, "Unauthorized");
executedConditions[transfer.conditionHash] = true;
balances[transfer.from] -= transfer.amount;
balances[transfer.to] += transfer.amount;
}
}pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/metatx/ERC2771Context.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract GaslessNFT is ERC721, ERC2771Context {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
mapping(address => bool) public hasMinted;
constructor(address trustedForwarder)
ERC721("GaslessNFT", "GNFT")
ERC2771Context(trustedForwarder)
{}
function mint() external {
address user = _msgSender();
require(!hasMinted[user], "Already minted");
_tokenIds.increment();
uint256 tokenId = _tokenIds.current();
hasMinted[user] = true;
_safeMint(user, tokenId);
}
function _msgSender() internal view virtual override(Context, ERC2771Context) returns (address) {
return ERC2771Context._msgSender();
}
function _msgData() internal view virtual override(Context, ERC2771Context) returns (bytes calldata) {
return ERC2771Context._msgData();
}
}// Before: Regular contract
contract RegularContract {
function doSomething() external {
// msg.sender is the actual caller
require(msg.sender == owner, "Not owner");
}
}
// After: Meta-transaction enabled contract
contract MetaEnabledContract is ERC2771Context {
constructor(address trustedForwarder) ERC2771Context(trustedForwarder) {}
function doSomething() external {
// _msgSender() works for both regular and meta-transactions
require(_msgSender() == owner, "Not owner");
}
}Meta-transactions typically require relayer services that:
Meta-transaction contracts may revert with various errors:
Install with Tessl CLI
npx tessl i tessl/npm-openzeppelin-solidity