Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

启用免 Gas 交易

Gas Waiver 可在 Stable 上实现免 Gas 交易。借助 Gas Waiver,应用程序代表用户支付 Gas 费用,因此用户无需持有 USDT0 即可与合约进行交互。

本指南介绍如何通过 Waiver Server API 进行集成。

前置条件

  • 由 Stable 团队签发的 Waiver Server API 密钥
  • 目标合约地址必须已在豁免的 AllowedTarget 策略中注册

Waiver Server

基础 URL:
  • 主网:待定
  • 测试网:https://waiver.testnet.stable.xyz

授权: Bearer <your-api-key>

概述

集成流程分为三个步骤:

  1. 构建 InnerTx:用户签署一笔 gasPrice = 0 的交易。
  2. 提交到 Waiver Server:将已签名的交易提交到 Waiver Server API。
  3. 处理响应:waiver server 包装并广播交易。处理流式返回的结果,并向用户展示交易哈希。

步骤 1:创建用户的 InnerTx

用户签署一笔 gasPrice = 0 的标准交易。to 地址和方法选择器必须被豁免的 AllowedTarget 策略所允许。

// config.ts
export const CONFIG = {
  RPC_URL: "https://rpc.testnet.stable.xyz",
  CHAIN_ID: 2201, // 988 for mainnet
  WAIVER_SERVER: "https://waiver.testnet.stable.xyz",
  USDT0_ADDRESS: "0x78Cf24370174180738C5B8E352B6D14c83a6c9A9",
};
import { ethers } from "ethers";
import { CONFIG } from "./config";
 
const provider = new ethers.JsonRpcProvider(CONFIG.RPC_URL);
const usdt0 = new ethers.Contract(CONFIG.USDT0_ADDRESS, [
  "function transfer(address to, uint256 amount) returns (bool)"
], provider);
 
const callData = usdt0.interface.encodeFunctionData("transfer", [
  recipientAddress,
  ethers.parseUnits("0.01", 18)
]);
 
const gasEstimate = await provider.estimateGas({
  from: userWallet.address,
  to: CONFIG.USDT0_ADDRESS,
  data: callData,
});
 
const nonce = await provider.getTransactionCount(userWallet.address);
 
const innerTx = {
  to: CONFIG.USDT0_ADDRESS,
  data: callData,
  value: 0,
  gasPrice: 0,
  gasLimit: gasEstimate,
  nonce: nonce,
  chainId: CONFIG.CHAIN_ID,
};
 
const signedInnerTx = await userWallet.signTransaction(innerTx);

步骤 2:提交到 Waiver Server

import { CONFIG } from "./config";
 
const API_KEY = process.env.WAIVER_API_KEY;
 
const response = await fetch(`${CONFIG.WAIVER_SERVER}/v1/submit`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${API_KEY}`,
  },
  body: JSON.stringify({
    transactions: [signedInnerTx],
  }),
});

批量提交

你可以在单次请求中提交多笔已签名的交易:

body: JSON.stringify({
  transactions: [signedTx1, signedTx2, signedTx3],
})

每个结果行都包含一个 index 字段,对应交易在数组中的位置。

步骤 3:处理响应

响应以 NDJSON(以换行符分隔的 JSON)形式流式返回。每一行对应一笔已提交的交易。

const reader = response.body.getReader();
const decoder = new TextDecoder();
 
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
 
  const lines = decoder.decode(value).trim().split("\n");
  for (const line of lines) {
    const result = JSON.parse(line);
    if (result.success) {
      console.log(`tx ${result.index} confirmed: ${result.txHash}`);
    } else {
      console.error(`tx ${result.index} failed: ${result.error.message}`);
    }
  }
}
成功响应:
{"index": 0, "id": "abc123", "success": true, "txHash": "0x..."}
失败响应:
{"index": 1, "id": "def456", "success": false, "error": {"code": "VALIDATION_FAILED", "message": "invalid signature"}}

错误代码

代码说明
PARSE_ERROR解析交易失败
INVALID_REQUEST请求体格式错误
BATCH_SIZE_EXCEEDED批量大小超出允许的最大值
VALIDATION_FAILED交易验证失败(例如:签名无效、目标不被允许)
BROADCAST_FAILED广播到链上失败
RATE_LIMITED超出速率限制
QUEUE_FULL服务器队列已满
TIMEOUT请求超时

API 参考

GET /v1/health

健康检查端点。认证:无需。

POST /v1/submit

提交一批已签名的内部交易。认证:必需(Bearer)。

请求体:
{
  "transactions": ["0x<signedInnerTx1>", "0x<signedInnerTx2>"]
}

响应以 NDJSON 形式流式返回。每一行对应一笔已提交交易的索引。

GET /v1/submit

用于流式提交的 WebSocket 接口。认证:必需(Bearer)。

关键要点

  • Gas Waiver 是一种服务端集成:你的后端将已签名的用户交易提交到 Waiver Server。用户从不直接与 Waiver Server 交互。
  • 用户始终签署 InnerTx,从而保持签名的完整性。豁免方无法修改用户的交易。
  • 目标合约必须位于豁免的 AllowedTarget 列表中。

后续推荐

  • 零 Gas 交易 — 查看以演示为核心的流程,以及如何在收据中验证零 Gas。
  • 自托管 Gas Waiver — 在不使用托管 API 的情况下运行你自己的豁免服务。
  • Gas 豁免协议 — 完整的包装交易规范和治理模型。
  • Stable SDK — 使用类型化客户端来签署用户交易,随后将其提交到 Waiver Server。