启用免 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>
概述
集成流程分为三个步骤:
- 构建 InnerTx:用户签署一笔
gasPrice = 0的交易。 - 提交到 Waiver Server:将已签名的交易提交到 Waiver Server API。
- 处理响应: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。

