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
);
No output. The StableSystem contract instance is ready to query or subscribe.

第 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.

接下来去哪里