跟踪解除绑定完成情况
解除绑定期完成后,协议会通过 StableSystem 预编译 (0x0000000000000000000000000000000000009999),经由系统交易发出 UnbondingCompleted 事件。这允许 dApp 通知用户并实时更新余额,而无需运行自定义索引器或轮询 REST 端点。
前提条件
概述
- 设置合约实例:为 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.
