> ## Documentation Index
> Fetch the complete documentation index at: https://seilabs-docs-evm-cookbook.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Listen to events

> Stream contract events as they happen over a WebSocket subscription, and fetch past events with eth_getLogs, using viem, ethers, or web3.py.

Contracts emit events to record what happened, such as a token transfer. This recipe watches `Transfer` events from USDC on Sei Mainnet as they happen. Then it fetches past `Transfer` events. USDC is busy, so new events usually arrive within a minute.

## Pick the right endpoint

Sei's public endpoints split the work:

| Task | Endpoint | Sei Mainnet | Sei Testnet |
| - | - | - | - |
| Live events (`eth_subscribe`) | WebSocket | `wss://evm-ws.sei-apis.com` | `wss://evm-ws-testnet.sei-apis.com` |
| Past events (`eth_getLogs`) | HTTP | `https://evm-rpc.sei-apis.com` | `https://evm-rpc-testnet.sei-apis.com` |

<Warning>Watch live events over WebSocket. The public WebSocket endpoints do not serve `eth_getLogs`, so fetch past events over HTTP. Watchers that poll the public HTTP endpoints for new events can miss events or report them twice.</Warning>

## Install

<CodeGroup>
  ```bash viem theme={null}
  npm install viem
  ```

  ```bash ethers theme={null}
  npm install ethers
  ```

  ```bash web3.py theme={null}
  pip install web3
  ```
</CodeGroup>

## Watch new events

Each script prints a line for every USDC transfer. The script keeps running until you stop it with <kbd>Ctrl</kbd>+<kbd>C</kbd>.

<CodeGroup>
  ```ts viem theme={null}
  import { createPublicClient, webSocket, parseAbiItem, formatUnits } from 'viem';
  import { sei } from 'viem/chains';

  const client = createPublicClient({
    chain: sei,
    transport: webSocket('wss://evm-ws.sei-apis.com'),
  });

  const USDC = '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392';

  const unwatch = client.watchEvent({
    address: USDC,
    event: parseAbiItem('event Transfer(address indexed from, address indexed to, uint256 value)'),
    strict: true,
    onLogs: (logs) => {
      for (const { args, blockNumber } of logs) {
        console.log(`${args.from} -> ${args.to}: ${formatUnits(args.value, 6)} USDC (block ${blockNumber})`);
      }
    },
    onError: (error) => console.error(error.message),
  });
  // Call unwatch() to stop the subscription
  ```

  ```ts ethers theme={null}
  import { ethers } from 'ethers';

  const provider = new ethers.WebSocketProvider('wss://evm-ws.sei-apis.com');

  const USDC = '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392';
  const usdc = new ethers.Contract(
    USDC,
    ['event Transfer(address indexed from, address indexed to, uint256 value)'],
    provider
  );

  await usdc.on('Transfer', (from, to, value, event) => {
    console.log(`${from} -> ${to}: ${ethers.formatUnits(value, 6)} USDC (block ${event.log.blockNumber})`);
  });
  // Call usdc.removeAllListeners() to stop the subscription
  ```

  ```python web3.py theme={null}
  import asyncio
  from decimal import Decimal
  from web3 import AsyncWeb3, WebSocketProvider
  from web3.utils.subscriptions import LogsSubscription

  USDC = "0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392"
  TRANSFER_ABI = [{
      "type": "event", "name": "Transfer", "anonymous": False,
      "inputs": [
          {"name": "from", "type": "address", "indexed": True},
          {"name": "to", "type": "address", "indexed": True},
          {"name": "value", "type": "uint256", "indexed": False},
      ],
  }]

  async def main():
      async with AsyncWeb3(WebSocketProvider("wss://evm-ws.sei-apis.com")) as w3:
          transfer = w3.eth.contract(address=USDC, abi=TRANSFER_ABI).events.Transfer()

          async def on_log(context):
              event = transfer.process_log(context.result)
              args = event["args"]
              amount = Decimal(args["value"]) / 10**6
              print(f"{args['from']} -> {args['to']}: {amount} USDC (block {event['blockNumber']})")

          await w3.subscription_manager.subscribe(
              LogsSubscription(address=USDC, topics=[transfer.topic], handler=on_log)
          )
          await w3.subscription_manager.handle_subscriptions()

  asyncio.run(main())
  ```
</CodeGroup>

**You are done when you see:**

```text theme={null}
0x3D20…CB04 -> 0x8397…62C4: 27.847524 USDC (block 235331015)
```

The scripts print the full sender and recipient addresses. They are shortened here. Sei has instant finality, so each event is final when it arrives. You do not need to wait for confirmations or handle reorganizations.

<Note>The web3.py example uses the subscription manager, which needs web3.py 7.7 or later.</Note>

## Fetch past events

`eth_getLogs` returns the events in a block range. On the public endpoints, one request can cover at most 2,000 blocks. A larger range fails with `block range too large`. This example fetches the transfers from the latest 1,000 blocks.

<CodeGroup>
  ```ts viem theme={null}
  import { createPublicClient, http, parseAbiItem } from 'viem';
  import { sei } from 'viem/chains';

  const client = createPublicClient({ chain: sei, transport: http() });

  const USDC = '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392';
  const latest = await client.getBlockNumber();

  const logs = await client.getLogs({
    address: USDC,
    event: parseAbiItem('event Transfer(address indexed from, address indexed to, uint256 value)'),
    fromBlock: latest - 999n,
    toBlock: latest,
  });
  console.log(`${logs.length} transfers in blocks ${latest - 999n} to ${latest}`);
  ```

  ```ts ethers theme={null}
  import { ethers } from 'ethers';

  const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com');

  const USDC = '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392';
  const usdc = new ethers.Contract(
    USDC,
    ['event Transfer(address indexed from, address indexed to, uint256 value)'],
    provider
  );
  const latest = await provider.getBlockNumber();

  const logs = await usdc.queryFilter(usdc.filters.Transfer(), latest - 999, latest);
  console.log(`${logs.length} transfers in blocks ${latest - 999} to ${latest}`);
  ```

  ```python web3.py theme={null}
  from web3 import Web3

  w3 = Web3(Web3.HTTPProvider("https://evm-rpc.sei-apis.com"))

  USDC = "0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392"
  TRANSFER_ABI = [{
      "type": "event", "name": "Transfer", "anonymous": False,
      "inputs": [
          {"name": "from", "type": "address", "indexed": True},
          {"name": "to", "type": "address", "indexed": True},
          {"name": "value", "type": "uint256", "indexed": False},
      ],
  }]
  transfer = w3.eth.contract(address=USDC, abi=TRANSFER_ABI).events.Transfer()
  latest = w3.eth.block_number

  logs = transfer.get_logs(from_block=latest - 999, to_block=latest)
  print(f"{len(logs)} transfers in blocks {latest - 999} to {latest}")
  ```
</CodeGroup>

**You are done when you see:**

```text theme={null}
319 transfers in blocks 235329562 to 235330561
```

To cover a longer range, split it into windows of 2,000 blocks or fewer and request them one after another. For a full history of a contract, use an [indexer](/learn/indexers) instead of `eth_getLogs`.

## Related recipes

* [WebSocket connections](/evm/evm-parity/websocket): new blocks and the `newHeads` subscription
* [Transaction lifecycle](/evm/evm-parity/examples/transaction-lifecycle): decode the events from one transaction's receipt
* [Deploy an ERC-20](/evm/cookbook/deploy-an-erc20): deploy a token that emits its own `Transfer` events


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.