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

<AgentInstructions>

## Submitting Feedback

If you encounter incorrect, outdated, or confusing documentation on this page, submit feedback:

POST https://docs.chainstack.com/feedback

```json
{
  "path": "/docs/monad-tutorial-event-monitoring",
  "feedback": "Description of the issue"
}
```

Only submit feedback when you have something specific and actionable to report.

</AgentInstructions>

# Monad: Monitoring events and transactions

> Build real-time smart contract event monitoring on Monad using ethers.js and web3.py with eth_subscribe WebSocket streaming and eth_getLogs polling.

This tutorial teaches you how to monitor on-chain events and transactions on Monad. You'll use WebSocket subscriptions (`eth_subscribe`) for real-time streaming, with HTTP polling via `eth_getLogs` as an alternative pattern for environments where WebSocket isn't an option.

<Check>
  **Get your own node endpoint today**

  [Start for free](https://console.chainstack.com/) and get your app to production levels immediately. No credit card required.

  You can sign up with your GitHub, X, Google, or Microsoft account.
</Check>

**TLDR:**

* Stream real-time events with `eth_subscribe("logs", {...})` over WebSocket
* Stream new blocks with `eth_subscribe("newHeads")`
* Alternative: query logs over HTTP with `eth_getLogs`
* Monitor ERC-20 Transfer events on WMON (Wrapped MON)
* Handle Monad's high-throughput blocks efficiently
* Both JavaScript and Python implementations

## Prerequisites

* [Chainstack account](https://console.chainstack.com/) with a Monad node endpoint
* Node.js v16+ or Python 3.8+
* Basic understanding of Ethereum events and logs

## Overview

Event monitoring on Monad differs from Ethereum in a few ways:

* **WebSocket subscriptions** — `eth_subscribe("newHeads")` and `eth_subscribe("logs", {...})` are supported for real-time streaming. `eth_subscribe("newPendingTransactions")` and `eth_subscribe("syncing")` are not supported and return `-32602 Invalid params`.
* **1-second blocks** — events appear roughly every second
* **High transaction volume** — blocks contain many transactions, so when using `eth_getLogs`, query small block ranges
* **Immediate finality** — no need to wait for confirmations or handle reorgs

This tutorial shows you how to build efficient monitoring tools that work with Monad's architecture.

<Note>
  Use your Chainstack node's WSS endpoint (`wss://…`) for subscription examples. See [Node access and credentials](/docs/manage-your-node#view-node-access-and-credentials).
</Note>

## Understanding Monad's event system

Events (logs) on Monad work the same as Ethereum:

1. Smart contracts emit events during execution
2. Events are stored in transaction receipts
3. You stream events in real time with `eth_subscribe("logs", {...})` over WebSocket, or query historical events with `eth_getLogs` over HTTP

Pick the pattern that fits your use case:

* **Real-time streaming** — use `eth_subscribe` over WebSocket. Lower latency, no polling loop, no missed blocks
* **Historical queries or HTTP-only environments** — use `eth_getLogs`. Use small block ranges (1–10 blocks) per query, and process logs immediately since they're final

## Stream events in real time with `eth_subscribe`

`eth_subscribe("logs", {...})` streams matching events to you over a WebSocket connection as soon as a block containing them lands. No polling, no missed blocks, no extra request budget burned on idle intervals.

### JavaScript — subscribe to WMON Transfers

```javascript subscribe-wmon.js theme={"system"}
const { ethers } = require("ethers");

const CHAINSTACK_WSS_ENDPOINT = "YOUR_CHAINSTACK_WSS_ENDPOINT";
const WMON_ADDRESS = "0x760AfE86e5de5fa0Ee542fc7B7B713e1c5425701";
const TRANSFER_TOPIC = ethers.id("Transfer(address,address,uint256)");

const provider = new ethers.WebSocketProvider(CHAINSTACK_WSS_ENDPOINT);

function decodeTransfer(log) {
  const from = "0x" + log.topics[1].slice(26);
  const to = "0x" + log.topics[2].slice(26);
  const value = BigInt(log.data);
  return {
    from,
    to,
    value: ethers.formatEther(value),
    blockNumber: log.blockNumber,
    transactionHash: log.transactionHash,
  };
}

async function subscribe() {
  console.log(`Subscribing to WMON Transfer events at ${WMON_ADDRESS}`);

  const filter = {
    address: WMON_ADDRESS,
    topics: [TRANSFER_TOPIC],
  };

  provider.on(filter, (log) => {
    const t = decodeTransfer(log);
    console.log(`Block ${t.blockNumber}: ${t.value} WMON from ${t.from} to ${t.to}`);
    console.log(`  Tx: ${t.transactionHash}`);
  });
}

subscribe();
```

Run:

```bash theme={"system"}
node subscribe-wmon.js
```

Under the hood, `provider.on(filter, handler)` sends a raw `eth_subscribe` request:

```json theme={"system"}
{"jsonrpc":"2.0","id":1,"method":"eth_subscribe","params":["logs",{"address":"0x760AfE86e5de5fa0Ee542fc7B7B713e1c5425701","topics":["0xddf252ad..."]}]}
```

### Python — subscribe to WMON Transfers

```python subscribe_wmon.py theme={"system"}
import asyncio
import json
import websockets

CHAINSTACK_WSS_ENDPOINT = "YOUR_CHAINSTACK_WSS_ENDPOINT"
WMON_ADDRESS = "0x760AfE86e5de5fa0Ee542fc7B7B713e1c5425701"
# keccak256("Transfer(address,address,uint256)")
TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"

async def subscribe():
    async with websockets.connect(CHAINSTACK_WSS_ENDPOINT) as ws:
        await ws.send(json.dumps({
            "jsonrpc": "2.0",
            "id": 1,
            "method": "eth_subscribe",
            "params": ["logs", {
                "address": WMON_ADDRESS,
                "topics": [TRANSFER_TOPIC],
            }],
        }))
        sub = json.loads(await ws.recv())
        print(f"Subscribed: {sub['result']}")

        async for raw in ws:
            msg = json.loads(raw)
            if msg.get("method") != "eth_subscription":
                continue
            log = msg["params"]["result"]
            from_addr = "0x" + log["topics"][1][26:]
            to_addr = "0x" + log["topics"][2][26:]
            value_wei = int(log["data"], 16)
            value = value_wei / 10**18
            print(f"Block {int(log['blockNumber'], 16)}: {value} WMON "
                  f"from {from_addr} to {to_addr}")
            print(f"  Tx: {log['transactionHash']}")

asyncio.run(subscribe())
```

Run:

```bash theme={"system"}
pip install websockets
python subscribe_wmon.py
```

### Subscribe to new block headers

Use `eth_subscribe("newHeads")` to react to every new block as it's produced:

```javascript subscribe-newheads.js theme={"system"}
const { ethers } = require("ethers");

const CHAINSTACK_WSS_ENDPOINT = "YOUR_CHAINSTACK_WSS_ENDPOINT";
const provider = new ethers.WebSocketProvider(CHAINSTACK_WSS_ENDPOINT);

provider.on("block", async (blockNumber) => {
  const block = await provider.getBlock(blockNumber);
  const utilization = ((Number(block.gasUsed) / Number(block.gasLimit)) * 100).toFixed(2);
  console.log(`Block ${blockNumber}: ${block.transactions.length} txs, gas ${utilization}%`);
});
```

## Alternative: poll with `eth_getLogs`

If you can't use WebSocket — for example, in a serverless function, a browser environment without persistent sockets, or a constrained runtime — poll `eth_getLogs` over HTTP instead.

### JavaScript polling implementation

```javascript monitor-wmon.js theme={"system"}
const { ethers } = require("ethers");

const CHAINSTACK_ENDPOINT = "YOUR_CHAINSTACK_MONAD_ENDPOINT";
const WMON_ADDRESS = "0x760AfE86e5de5fa0Ee542fc7B7B713e1c5425701";

// Transfer event signature: keccak256("Transfer(address,address,uint256)")
const TRANSFER_TOPIC = ethers.id("Transfer(address,address,uint256)");

const provider = new ethers.JsonRpcProvider(CHAINSTACK_ENDPOINT);

async function getTransferEvents(fromBlock, toBlock) {
  const filter = {
    address: WMON_ADDRESS,
    topics: [TRANSFER_TOPIC],
    fromBlock: fromBlock,
    toBlock: toBlock,
  };

  const logs = await provider.getLogs(filter);
  return logs;
}

function decodeTransferEvent(log) {
  // Transfer(address indexed from, address indexed to, uint256 value)
  const from = "0x" + log.topics[1].slice(26);
  const to = "0x" + log.topics[2].slice(26);
  const value = BigInt(log.data);

  return {
    from,
    to,
    value: ethers.formatEther(value),
    blockNumber: log.blockNumber,
    transactionHash: log.transactionHash,
    logIndex: log.index,
  };
}

async function monitorTransfers() {
  let lastBlock = await provider.getBlockNumber();
  console.log(`Starting WMON Transfer monitor at block ${lastBlock}`);
  console.log(`Contract: ${WMON_ADDRESS}\n`);

  // Poll every second
  setInterval(async () => {
    try {
      const currentBlock = await provider.getBlockNumber();

      if (currentBlock > lastBlock) {
        // Query new blocks
        const fromBlock = lastBlock + 1;
        const toBlock = currentBlock;

        console.log(`Checking blocks ${fromBlock} to ${toBlock}...`);

        const logs = await getTransferEvents(fromBlock, toBlock);

        if (logs.length > 0) {
          console.log(`Found ${logs.length} Transfer events:\n`);

          for (const log of logs) {
            const transfer = decodeTransferEvent(log);
            console.log(`Block ${transfer.blockNumber}:`);
            console.log(`  From: ${transfer.from}`);
            console.log(`  To:   ${transfer.to}`);
            console.log(`  Amount: ${transfer.value} WMON`);
            console.log(`  Tx: ${transfer.transactionHash}\n`);
          }
        }

        lastBlock = currentBlock;
      }
    } catch (error) {
      console.error("Error:", error.message);
    }
  }, 1000);
}

monitorTransfers();
```

Run:

```bash theme={"system"}
node monitor-wmon.js
```

### Python polling implementation

```python monitor_wmon.py theme={"system"}
from web3 import Web3
import time

CHAINSTACK_ENDPOINT = "YOUR_CHAINSTACK_MONAD_ENDPOINT"
WMON_ADDRESS = "0x760AfE86e5de5fa0Ee542fc7B7B713e1c5425701"

# Transfer event signature
TRANSFER_TOPIC = Web3.keccak(text="Transfer(address,address,uint256)").hex()

web3 = Web3(Web3.HTTPProvider(CHAINSTACK_ENDPOINT))
print(f"Connected: {web3.is_connected()}")

def get_transfer_events(from_block, to_block):
    """Fetch Transfer events in a block range."""
    filter_params = {
        'address': WMON_ADDRESS,
        'topics': [TRANSFER_TOPIC],
        'fromBlock': from_block,
        'toBlock': to_block
    }

    logs = web3.eth.get_logs(filter_params)
    return logs

def decode_transfer_event(log):
    """Decode a Transfer event log."""
    from_address = '0x' + log['topics'][1].hex()[26:]
    to_address = '0x' + log['topics'][2].hex()[26:]
    value = int(log['data'].hex(), 16)

    return {
        'from': Web3.to_checksum_address(from_address),
        'to': Web3.to_checksum_address(to_address),
        'value': web3.from_wei(value, 'ether'),
        'block_number': log['blockNumber'],
        'transaction_hash': log['transactionHash'].hex(),
        'log_index': log['logIndex']
    }

def monitor_transfers():
    """Monitor WMON Transfer events continuously."""
    last_block = web3.eth.block_number
    print(f"Starting WMON Transfer monitor at block {last_block}")
    print(f"Contract: {WMON_ADDRESS}\n")

    while True:
        try:
            current_block = web3.eth.block_number

            if current_block > last_block:
                from_block = last_block + 1
                to_block = current_block

                print(f"Checking blocks {from_block} to {to_block}...")

                logs = get_transfer_events(from_block, to_block)

                if logs:
                    print(f"Found {len(logs)} Transfer events:\n")

                    for log in logs:
                        transfer = decode_transfer_event(log)
                        print(f"Block {transfer['block_number']}:")
                        print(f"  From: {transfer['from']}")
                        print(f"  To:   {transfer['to']}")
                        print(f"  Amount: {transfer['value']} WMON")
                        print(f"  Tx: {transfer['transaction_hash']}\n")

                last_block = current_block

            # Poll every second
            time.sleep(1)

        except Exception as e:
            print(f"Error: {e}")
            time.sleep(1)

if __name__ == "__main__":
    monitor_transfers()
```

Run:

```bash theme={"system"}
python monitor_wmon.py
```

## Build a block monitor with polling

Monitor all transactions in new blocks over HTTP. For a WebSocket equivalent, see the `eth_subscribe("newHeads")` example above.

### JavaScript block monitor

```javascript monitor-blocks.js theme={"system"}
const { ethers } = require("ethers");

const CHAINSTACK_ENDPOINT = "YOUR_CHAINSTACK_MONAD_ENDPOINT";
const provider = new ethers.JsonRpcProvider(CHAINSTACK_ENDPOINT);

async function processBlock(blockNumber) {
  const block = await provider.getBlock(blockNumber, true); // true = include transactions

  const timestamp = new Date(block.timestamp * 1000).toISOString();
  const gasUsed = block.gasUsed.toString();
  const gasLimit = block.gasLimit.toString();
  const utilization = ((Number(block.gasUsed) / Number(block.gasLimit)) * 100).toFixed(2);

  console.log(`\n=== Block ${blockNumber} ===`);
  console.log(`Timestamp: ${timestamp}`);
  console.log(`Transactions: ${block.transactions.length}`);
  console.log(`Gas used: ${gasUsed} / ${gasLimit} (${utilization}%)`);
  console.log(`Miner: ${block.miner}`);

  // Process individual transactions if needed
  if (block.prefetchedTransactions && block.prefetchedTransactions.length > 0) {
    // Show first 5 transactions
    const txs = block.prefetchedTransactions.slice(0, 5);
    console.log(`\nFirst ${txs.length} transactions:`);

    for (const tx of txs) {
      const value = ethers.formatEther(tx.value);
      console.log(`  ${tx.hash.slice(0, 18)}... | ${tx.from.slice(0, 10)}... -> ${tx.to?.slice(0, 10) || 'Contract creation'}... | ${value} MON`);
    }

    if (block.prefetchedTransactions.length > 5) {
      console.log(`  ... and ${block.prefetchedTransactions.length - 5} more`);
    }
  }
}

async function monitorBlocks() {
  let lastBlock = await provider.getBlockNumber();
  console.log(`Starting block monitor at block ${lastBlock}`);

  setInterval(async () => {
    try {
      const currentBlock = await provider.getBlockNumber();

      if (currentBlock > lastBlock) {
        for (let i = lastBlock + 1; i <= currentBlock; i++) {
          await processBlock(i);
        }
        lastBlock = currentBlock;
      }
    } catch (error) {
      console.error("Error:", error.message);
    }
  }, 1000);
}

monitorBlocks();
```

### Python block monitor

```python monitor_blocks.py theme={"system"}
from web3 import Web3
import time
from datetime import datetime

CHAINSTACK_ENDPOINT = "YOUR_CHAINSTACK_MONAD_ENDPOINT"
web3 = Web3(Web3.HTTPProvider(CHAINSTACK_ENDPOINT))

def process_block(block_number):
    """Process and display block information."""
    block = web3.eth.get_block(block_number, full_transactions=True)

    timestamp = datetime.fromtimestamp(block.timestamp).isoformat()
    gas_used = block.gasUsed
    gas_limit = block.gasLimit
    utilization = (gas_used / gas_limit) * 100

    print(f"\n=== Block {block_number} ===")
    print(f"Timestamp: {timestamp}")
    print(f"Transactions: {len(block.transactions)}")
    print(f"Gas used: {gas_used:,} / {gas_limit:,} ({utilization:.2f}%)")
    print(f"Miner: {block.miner}")

    # Show first 5 transactions
    if block.transactions:
        txs = block.transactions[:5]
        print(f"\nFirst {len(txs)} transactions:")

        for tx in txs:
            value = web3.from_wei(tx.value, 'ether')
            to_addr = tx.to[:10] + '...' if tx.to else 'Contract creation'
            print(f"  {tx.hash.hex()[:18]}... | {tx['from'][:10]}... -> {to_addr} | {value:.4f} MON")

        if len(block.transactions) > 5:
            print(f"  ... and {len(block.transactions) - 5} more")

def monitor_blocks():
    """Monitor new blocks continuously."""
    last_block = web3.eth.block_number
    print(f"Starting block monitor at block {last_block}")

    while True:
        try:
            current_block = web3.eth.block_number

            if current_block > last_block:
                for block_num in range(last_block + 1, current_block + 1):
                    process_block(block_num)
                last_block = current_block

            time.sleep(1)

        except Exception as e:
            print(f"Error: {e}")
            time.sleep(1)

if __name__ == "__main__":
    monitor_blocks()
```

## Monitor custom contract events

Track events from any contract by defining the event signature:

```javascript monitor-custom.js theme={"system"}
const { ethers } = require("ethers");

const CHAINSTACK_ENDPOINT = "YOUR_CHAINSTACK_MONAD_ENDPOINT";
const provider = new ethers.JsonRpcProvider(CHAINSTACK_ENDPOINT);

// Example: Monitor any ERC-20 Approval events
const APPROVAL_TOPIC = ethers.id("Approval(address,address,uint256)");

// Or monitor a specific contract
const CONTRACT_ADDRESS = "YOUR_CONTRACT_ADDRESS";
const CUSTOM_EVENT_TOPIC = ethers.id("YourEvent(address,uint256)");

async function monitorEvents(address, topics, eventName) {
  let lastBlock = await provider.getBlockNumber();
  console.log(`Monitoring ${eventName} events from block ${lastBlock}`);

  setInterval(async () => {
    try {
      const currentBlock = await provider.getBlockNumber();

      if (currentBlock > lastBlock) {
        const filter = {
          address: address, // null for all contracts
          topics: topics,
          fromBlock: lastBlock + 1,
          toBlock: currentBlock,
        };

        const logs = await provider.getLogs(filter);

        if (logs.length > 0) {
          console.log(`\nFound ${logs.length} ${eventName} events:`);
          for (const log of logs) {
            console.log(`  Block ${log.blockNumber}: ${log.transactionHash}`);
          }
        }

        lastBlock = currentBlock;
      }
    } catch (error) {
      console.error("Error:", error.message);
    }
  }, 1000);
}

// Monitor all ERC-20 Approvals on Monad
monitorEvents(null, [APPROVAL_TOPIC], "Approval");
```

## Filter events by address

Monitor transfers to or from a specific address:

```javascript monitor-address.js theme={"system"}
const { ethers } = require("ethers");

const CHAINSTACK_ENDPOINT = "YOUR_CHAINSTACK_MONAD_ENDPOINT";
const WMON_ADDRESS = "0x760AfE86e5de5fa0Ee542fc7B7B713e1c5425701";
const WATCH_ADDRESS = "YOUR_ADDRESS_TO_WATCH";

const TRANSFER_TOPIC = ethers.id("Transfer(address,address,uint256)");

const provider = new ethers.JsonRpcProvider(CHAINSTACK_ENDPOINT);

async function monitorAddressTransfers(address) {
  let lastBlock = await provider.getBlockNumber();

  // Pad address to 32 bytes for topic matching
  const paddedAddress = ethers.zeroPadValue(address, 32);

  console.log(`Monitoring WMON transfers for ${address}`);
  console.log(`Starting at block ${lastBlock}\n`);

  setInterval(async () => {
    try {
      const currentBlock = await provider.getBlockNumber();

      if (currentBlock > lastBlock) {
        // Incoming transfers (address is 'to')
        const incomingFilter = {
          address: WMON_ADDRESS,
          topics: [TRANSFER_TOPIC, null, paddedAddress],
          fromBlock: lastBlock + 1,
          toBlock: currentBlock,
        };

        // Outgoing transfers (address is 'from')
        const outgoingFilter = {
          address: WMON_ADDRESS,
          topics: [TRANSFER_TOPIC, paddedAddress, null],
          fromBlock: lastBlock + 1,
          toBlock: currentBlock,
        };

        const [incomingLogs, outgoingLogs] = await Promise.all([
          provider.getLogs(incomingFilter),
          provider.getLogs(outgoingFilter),
        ]);

        for (const log of incomingLogs) {
          const from = "0x" + log.topics[1].slice(26);
          const value = ethers.formatEther(BigInt(log.data));
          console.log(`📥 INCOMING: ${value} WMON from ${from}`);
          console.log(`   Tx: ${log.transactionHash}\n`);
        }

        for (const log of outgoingLogs) {
          const to = "0x" + log.topics[2].slice(26);
          const value = ethers.formatEther(BigInt(log.data));
          console.log(`📤 OUTGOING: ${value} WMON to ${to}`);
          console.log(`   Tx: ${log.transactionHash}\n`);
        }

        lastBlock = currentBlock;
      }
    } catch (error) {
      console.error("Error:", error.message);
    }
  }, 1000);
}

monitorAddressTransfers(WATCH_ADDRESS);
```

## Best practices for Monad

<Note>
  **Optimize your monitoring for Monad's characteristics:**

  1. **Prefer `eth_subscribe` over polling** — WebSocket subscriptions are lower latency and don't burn request budget on idle intervals. Reach for `eth_getLogs` polling only when WebSocket isn't an option.

  2. **Use small block ranges when polling** — query 1–10 blocks at a time. Monad blocks can contain thousands of transactions.

  3. **Match polling cadence to block time** — Monad produces blocks every \~1 second. Polling faster wastes requests; slower misses events.

  4. **Handle high volume** — be prepared for blocks with many events. Process asynchronously if needed.

  5. **No reorg handling needed** — Monad has instant finality. Once you see an event, it's permanent.

  6. **Batch your queries** — if monitoring multiple contracts, combine filters where possible.
</Note>

<Warning>
  **`eth_getLogs` limitations:**

  * Most nodes limit the block range per query (typically 1,000–10,000 blocks)
  * For historical data, paginate through block ranges
  * For real-time monitoring, stick to recent blocks only — or use `eth_subscribe("logs", {...})` instead
</Warning>

## Monad-specific notes

<Note>
  **Key differences from Ethereum monitoring:**

  * **`eth_subscribe` subset** — `newHeads` and `logs` are supported. `eth_subscribe("newPendingTransactions")` and `eth_subscribe("syncing")` are not supported on Monad and return `-32602 Invalid params`.
  * **1-second blocks** — events appear faster than on Ethereum. Your monitor needs to keep up.
  * **No pending transactions** — you can't monitor the mempool. Only confirmed transactions are visible.
  * **Immediate finality** — no need to wait for confirmations. Events are final when you see them.
  * **High throughput** — expect more events per block than on Ethereum. Design accordingly.
</Note>

## Complete monitoring script

A production-ready monitor combining all techniques:

```javascript monitor-complete.js theme={"system"}
const { ethers } = require("ethers");

const CHAINSTACK_ENDPOINT = "YOUR_CHAINSTACK_MONAD_ENDPOINT";
const provider = new ethers.JsonRpcProvider(CHAINSTACK_ENDPOINT);

class MonadEventMonitor {
  constructor(config) {
    this.lastBlock = 0;
    this.config = config;
    this.isRunning = false;
  }

  async start() {
    this.lastBlock = await provider.getBlockNumber();
    this.isRunning = true;
    console.log(`Monitor started at block ${this.lastBlock}`);
    this.poll();
  }

  stop() {
    this.isRunning = false;
    console.log("Monitor stopped");
  }

  async poll() {
    while (this.isRunning) {
      try {
        const currentBlock = await provider.getBlockNumber();

        if (currentBlock > this.lastBlock) {
          await this.processNewBlocks(this.lastBlock + 1, currentBlock);
          this.lastBlock = currentBlock;
        }

        await this.sleep(1000);
      } catch (error) {
        console.error("Poll error:", error.message);
        await this.sleep(1000);
      }
    }
  }

  async processNewBlocks(fromBlock, toBlock) {
    for (const filter of this.config.filters) {
      const filterWithBlocks = {
        ...filter.params,
        fromBlock,
        toBlock,
      };

      const logs = await provider.getLogs(filterWithBlocks);

      if (logs.length > 0) {
        filter.handler(logs);
      }
    }
  }

  sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
}

// Example usage
const WMON = "0x760AfE86e5de5fa0Ee542fc7B7B713e1c5425701";
const TRANSFER = ethers.id("Transfer(address,address,uint256)");

const monitor = new MonadEventMonitor({
  filters: [
    {
      name: "WMON Transfers",
      params: {
        address: WMON,
        topics: [TRANSFER],
      },
      handler: (logs) => {
        console.log(`\n📊 ${logs.length} WMON transfers detected`);
        for (const log of logs.slice(0, 3)) {
          const value = ethers.formatEther(BigInt(log.data));
          console.log(`  ${value} WMON in block ${log.blockNumber}`);
        }
        if (logs.length > 3) {
          console.log(`  ... and ${logs.length - 3} more`);
        }
      },
    },
  ],
});

monitor.start();

// Stop after 60 seconds (for demo)
// setTimeout(() => monitor.stop(), 60000);
```

## Next steps

Now that you can monitor events on Monad, you can:

* Build real-time dashboards for DEX activity
* Create alerting systems for large transfers
* Track NFT mints and sales
* Monitor your own smart contract events
* Build analytics pipelines for on-chain data
