自托管 gas 豁免
自托管 Gas 豁免让你可以运营自己的豁免基础设施,而不必使用托管的 Waiver Server API。你通过链上治理注册一个豁免地址,然后直接向网络广播包装交易。
本指南涵盖注册豁免地址、收集已签名的用户交易、构造包装交易并广播它们。
关于托管的 Waiver Server API 集成路径,参见 启用免 gas 交易。
前置条件
- 通过验证者治理在链上注册的豁免地址。
- 为你的目标合约配置的
AllowedTarget策略。
概述
自托管流程:
- 从用户收集已签名的 InnerTx,其
gasPrice = 0。 - 构造 WrapperTx:对 InnerTx 进行 RLP 编码,并将其包装在一笔发送到 marker 地址的交易中。
- 广播 WrapperTx,通过
eth_sendRawTransaction。
步骤 1:收集用户的 InnerTx
用户签名一笔 gasPrice = 0 的交易。to 地址和方法选择器必须匹配你豁免的 AllowedTarget 策略。
// config.ts
export const CONFIG = {
RPC_URL: "https://rpc.testnet.stable.xyz",
CHAIN_ID: 2201, // 988 for mainnet
MARKER_ADDRESS: "0x000000000000000000000000000000000000f333",
USDT0_ADDRESS: "0x78Cf24370174180738C5B8E352B6D14c83a6c9A9",
};// collectInnerTx.ts
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:构造 WrapperTx
对已签名的 InnerTx 进行 RLP 编码,并将其包装在一笔发送到 marker 地址的交易中。gasLimit 必须同时覆盖内部执行和包装开销。
// constructWrapper.ts
import { ethers } from "ethers";
import { CONFIG } from "./config";
const innerTxBytes = ethers.decodeRlp(signedInnerTx);
const rlpEncoded = ethers.encodeRlp(innerTxBytes);
const waiverNonce = await provider.getTransactionCount(waiverWallet.address);
const wrapperTx = {
to: CONFIG.MARKER_ADDRESS,
data: rlpEncoded,
value: 0,
gasPrice: 0,
gasLimit: (gasEstimate * 12n / 10n) * 2n, // ~2x inner gas for overhead
nonce: waiverNonce,
chainId: CONFIG.CHAIN_ID,
};
const signedWrapperTx = await waiverWallet.signTransaction(wrapperTx);步骤 3:广播
通过标准 JSON-RPC 提交已签名的 WrapperTx。
// broadcast.ts
const txHash = await provider.send("eth_sendRawTransaction", [signedWrapperTx]);
console.log("Wrapper tx broadcast:", txHash);
const receipt = await provider.waitForTransaction(txHash);
console.log("Confirmed:", receipt.status === 1);Wrapper tx broadcast: 0x...
Confirmed: true关键要点
- 自托管豁免需要一个通过链上验证者治理注册的豁免地址。
- WrapperTx 被发送到 marker 地址(
0x...f333),并以 RLP 编码的 InnerTx 作为数据。 - InnerTx 和 WrapperTx 都必须满足
gasPrice = 0和value = 0。
下一步推荐
- Gas 豁免概念 — 在运行你自己的豁免之前理解其机制。
- Gas 豁免协议 — 参考完整的协议规范,了解 marker 路由、授权和执行语义。
- 启用免 gas 交易 — 使用托管的 Waiver Server API,而不是自托管。

