---
title: "什么是元交易（ERC-2771）？"
description: "了解元交易，包括 gas 费用以及为什么它不再是理想的解决方案。"
---

# 什么是元交易（ERC-2771）？

所有 Ethereum 交易都需要消耗 gas，且每笔交易的发送方都必须拥有足够的 Ether 来支付所消耗的 gas。这就要求新用户在使用 dapp 之前必须先购买 Ether（这可能是一件令人望而却步的事），成为用户上手过程中的一大障碍。

所谓“元交易”（Meta Transactions）使得代用户支付 gas 费用成为可能。

不过，许多此类方案正在[转向账户抽象（Account Abstraction，ERC-4337）基础设施](http://www.alchemy.com/overviews/4337-vs-2771)，因为它是一种更具前景、更成熟的 gas 抽象方式，功能也更加丰富。

## 什么是元交易？

元交易的思路很简单：由一个称为 Relayer 的第三方代替用户发送交易并支付 gas 费用。

用户对消息进行签名（最好符合 EIP-712 规范），消息中包含待执行交易的相关信息。

签名后的消息会被传递给 Relayer，由 Relayer 负责验证是否会获得报酬（这一步可以是可选的，后面会详细说明）、确认自身是否有足够资金支付 gas 费用、签署一笔原生交易并提交执行。

我们以 OpenZepplin 的 Relayer 为例来理解元交易：

<ImageBlock
  src="https://media.alchemy.com/1703861042-meta-transactions.gif"
  alt="元交易动态图:用户签署一条消息,中继器提交该消息并支付 gas"
  width={720}
  height={405}
  caption="元交易分步指南"
/>

## 元交易请求长什么样？

下面是一个元交易消息的示例……

<ImageBlock
  src="https://media.alchemy.com/1703861080-sample-meta-transaction.jpeg"
  alt="铸造 NFT 的元交易消息示例,展示 from 地址和 call data"
  width={1194}
  height={632}
  caption="元交易示例"
/>

上面的消息用于铸造一个 NFT，需要由地址 0x7099797... 进行签名。

消息签名完成后，客户端（或用户）就可以创建一个元交易请求（见下图）。该请求会以 POST 请求的形式发送给 Autotask——一个 webhook，被调用时会在内部使用 Relayer 的私钥对原生交易进行签名。

<ImageBlock
  src="https://media.alchemy.com/1703861109-meta-transaction-post-request.jpeg"
  alt="发送给 Autotask 中继器 webhook 的元交易 POST 请求负载"
  width={2264}
  height={726}
  caption="元交易 POST 请求"
/>

## 什么是元交易中的 Relayer？

Relayer 是一个 Ethereum 账户，用于持有为用户 gas 费用买单所需的资金。该账户的私钥存储在服务提供商服务器上的一个安全 vault 中。

开发者可以向 Relayer 转入资金，用于覆盖用户的交易费用。

开发者可以按需创建任意数量的 Relayer，但每个 Relayer 都需要单独用 ETH 充值。

OZ Relay 支持暂停 Relayer、仅接受白名单地址的请求，以及 gas 价格上限设置。如有需要，还可以在 Autotask 中编写更多条件逻辑。

需要注意的是，此时用户自定义的交易实际上存放在 Relayer 原生交易的 data 字段中。元交易本质上就是嵌套在另一笔交易内部的交易！

## 元交易中的 MinimalForwarder 是什么？

到目前为止，还没有进行任何链上验证。这正是 MinimalForwarder 发挥作用的地方。

MinimalForwarder 是一个链上智能合约，用于验证用户签名的消息，以确保其有效性并防止重放攻击。

<CodeSnippet code={`struct ForwardRequest {
    address from;
    address to;
    uint256 value;
    uint256 gas;
    uint256 nonce;
    bytes data;
}

function verify(ForwardRequest calldata req, bytes calldata signature) public view returns (bool) {

    address signer = _hashTypedDataV4(
        keccak256(
            abi.encode(
                _TYPEHASH,
                req.from,
                req.to,
                req.value,
                req.gas,
                req.nonce,
                keccak256(req.data)
            )
        )
    ).recover(signature);

    return _nonces[req.from] == req.nonce && signer == req.from;

}`} language="text" theme="dark" />

验证通过后，MinimalForwarder 会带上适当的 calldata 调用目标合约，从而执行该交易。

<CodeSnippet code={`function execute(ForwardRequest calldata req, bytes calldata signature)
        public
        payable
        returns (bool, bytes memory)
{
    require(verify(req, signature), "MinimalForwarder: signature does not match request");
    _nonces[req.from] = req.nonce + 1;

    (bool success, bytes memory returndata) = req.to.call{gas: req.gas, value: req.value}(abi.encodePacked(req.data, req.from));

    // Validate that the relayer has sent enough gas for the call.
    // See
    if (gasleft() <= req.gas / 63) {
        // We explicitly trigger invalid opcode to consume all gas and bubble-up the effects, since
        // neither revert or assert consume all gas since Solidity 0.8.0
        //
        /// @solidity memory-safe-assembly
        assembly {
            invalid()
        }
    }

    return (success, returndata);

}`} language="text" theme="dark" />

这也正是元交易的缺点所在：被 MinimalForwarder 调用的合约必须允许 MinimalForwarder 代表用户对其进行调用。

## 什么是 ERC-2771？

最后，当调用到达目标合约时，该合约需要能够识别出嵌套在 Relayer 交易内部的原始用户（_msg.sender_）和 calldata（_msg.data_）。为了将这一过程标准化，[ERC2771](https://eips.ethereum.org/EIPS/eip-2771) 应运而生。

OpenZepplin 提供了一个名为 [ERC2771Context](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/metatx/ERC2771Context.sol) 的工具合约，方便智能合约开发者从 MinimalForwarder 发来的数据中提取出真正的 msg.sender 和 msg.data。

<CodeSnippet code={`contract YourContract is ERC2771Context {
    constructor(MinimalForwarder forwarder, string memory uri_)
        ERC2771Context(address(forwarder))
    {}

    // Depending on whether the call is made by the \`MinimalForwarder\` or not the \`msg.sender\` and \`msg.data\` will be inferred accordingly

}`} language="text" theme="dark" />

由于每个智能合约都必须知道如何解析元交易，因此为已部署的合约实现该功能颇为困难，还可能引入 bug！

尽管市面上还有许多其他元交易基础设施服务，但它们都要求目标合约继承基于 EIP-712 或 EIP-2771 的合约，以推导出 _msg.sender_ 和 _msg.data_。

这一标准固然不错，但它无法回溯适用于已有合约。这正是 ERC-4337 被创造出来的原因，如今它正在取代绝大多数基于 ERC-2771 的方案。

## 常见问题

### 什么是元交易？

元交易允许一个称为 Relayer 的第三方代替用户发送交易并支付 gas 费用，从而消除了用户在与[应用](https://www.alchemy.com/dapps/top/defi-dapps)交互前必须持有 Ether 这一门槛。

### 元交易是如何运作的？

用户对包含交易信息的消息进行签名，签名后的消息会被传递给 Relayer，由 Relayer 验证请求、支付 gas 费用、签署一笔原生交易，并将其提交到区块链上执行。

### 什么是 ERC-2771？

ERC-2771 是一项标准，它允许智能合约在通过受信任的 forwarder 接收元交易时，识别出原始用户和 calldata，从而确保正确提取 msg.sender 和 msg.data。

### 元交易中的 MinimalForwarder 是什么？

MinimalForwarder 是一个链上智能合约，用于验证用户签名消息的真实性并防止重放攻击，验证通过后再通过调用目标合约来执行该交易。

### 什么是 ERC2771Context？

ERC2771Context 是 [OpenZeppelin](https://www.alchemy.com/dapps/openzeppelin) 提供的一个工具合约，方便智能合约开发者从 MinimalForwarder 发送的元交易中提取出真正的 msg.sender 和 msg.data。

### ERC-2771 元交易有哪些局限性？

ERC-2771 要求目标合约通过继承基于 EIP-712 或 EIP-2771 的合约来显式支持该标准，这使得已部署合约的实现变得困难，还可能引入 bug。

### ERC-2771 与 ERC-4337 相比如何？

虽然 ERC-2771 可以通过 relayer 和 forwarder 实现无 gas 交易，但 ERC-4337（账户抽象）正在取代大多数 ERC-2771 方案，因为它可以回溯适用，且不需要对现有合约进行修改。

### Relayer 在元交易中扮演什么角色？

Relayer 是一个 Ethereum 账户，用于为用户垫付 gas 费用，其私钥安全存储在服务提供商的服务器上，还可以配置白名单、gas 价格上限以及条件逻辑等功能。
