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 Server API。你通过链上治理注册一个豁免地址,然后直接向网络广播包装交易。

本指南涵盖注册豁免地址、收集已签名的用户交易、构造包装交易并广播它们。

关于托管的 Waiver Server API 集成路径,参见 启用免 gas 交易

前置条件

  • 通过验证者治理在链上注册的豁免地址。
  • 为你的目标合约配置的 AllowedTarget 策略。

概述

自托管流程:

  1. 从用户收集已签名的 InnerTx,其 gasPrice = 0
  2. 构造 WrapperTx:对 InnerTx 进行 RLP 编码,并将其包装在一笔发送到 marker 地址的交易中。
  3. 广播 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 = 0value = 0

下一步推荐