> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stablenet.network/llms.txt
> Use this file to discover all available pages before exploring further.

# 온체인 이벤트 구독하기

> WebSocket으로 StableNet 실시간 이벤트를 구독하고 eth_getLogs로 과거 로그를 조회합니다.

> WebSocket 구독과 로그 쿼리를 사용해 StableNet에서 새 블록과 컨트랙트 이벤트를 실시간으로 수신합니다.

## 배우는 내용

이 튜토리얼을 완료하면 다음을 할 수 있습니다:

* WebSocket으로 새 블록 헤더 구독
* 토픽 필터에 맞는 컨트랙트 이벤트 구독
* `eth_getLogs`로 컨트랙트 과거 로그 조회

## 사전 준비

* Node.js ≥ 18
* `npm install ethers` (v6)
* 이벤트를 발생시키는 배포된 컨트랙트 (예: [Foundry로 배포하기](/ko/v0.3/build/tutorials/deploy-with-foundry)의 `WKRCPayment` 컨트랙트)

## WebSocket으로 연결

`eth_subscribe`는 지속적인 WebSocket 연결이 필요합니다. HTTP 엔드포인트는 구독을 지원하지 않습니다.

```typescript theme={null}
import { ethers } from "ethers";

const wsProvider = new ethers.WebSocketProvider(
  "wss://api.test.stablenet.network/ws"
);
```

## 새 블록 헤더 구독

새 블록이 생성될 때마다 헤더 이벤트가 발생합니다. 체인 진행 상황 추적이나 폴링 로직 트리거에 활용합니다.

```typescript theme={null}
wsProvider.on("block", async (blockNumber) => {
  const block = await wsProvider.getBlock(blockNumber);
  console.log(`Block ${block.number} — hash: ${block.hash}`);
});
```

```text theme={null}
Block 1234567 — hash: 0xabc...
Block 1234568 — hash: 0xdef...
```

## 컨트랙트 이벤트 구독

특정 컨트랙트의 이벤트를 수신하려면 컨트랙트의 ABI와 주소로 `ethers.Contract` 인스턴스를 생성합니다. 아래 예시는 `WKRCPayment` 컨트랙트의 `Payment` 이벤트를 구독합니다.

```typescript theme={null}
const CONTRACT_ADDRESS = "0xYourDeployedContract";
const ABI = [
  "event Payment(address indexed from, address indexed to, uint256 amount)"
];

const contract = new ethers.Contract(CONTRACT_ADDRESS, ABI, wsProvider);

contract.on("Payment", (from, to, amount, event) => {
  console.log(`Payment from ${from} to ${to}: ${ethers.formatEther(amount)} WKRC`);
  console.log(`  tx: ${event.log.transactionHash}`);
});
```

```text theme={null}
Payment from 0xSender to 0xRecipient: 1.0 WKRC
  tx: 0xabc...
```

## 토픽으로 이벤트 필터링

특정 발신자의 이벤트만 수신하려면 토픽 필터를 추가합니다. EVM 토픽 필터에서 `null`은 와일드카드입니다.

```typescript theme={null}
// 특정 주소가 보낸 결제만 수신
const filter = contract.filters.Payment("0xSpecificSender");
contract.on(filter, (from, to, amount) => {
  console.log(`Filtered payment: ${ethers.formatEther(amount)} WKRC`);
});
```

## eth\_getLogs로 과거 로그 조회

지속적인 연결 없이 과거 이벤트를 조회하려면 `eth_getLogs`를 사용합니다. 블록 범위와 토픽 필터를 지정합니다.

```typescript theme={null}
const httpProvider = new ethers.JsonRpcProvider(
  "https://api.test.stablenet.network"
);

const logs = await httpProvider.getLogs({
  address: CONTRACT_ADDRESS,
  fromBlock: 1200000,
  toBlock: "latest",
  topics: [
    ethers.id("Payment(address,address,uint256)")  // 이벤트 시그니처의 keccak256
  ],
});

for (const log of logs) {
  const parsed = contract.interface.parseLog(log);
  console.log(
    `Block ${log.blockNumber}: ${ethers.formatEther(parsed.args.amount)} WKRC`
  );
}
```

```text theme={null}
Block 1234500: 1.0 WKRC
Block 1234512: 0.5 WKRC
```

## 구독 정리

더 이상 필요하지 않을 때 이벤트 리스너를 제거해 메모리 누수를 방지합니다.

```typescript theme={null}
// 특정 리스너 제거
contract.off("Payment", listenerFunction);

// 이벤트의 모든 리스너 제거
contract.removeAllListeners("Payment");

// WebSocket 연결 종료
await wsProvider.destroy();
```

## 토픽 매칭 규칙

토픽은 indexed 이벤트 파라미터에 매핑됩니다. 한 위치에 여러 값을 지정하면 OR 조건, 값이 없는 위치는 와일드카드입니다.

| 위치            | 의미                         |
| ------------- | -------------------------- |
| `topics[0]`   | 이벤트 시그니처 해시 (항상 필수)        |
| `topics[1]`   | 첫 번째 indexed 파라미터 (`from`) |
| `topics[2]`   | 두 번째 indexed 파라미터 (`to`)   |
| 어떤 위치든 `null` | 모든 값에 매칭                   |

최대 4개의 토픽 위치를 지원합니다.

## 다음 단계

<CardGroup cols={2}>
  <Card title="Foundry로 배포하기" href="/ko/v0.3/build/tutorials/deploy-with-foundry">이벤트를 발생시키는 컨트랙트를 배포합니다.</Card>
  <Card title="RPC API 레퍼런스" href="/ko/v0.3/reference/rpc-api">eth\_subscribe, eth\_getLogs, 필터 메서드 전체 레퍼런스.</Card>
  <Card title="온체인 데이터 읽기" href="/ko/v0.3/read-onchain-data">잔액, 스토리지 슬롯, 컨트랙트 상태를 조회합니다.</Card>
  <Card title="수수료 위임" href="/ko/v0.3/build/tutorials/fee-delegation">별도 계정이 사용자 대신 가스를 납부하게 합니다.</Card>
</CardGroup>
