> ## Documentation Index
> Fetch the complete documentation index at: https://docs.li.fi/llms.txt
> Use this file to discover all available pages before exploring further.

# 无 gas 交易

> 让用户在无需持有源链原生代币的情况下完成交换和 bridge，由 LI.FI 中继已签名的载荷并支付 gas。

无 gas 执行让用户可以在不持有源链原生代币的情况下完成交换或 bridge。用户签署的是一个 EIP-712 载荷，而不是发送交易。LI.FI 会在链上中继该载荷并支付 gas，随后以输入代币收取中继成本，并在报价中单独列出。

本指南只涵盖无 gas 方式在标准集成基础上新增的内容。关于它所依赖的基础概念，请参阅[请求路由与报价](/introduction/user-flows-and-examples/requesting-route-fetching-quote)、[报价与路由的区别](/introduction/user-flows-and-examples/difference-between-quote-and-route)、[状态跟踪](/introduction/user-flows-and-examples/status-tracking)，以及关于身份验证和速率限制的 [API 参考](/api-reference/introduction)。

***

## 工作原理

<Steps>
  <Step title="获取可签名的步骤" icon="file-signature">
    在标准报价请求中加入 `gasless=true`，或在路由请求中设置 `options.gasless: true`，并针对所选步骤提交交易数据。两种方式都会返回一个携带 EIP-712 载荷、过期时间，以及成本估算中中继费用条目的步骤。
  </Step>

  <Step title="签名" icon="pen">
    用户使用 `eth_signTypedData_v4` 对类型化数据进行签名。用户账户不会发出任何交易，也不需要原生代币。
  </Step>

  <Step title="中继" icon="paper-plane">
    将已签名的步骤提交到 `POST /v1/advanced/relay`。LI.FI 会验证签名，将载荷与原始报价进行校验，并从中继账户广播交易。响应中会携带一个任务 ID。
  </Step>

  <Step title="跟踪" icon="magnifying-glass">
    轮询 `GET /v1/status?taskId=...`，直到转账状态变为 `DONE` 或 `FAILED`。
  </Step>
</Steps>

已签名的载荷是一批通过用户自己的账户、在 EIP-7702 委托下执行的调用：中继费用转账、ERC-20 输入代币的授权，以及交换或 bridge 调用。中继器只能按签名内容原样提交这批调用，且整批调用要么全部成功要么全部回滚。只要其中一个调用回滚，其余调用（包括费用转账）也会一并回滚。

***

## 前置条件

| 前置条件          | 说明                                                                                                                    |
| ------------- | --------------------------------------------------------------------------------------------------------------------- |
| 源链            | LI.FI 当前为该账户已安装的委托合约提供中继服务的 EVM 链。可用性在报价时确定。目标链可以是 [LI.FI 支持](/introduction/chains)的任意链。                              |
| `fromAddress` | 每个无 gas 请求都必须提供。账户的链上状态决定了载荷的构建方式。                                                                                    |
| 账户类型          | 一个已经通过 EIP-7702 委托给 LI.FI 代为中继的委托合约的 EOA，目前为 [Calibur](https://github.com/Uniswap/calibur)。通过 ERC-1271 签名的智能合约钱包不受支持。 |

<Warning>
  未完成委托的 EOA 目前无法获得服务。`/v1/quote` 在这种情况下可能返回一个通用的"无可用报价"错误。要获取针对该账户和链的具体原因，请改为请求路由，并检查 `unavailableRoutes.filteredOut[].reason`。
</Warning>

***

## 第一步：获取可签名的步骤

两种标准报价流程均无需改动即可使用。无 gas 只是一个请求标志。

在标准报价请求中加入 `gasless=true`，响应即为可签名的步骤：

<CodeGroup>
  ```bash curl theme={"system"}
  curl --request GET \
    --url 'https://li.quest/v1/quote?fromChain=42161&toChain=42161&fromToken=0xaf88d065e77c8cC2239327C5EDb3A432268e5831&toToken=0x82aF49447D8a07e3bd95BD0d56f35241523fBab1&fromAmount=50000000&fromAddress=0xYOUR_USER_ADDRESS&integrator=YOUR_INTEGRATOR_NAME&gasless=true' \
    --header 'x-lifi-api-key: YOUR_API_KEY'
  ```

  ```ts TypeScript theme={"system"}
  const params = new URLSearchParams({
    fromChain: '42161',                                            // Arbitrum
    toChain: '42161',
    fromToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',       // USDC
    toToken: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',         // WETH
    fromAmount: '50000000',                                        // 50 USDC（6 位小数）
    fromAddress: '0xYOUR_USER_ADDRESS',                            // 无 gas 场景必填
    integrator: 'YOUR_INTEGRATOR_NAME',
    gasless: 'true',
  })

  const step = await fetch(`https://li.quest/v1/quote?${params}`, {
    headers: { 'x-lifi-api-key': API_KEY },
  }).then((res) => res.json())
  ```
</CodeGroup>

如果想从多条路由中选择，可在标准的 `/v1/advanced/routes` 请求体中设置 `options.gasless: true` 来请求路由。这些路由已经计入了中继费用，但尚未携带可签名的载荷。将所选路由的步骤原样提交到 `/v1/advanced/stepTransaction`，响应会以与单次报价相同的结构返回。

<Note>
  无 gas 方式不适用于 `/v1/quote/toAmount`，也不能与 `executionType=message` 或 `executionType=all` 组合使用，这类请求会被拒绝。`/v1/quote/contractCalls` 不支持无 gas 执行，会忽略 `gasless` 字段，请求会按普通合约调用报价继续处理。反向报价会被拒绝，因为其收敛计算只能求解基于百分比的费用，无法求解以固定 gas 计价的中继费用。
</Note>

### 可签名的步骤

一个无 gas 步骤是一个标准的 LI.FI 步骤，外加两个额外属性，以及成本估算中的中继费用条目：

```json theme={"system"}
{
  "id": "9d5cbeeb-...",
  "type": "lifi",
  "tool": "sushiswap",
  "action": { /* 与往常相同 */ },
  "estimate": {
    "feeCosts": [
      { "name": "LIFI Gasless Relay Fee", "included": true /* ... */ }
    ]
    // ...
  },

  // 用户要签名的载荷。
  "typedData": [
    {
      "primaryType": "SignedBatchedCall",
      "domain": {
        "name": "Calibur",
        "version": "1.0.0",
        "chainId": 42161,
        "verifyingContract": "0xYOUR_USER_ADDRESS",  // 用户自己的账户
        "salt": "0x..."
      },
      "types": { /* EIP712Domain, SignedBatchedCall, BatchedCall, Call */ },
      "message": {
        "batchedCall": {
          "calls": [
            { "to": "0x...", "value": "0", "data": "0x..." },  // 中继费用转账
            { "to": "0x...", "value": "0", "data": "0x..." },  // 授权（仅限 ERC-20 输入）
            { "to": "0x...", "value": "0", "data": "0x..." }   // 交换或 bridge 调用
          ],
          "revertOnFailure": true
        },
        "nonce": "...",
        "keyHash": "0x00...00",
        "executor": "0x...",
        "deadline": 1765432100
      }
    }
  ],

  // 签名不再被接受的 Unix 秒数时间点。
  "expiresAt": 1765432100
}
```

* **`typedData`** 可直接用于 `eth_signTypedData_v4`。在 EIP-7702 委托下，执行这批调用的账户就是用户自己的地址，这也是为什么 `domain.verifyingContract` 是该地址。委托实现通过 `domain.salt` 绑定。
* **`expiresAt`** 是 Unix 秒数，与载荷中的 `deadline` 相等。有效期很短，以分钟而非小时计。过期后请重新请求报价，不会产生任何费用。
* **`transactionRequest`** 在这里不会用到。执行是通过已签名的类型化数据和中继接口完成的。

<Warning>
  提交到中继接口的步骤必须与收到时完全一致。LI.FI 会将其与报价时存储的记录进行校验，因此对调用、金额、接收方或截止时间的任何改动都会被拒绝。
</Warning>

***

## 第二步：签署载荷

使用标准的 EIP-712 类型化数据签名方式进行签名。通过 JSON-RPC 将载荷传递给钱包时，请原样使用，因为 types 对象中包含了钱包会校验的 `EIP712Domain` 条目。

<CodeGroup>
  ```ts viem theme={"system"}
  const [payload] = step.typedData

  const signature = await walletClient.signTypedData({
    account,
    domain: payload.domain,
    types: payload.types,
    primaryType: payload.primaryType,
    message: payload.message,
  })
  ```

  ```ts ethers v6 theme={"system"}
  const [payload] = step.typedData

  // ethers 会自行推导 domain 类型，且会拒绝显式的 EIP712Domain 条目。
  const { EIP712Domain, ...types } = payload.types

  const signature = await signer.signTypedData(payload.domain, types, payload.message)
  ```
</CodeGroup>

***

## 第三步：提交中继请求

将该步骤原样提交，并在类型化数据条目中附上签名：

```ts TypeScript theme={"system"}
const relayBody = {
  ...step,
  typedData: [{ ...step.typedData[0], signature }],
}

const response = await fetch('https://li.quest/v1/advanced/relay', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-lifi-api-key': API_KEY,
  },
  body: JSON.stringify(relayBody),
})
```

请求被接受时，接口返回 `200`：

```json theme={"system"}
{
  "status": "ok",
  "data": {
    "taskId": "0x2f8a...c41d"
  }
}
```

"已接受"意味着 LI.FI 已经验证了已签名的执行内容，将其持久化记录，并且下游执行方已接受该任务进行提交。请保存返回的任务 ID，并在收到确认后使用状态接口进行跟踪。如果请求在收到该确认之前失败，请将结果视为不确定状态，并遵循下文的重试指引，而不要假定载荷是否已被接受。

### 中继错误

拒绝会采用 LI.FI 标准的错误响应格式：与其他所有接口相同的响应体结构、相同的数字[错误码](/api-reference/error-codes)，以及相同的 HTTP 状态码。状态码和 `code` 标明了应对方式，`message` 则说明具体是哪项检查未通过，供你记录日志或用于支持排查。

| HTTP | `code` | 含义                                                       | 应对方式                                                                                                |
| ---- | ------ | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| 400  | 1011   | 已签名的载荷永远无法执行。签名、载荷完整性、账户状态或试运行阶段的验证失败，未提交任何内容。           | 请求一个新的报价并让用户重新签名。重新提交会以相同方式失败。                                                                      |
| 404  | 1003   | 已签名的步骤无法匹配到一个有效的报价完整性记录，因为其绑定关系缺失、已过期，或在被接受前已被消耗。        | 请求一个新的报价。                                                                                           |
| 409  | 1007   | 该已签名报价的 nonce 与链上状态不再匹配，或 gas 价格发生变化，导致报价中的费用不再足以覆盖执行成本。 | 在状态稳定后请求新的报价；但如果这紧跟在一次不确定的 `424` 之后，请先核实原始执行情况，再提交替代请求。                                             |
| 422  | 1004   | 中继无法为该请求提供服务。无 gas 中继已关闭，或该账户需要中继不支持的某项能力。               | 请勿重试，请联系 LI.FI。                                                                                     |
| 424  | 1008   | 中继无法确认接受流程是否已完成。该失败可能发生在持久化记录建立之前，也可能发生在下游提交已经开始之后。      | 将结果视为不确定状态。不要立即签署或提交另一笔不同的执行。以退避方式重试完全相同的请求。后续如果返回 `404`，意味着需要重新报价；如果返回 `409`，则可能是原始执行已经推进，需先核实再替换。 |

在下游提交之前报告的失败不会广播交易，也不会向用户收费。`424` 情况有所不同：它可能代表一个不确定的下游结果，不能被当作"未提交任何内容"的证据。

***

## 第四步：跟踪执行

状态跟踪的方式与[状态跟踪指南](/introduction/user-flows-and-examples/status-tracking)中描述的一致，但有两个无 gas 场景特有的地方。从中继被接受的那一刻起，即使还没有任何交易产生，执行就可以通过任务 ID 进行查询。一旦响应中出现了交易哈希，同一笔执行也可以通过该哈希查询。

```bash curl theme={"system"}
curl --request GET \
  --url 'https://li.quest/v1/status?taskId=0x2f8a...c41d' \
  --header 'x-lifi-api-key: YOUR_API_KEY'
```

有两种状态是无 gas 场景特有的：

| `status`  | `substatus`            | 含义                                                        |
| --------- | ---------------------- | --------------------------------------------------------- |
| `PENDING` | 无                      | 已接受，尚未广播任何交易。                                             |
| `FAILED`  | `EXPIRED`              | 签名截止时间在广播前已过期。未产生任何花费或收费，请重新请求报价。                         |
| `FAILED`  | `UNKNOWN_FAILED_ERROR` | 执行失败，`substatusMessage` 中会携带具体原因。失败的批次是原子性回滚的，因此不会收取任何费用。 |

其余部分遵循标准的状态语义。响应会将该笔转账归属到发起签名的用户地址，而不是中继账户，因此你的记账方式与用户自行提交交易时保持一致。

***

## 费用

中继费用覆盖了 LI.FI 代表用户执行交易所花费的 gas。它是一个以 gas 计价的固定金额，依据链的 gas 特征和报价时的实时 gas 价格计算得出，随后换算为输入代币。该数值在 `/v1/quote`、`/v1/advanced/routes` 和 `/v1/advanced/stepTransaction` 中保持一致。

它会以固定名称 `LIFI Gasless Relay Fee`（`included: true`）出现在成本估算中，这意味着它会在路由前从 `fromAmount` 中扣除，报价中的输出金额已经反映了这一点。用户的总支出正好等于 `fromAmount`。

在链上，费用转账是已签名批次中的第一个调用。它受签名保护，中继器无法改动，并且与交换操作是原子性的，因此除非交换成功执行，否则无法收取该费用。被拒绝的中继请求、过期的签名和失败的执行都不会产生任何收费。

<Note>
  费用在已签名的报价中是固定的，但如果 gas 价格变动超出可接受的容差范围，中继时的检查仍会以 `409` 拒绝该请求。届时请重新请求报价。
</Note>

***

## 无法提供无 gas 服务的情况

在 `/v1/quote` 上，拒绝会以标准的"未找到"错误返回。在 `/v1/advanced/routes` 上，它会出现在 `unavailableRoutes.filteredOut` 中，其中 `reason` 是一句人类可读的说明，遵循与其他所有路由过滤条件相同的约定。你可以将其展示给用户，或写入日志，但不要以编程方式对其进行匹配。

以下情况会导致无 gas 请求被拒绝：

* 该账户是未完成委托的 EOA，或委托给了 LI.FI 不提供中继服务的合约；
* 该账户是智能合约钱包；
* 请求中未携带 `fromAddress`；
* 该路由在输入金额之外还收取固定的原生代币费用，无 gas 用户无法承担这笔费用，其他工具可能仍可服务该笔交易；
* 中继费用扣除后，剩余的输入金额不足以支撑执行，这也是该功能对交易规模设下限的原因。

***

## 操作注意事项

<AccordionGroup>
  <Accordion title="将每个已签名的报价视为独立的一次执行" icon="list-ol">
    不同的报价使用各自独立的、带键值的 nonce，可以为同一账户并存。`409` nonce 冲突意味着该已签名报价对应的 nonce 与链上状态不再匹配，例如因为同一笔执行已经被消耗。请重新请求报价，而不要重新签名或修改旧的载荷。
  </Accordion>

  <Accordion title="在签名时才请求报价" icon="clock">
    签名截止时间很短。请在用户准备好签名时才请求报价，并在签名完成后立即中继。费用过期和报价过期属于常见的拒绝情形，且都不会产生任何费用，重新获取并重试即可。
  </Accordion>

  <Accordion title="不要缓存报价" icon="ban">
    每个报价都嵌入了账户状态和一个很短的截止时间。请将其视为一次性的签名载荷，遵守 `expiresAt`，并只在用户准备好签名时才请求它。
  </Accordion>

  <Accordion title="重试同一个载荷，但要处理状态变化" icon="rotate">
    在超时或 `424` 之后，只应以退避方式重试完全相同的已签名请求，切勿立即另外创建一次替代执行。重试可能会恢复一个已存在的、尚未广播的记录；如果报价完整性记录在被接受前已被消耗，会返回 `404`；如果 nonce 或费用状态发生变化，会返回 `409`。收到 `404` 后请重新报价；收到 `409` 后，请先核实原始执行是否已经推进。成功收到确认后，请保存任务 ID 并跟踪状态，而不要重新提交该载荷。
  </Accordion>
</AccordionGroup>

***

## 后续步骤

<CardGroup cols={2}>
  <Card title="Gas Fronting" icon="gas-pump" href="/guides/gas-subsidy" horizontal>
    在桥接资产的同时提供目标链原生 gas，让用户到账后即可交易。
  </Card>

  <Card title="状态跟踪" icon="magnifying-glass" href="/introduction/user-flows-and-examples/status-tracking" horizontal>
    转账完整的状态与子状态词汇表。
  </Card>

  <Card title="错误码" icon="triangle-exclamation" href="/api-reference/error-codes" horizontal>
    API 返回的每个数字错误码及其含义。
  </Card>

  <Card title="API 参考" icon="code" href="/api-reference/introduction" horizontal>
    身份验证、速率限制和完整的接口列表。
  </Card>
</CardGroup>
