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";
 
// Minimal ABI: only the events you want to index.
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); // 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 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();

다음 추천

  • 언본딩 완료 추적 — 프로토콜에서 발생하는 시스템 트랜잭션 이벤트(언본딩 완료)를 인덱싱합니다.
  • P2P 결제 앱 구축 — USDT0 Transfer 이벤트에 인덱싱을 적용하고 결제 내역 화면을 구축합니다.
  • JSON-RPC 레퍼런스 — Stable이 지원하는 eth_getLogs 및 관련 메서드를 확인합니다.