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

索引合约事件

索引将链上事件转换为你的应用程序可以响应的数据:余额更新、交易历史、UI 通知。本指南介绍如何使用 ethers.js 订阅已部署 Stable 合约的事件,以及如何回填历史事件,以便你的服务脱机时不会错过任何发出的事件。

前提条件

  • 在 Stable 测试网或主网上部署的合约。如果需要,请参阅部署验证
  • Node.js 20 或更高版本。
  • 合约地址和你想要索引的事件的 ABI。

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.ts
Listening 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 1284371

4. 按索引参数筛选事件

带有 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.ts
Watching 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();

接下来建议