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

언본딩 완료 추적

언본딩 기간이 완료되면 프로토콜은 시스템 트랜잭션을 통해 StableSystem 사전 컴파일(0x0000000000000000000000000000000000009999)을 통해 UnbondingCompleted 이벤트를 발생시킵니다. 이를 통해 dApp은 사용자에게 알리고 사용자 지정 인덱서를 실행하거나 REST 엔드포인트를 폴링하지 않고도 실시간으로 잔액을 업데이트할 수 있습니다.

전제 조건

  • 시스템 트랜잭션에 대한 이해.
  • 스테이킹, 특히 undelegate 및 언본딩 프로세스에 대한 지식.
  • 표준 web3 라이브러리(예: ethers.js v6)를 사용한 계약 이벤트 구독 및 필터링 경험.

개요

  • 계약 인스턴스 설정: StableSystem 사전 컴파일을 위한 계약 인스턴스를 생성합니다.
  • 애플리케이션에서 이벤트 처리: 애플리케이션 로직에 따라 실시간 이벤트를 구독하거나 기록 데이터를 쿼리합니다.
  • 연결 문제 처리: 지속적인 WebSocket 구독을 위한 재연결 로직을 구현합니다.

1단계: 계약 인스턴스 설정

UnbondingCompleted 이벤트 ABI를 사용하여 StableSystem 사전 컴파일을 위한 계약 인스턴스를 생성합니다.

// config.ts
import { ethers } from "ethers";
 
export const STABLE_SYSTEM_ADDRESS =
  "0x0000000000000000000000000000000000009999";
 
export const STABLE_SYSTEM_ABI = [
  "event UnbondingCompleted(address indexed delegator, address indexed validator, uint64 indexed originBlockHeight, uint256 amount)",
];
 
export const provider = new ethers.JsonRpcProvider("https://rpc.testnet.stable.xyz");
export const stableSystem = new ethers.Contract(
  STABLE_SYSTEM_ADDRESS,
  STABLE_SYSTEM_ABI,
  provider
);
출력 없음. StableSystem 계약 인스턴스는 쿼리 또는 구독할 준비가 되었습니다.

2단계: 애플리케이션에서 이벤트 처리

애플리케이션 로직에 따라 실시간 이벤트를 구독하거나 기록 데이터를 쿼리하거나 둘 다 수행합니다.

실시간 구독

언본딩이 완료될 때 실시간 알림을 위해 UnbondingCompleted 이벤트를 구독합니다. 잔액 업데이트 트리거, 알림 전송 또는 대시보드 통계 새로 고침에 유용합니다.

// subscribeBasic.ts
import { ethers } from "ethers";
import { stableSystem } from "./config";
 
stableSystem.on("UnbondingCompleted", (delegator, validator, originBlockHeight, amount, event) => {
  console.log("Unbonding completed:");
  console.log("  Delegator:", delegator);
  console.log("  Validator:", validator);
  console.log("  Origin block:", originBlockHeight.toString());
  console.log("  Amount:", ethers.formatEther(amount), "tokens");
  console.log("  Block:", event.log.blockNumber);
  console.log("  Tx Hash:", event.log.transactionHash);
});
Unbonding completed:
  Delegator: 0xabcd...
  Validator: 0x1234...
  Origin block: 36975999
  Amount: 100.0 tokens
  Block: 36976000
  Tx Hash: 0x12ab...

사용자별 필터링

특정 위임자 주소에 대한 이벤트만 수신하려면 인덱스 이벤트 매개변수를 사용하여 필터를 생성합니다.

// subscribeByUser.ts
import { ethers } from "ethers";
import { stableSystem } from "./config";
 
const userAddress = "0xabcd...";
const filter = stableSystem.filters.UnbondingCompleted(userAddress);
 
stableSystem.on(filter, (delegator, validator, originBlockHeight, amount) => {
  console.log("User unbonding completed:", {
    delegator,
    validator,
    originBlockHeight,
    amount: ethers.formatEther(amount),
  });
});
User unbonding completed: {
  delegator: "0xabcd...",
  validator: "0x1234...",
  originBlockHeight: 36975999n,
  amount: "100.0"
}

검증자별 필터링

// subscribeByValidator.ts
import { stableSystem } from "./config";
 
const validatorAddress = "0x1234...";
const validatorFilter = stableSystem.filters.UnbondingCompleted(
  null,
  validatorAddress
);
 
stableSystem.on(validatorFilter, (delegator, validator, originBlockHeight, amount) => {
  console.log("Validator unbonding completed:", {
    delegator,
    validator,
    originBlockHeight,
    amount,
  });
});
Validator unbonding completed: {
  delegator: "0xabcd...",
  validator: "0x1234...",
  originBlockHeight: 36975999n,
  amount: 100000000000000000000n
}

기록 쿼리

dApp이 과거 언본딩 완료 기록을 표시해야 하는 경우 블록 범위가 있는 이벤트 필터를 사용하여 기록 이벤트를 쿼리합니다.

// queryHistory.ts
import { ethers } from "ethers";
import { provider, stableSystem } from "./config";
 
async function getUnbondingHistory(
  userAddress: string,
  fromBlock: number,
  toBlock: number
) {
  const filter = stableSystem.filters.UnbondingCompleted(userAddress);
  const events = await stableSystem.queryFilter(filter, fromBlock, toBlock);
 
  return events.map((event) => ({
    delegator: event.args.delegator,
    validator: event.args.validator,
    originBlockHeight: event.args.originBlockHeight,
    amount: ethers.formatEther(event.args.amount),
    blockNumber: event.blockNumber,
    txHash: event.transactionHash,
  }));
}
 
const currentBlock = await provider.getBlockNumber();
const history = await getUnbondingHistory(
  "0xabcd...",
  currentBlock - 1000,
  currentBlock
);
 
console.log(history);
[
  {
    delegator: "0xabcd...",
    validator: "0x1234...",
    originBlockHeight: 36975999n,
    amount: "100.0",
    blockNumber: 36976000,
    txHash: "0x12ab..."
  }
]

3단계: 연결 문제 처리

이벤트 구독은 지속적인 WebSocket 연결에 의존합니다. 아래 예시는 오류 및 깔끔한 종료 후 재연결됩니다. 재시도하기 전에 이전 리스너를 제거하고 이전 공급자를 닫습니다. Node.js 22 및 최신 브라우저의 내장 WebSocket을 사용합니다.

// subscribeWithReconnection.ts
import { ethers } from "ethers";
import { STABLE_SYSTEM_ADDRESS, STABLE_SYSTEM_ABI } from "./config";
 
let reconnectAttempts = 0;
const MAX_RECONNECT_ATTEMPTS = 5;
const WS_URL = "wss://rpc.testnet.stable.xyz";
 
type Connection = {
  socket: WebSocket;
  provider: ethers.WebSocketProvider;
  stableSystem: ethers.Contract;
};
 
let activeConnection: Connection | undefined;
let reconnectTimer: ReturnType<typeof setTimeout> | undefined;
let stopped = false;
 
function handleUnbonding(
  delegator: string,
  validator: string,
  originBlockHeight: bigint,
  amount: bigint
) {
  console.log("Unbonding completed:", {
    delegator,
    validator,
    originBlockHeight,
    amount,
  });
}
 
async function closeConnection(connection: Connection) {
  if (activeConnection === connection) activeConnection = undefined;
  try {
    await connection.stableSystem.removeAllListeners();
  } finally {
    await connection.provider.destroy();
  }
}
 
async function scheduleReconnect(connection: Connection, reason: string) {
  if (stopped || activeConnection !== connection || reconnectTimer) return;
 
  console.warn(`WebSocket ${reason}; reconnecting.`);
  await closeConnection(connection);
 
  if (stopped) return;
 
  if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
    console.error("Maximum reconnect attempts reached.");
    return;
  }
 
  const delay = Math.min(1000 * 2 ** reconnectAttempts, 30_000);
  reconnectAttempts++;
  reconnectTimer = setTimeout(() => {
    reconnectTimer = undefined;
    setupEventListener();
  }, delay);
}
 
function setupEventListener() {
  const socket = new WebSocket(WS_URL);
  const provider = new ethers.WebSocketProvider(socket);
  const stableSystem = new ethers.Contract(
    STABLE_SYSTEM_ADDRESS,
    STABLE_SYSTEM_ABI,
    provider
  );
  const connection = { socket, provider, stableSystem };
  activeConnection = connection;
 
  socket.addEventListener("open", () => {
    if (activeConnection === connection) {
      reconnectAttempts = 0;
      console.log("Connected to Stable Testnet WebSocket.");
    }
  }, { once: true });
 
  socket.addEventListener("close", (event) => {
    void scheduleReconnect(connection, `closed with code ${event.code}`);
  }, { once: true });
 
  socket.addEventListener("error", () => {
    void scheduleReconnect(connection, "reported an error");
  }, { once: true });
 
  void stableSystem.on("UnbondingCompleted", handleUnbonding).catch((error) => {
    console.error("Subscription failed:", error);
    void scheduleReconnect(connection, "subscription failed");
  });
}
 
setupEventListener();
 
export async function stopEventListener() {
  stopped = true;
  if (reconnectTimer) clearTimeout(reconnectTimer);
  if (activeConnection) await closeConnection(activeConnection);
}
Connected to Stable Testnet WebSocket.

다음 단계