索引合约事件
索引将链上事件转换为你的应用程序可以响应的数据:余额更新、交易历史、UI 通知。本指南介绍如何使用 ethers.js 订阅已部署 Stable 合约的事件,以及如何回填历史事件,以便你的服务脱机时不会错过任何发出的事件。
前提条件
1. 安装和配置
npm install ethers// config.ts
import { ethers } from "ethers";
export const STABLE_TESTNET_RPC = "https://rpc.testnet.stable.xyz";
export const STABLE_TESTNET_WS = "wss://rpc.testnet.stable.xyz";
export const CONTRACT_ADDRESS = "0xDeployedContractAddress";
// 最小 ABI:仅包含你想要索引的事件。
export const CONTRACT_ABI = [
"event NumberUpdated(address indexed caller, uint256 oldValue, uint256 newValue)",
];2. 订阅实时事件
使用 WebSocket 提供程序,以便在验证者最终确定每个区块后立即接收事件。WebSocket 避免了轮询开销,并将通知延迟保持在接近区块时间(Stable 上约为 0.7 秒)。
// watchLive.ts
import { ethers } from "ethers";
import { STABLE_TESTNET_WS, CONTRACT_ADDRESS, CONTRACT_ABI } from "./config";
const provider = new ethers.WebSocketProvider(STABLE_TESTNET_WS);
const contract = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, provider);
contract.on("NumberUpdated", (caller, oldValue, newValue, event) => {
console.log("NumberUpdated:");
console.log(" caller: ", caller);
console.log(" oldValue: ", oldValue.toString());
console.log(" newValue: ", newValue.toString());
console.log(" tx: ", event.log.transactionHash);
console.log(" block: ", event.log.blockNumber);
});
console.log("Listening for NumberUpdated events...");npx tsx watchLive.tsListening for NumberUpdated events...
NumberUpdated:
caller: 0x1234...abcd
oldValue: 0
newValue: 42
tx: 0x8f3a...2d41
block: 1284371事件会随着调用者调用你的合约而实时到达。
3. 回填历史事件
当服务启动时,通常需要追溯其脱机期间发出的事件。使用带有块范围的 queryFilter。
// backfill.ts
import { ethers } from "ethers";
import { STABLE_TESTNET_RPC, CONTRACT_ADDRESS, CONTRACT_ABI } from "./config";
const provider = new ethers.JsonRpcProvider(STABLE_TESTNET_RPC);
const contract = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, provider);
const latest = await provider.getBlockNumber();
const fromBlock = Math.max(0, latest - 10_000); // 最后约 1万 个区块
const events = await contract.queryFilter(
contract.filters.NumberUpdated(),
fromBlock,
latest
);
for (const event of events) {
console.log(
`[block ${event.blockNumber}]`,
event.args.caller,
"set number to",
event.args.newValue.toString()
);
}
console.log(`Backfilled ${events.length} events from block ${fromBlock} to ${latest}`);npx tsx backfill.ts[block 1282351] 0x1234...abcd set number to 10
[block 1283092] 0xef01...2345 set number to 25
[block 1284371] 0x1234...abcd set number to 42
Backfilled 3 events from block 1282351 to 12843714. 按索引参数筛选事件
带有 indexed 参数(如上文的 caller)的事件可以在服务器端进行筛选。传递筛选值,而不是读取每个事件并在你的应用程序中进行筛选。
// watchUser.ts
import { ethers } from "ethers";
import { STABLE_TESTNET_WS, CONTRACT_ADDRESS, CONTRACT_ABI } from "./config";
const provider = new ethers.WebSocketProvider(STABLE_TESTNET_WS);
const contract = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, provider);
const userAddress = "0x1234...abcd";
const filter = contract.filters.NumberUpdated(userAddress);
contract.on(filter, (caller, oldValue, newValue, event) => {
console.log(`${caller} set number to ${newValue.toString()}`);
});
console.log(`Watching NumberUpdated for ${userAddress}...`);npx tsx watchUser.tsWatching NumberUpdated for 0x1234...abcd...
0x1234...abcd set number to 42处理连接断开
WebSocket 连接可能会断开。对于生产索引器,请实现重新连接逻辑,以免错过事件。
// resilientWatch.ts
import { ethers } from "ethers";
import { STABLE_TESTNET_WS, CONTRACT_ADDRESS, CONTRACT_ABI } from "./config";
let reconnectAttempts = 0;
const MAX_RECONNECT = 5;
function setupWatcher() {
const provider = new ethers.WebSocketProvider(STABLE_TESTNET_WS);
const contract = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, provider);
contract.on("NumberUpdated", (caller, oldValue, newValue) => {
console.log(`${caller} set number to ${newValue.toString()}`);
});
provider.websocket.onerror = (err: any) => {
console.error("Provider error:", err);
if (reconnectAttempts < MAX_RECONNECT) {
reconnectAttempts++;
setTimeout(setupWatcher, 5000);
}
};
}
setupWatcher();接下来建议
- 跟踪解绑完成情况:索引协议发出的系统交易事件(解绑完成情况)。
- 构建 P2P 支付应用程序:应用索引到 USDT0 Transfer 事件并构建支付历史视图。
- JSON-RPC 参考:查看 Stable 支持哪些
eth_getLogs和相关方法。

