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

가스 없는 트랜잭션 활성화

가스 면제는 Stable에서 가스 없는 트랜잭션을 가능하게 합니다. 가스 면제를 통해 애플리케이션은 사용자를 대신하여 가스 요금을 부담하므로 사용자는 가스 비용으로 USDT0를 보유할 필요 없이 계약과 상호 작용할 수 있습니다.

이 가이드는 Stable이 면제를 운영하고 API 키로 인증하는 호스팅된 Waiver Server API를 다룹니다. 대신 거버넌스를 통해 자체 면제 주소를 등록하려면 통합 경로 선택을 참조하십시오.

전제 조건

  • Stable 팀에서 발급한 Waiver 서버용 API 키
  • 대상 계약 주소는 면제의 AllowedTarget 정책에 등록되어야 합니다.

Waiver 서버

기본 URL:
  • 메인넷: TBD
  • 테스트넷: https://waiver.testnet.stable.xyz

인증: Bearer <your-api-key>

개요

통합 흐름은 세 단계로 구성됩니다.

  1. InnerTx 빌드: 사용자가 gasPrice = 0으로 트랜잭션에 서명합니다.
  2. Waiver 서버에 제출: 서명된 트랜잭션을 Waiver Server API에 제출합니다.
  3. 응답 처리: 면제 서버가 트랜잭션을 래핑하고 브로드캐스트합니다. 스트리밍된 결과를 처리하고 트랜잭션 해시를 사용자에게 표시합니다.

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 목록에 있어야 합니다.

다음 권장 사항