가스 없는 트랜잭션 활성화
가스 면제는 Stable에서 가스 없는 트랜잭션을 가능하게 합니다. 가스 면제를 통해 애플리케이션은 사용자를 대신하여 가스 요금을 부담하므로 사용자는 가스 비용으로 USDT0를 보유할 필요 없이 계약과 상호 작용할 수 있습니다.
이 가이드는 Stable이 면제를 운영하고 API 키로 인증하는 호스팅된 Waiver Server API를 다룹니다. 대신 거버넌스를 통해 자체 면제 주소를 등록하려면 통합 경로 선택을 참조하십시오.
전제 조건
- Stable 팀에서 발급한 Waiver 서버용 API 키
- 대상 계약 주소는 면제의
AllowedTarget정책에 등록되어야 합니다.
Waiver 서버
기본 URL:- 메인넷: TBD
- 테스트넷:
https://waiver.testnet.stable.xyz
인증: Bearer <your-api-key>
개요
통합 흐름은 세 단계로 구성됩니다.
- InnerTx 빌드: 사용자가
gasPrice = 0으로 트랜잭션에 서명합니다. - Waiver 서버에 제출: 서명된 트랜잭션을 Waiver Server API에 제출합니다.
- 응답 처리: 면제 서버가 트랜잭션을 래핑하고 브로드캐스트합니다. 스트리밍된 결과를 처리하고 트랜잭션 해시를 사용자에게 표시합니다.
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 서버에 제출
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).
주요 요점
- 가스 면제는 서버 측 통합입니다. 백엔드가 서명된 사용자 트랜잭션을 Waiver 서버에 제출합니다. 사용자는 Waiver 서버와 직접 상호 작용하지 않습니다.
- 사용자는 항상 InnerTx에 서명하여 서명 무결성을 유지합니다. 면제는 사용자 트랜잭션을 수정할 수 없습니다.
- 대상 계약은 면제의
AllowedTarget목록에 있어야 합니다.
다음 권장 사항
- 제로 가스 트랜잭션: 데모 중심 흐름과 영 가스를 영수증에서 확인하는 방법을 참조하세요.
- 자체 호스팅 가스 면제: 호스팅된 API 없이 자체 면제를 실행하세요.
- 가스 면제 프로토콜: 전체 래퍼 트랜잭션 사양 및 거버넌스 모델.
- 가스 면제로 릴레이: Enterprise SDK를 사용하여 자체 거버넌스 등록 면제에서 가스를 후원하세요.

