索引合约事件
索引将链上事件转化为应用程序可以响应的数据:余额更新、交易历史、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";
// Minimal ABI: only the events you want to index.
export const CONTRACT_ABI = [
"event NumberUpdated(address indexed caller, uint256 oldValue, uint256 newValue)",
];2. 订阅实时事件
使用 WebSocket provider,这样你就能在验证者最终确认每个区块后立即收到事件。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); // last ~10k blocks
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及相关方法。

