> ## 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.

# WebSocket Connections

> Connecting to Sei via WebSocket for real-time block and event subscriptions

# WebSocket connections

Sei supports `eth_subscribe` over WebSocket. With the WebSocket transports in standard libraries, you can subscribe to new blocks, event logs, and pending transactions.

## Endpoints

| Network | WebSocket endpoint |
| - | - |
| Sei Mainnet | `wss://evm-ws.sei-apis.com` |
| Sei Testnet | `wss://evm-ws-testnet.sei-apis.com` |

## Connecting

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

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

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

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

## Watching new blocks

<CodeGroup>
  ```ts viem theme={null}
  const unwatch = client.watchBlocks({
    onBlock: (block) => {
      console.log('New block:', block.number);
    },
  });

  // Stop watching
  unwatch();
  ```

  ```ts ethers theme={null}
  provider.on('block', (blockNumber) => {
    console.log('New block:', blockNumber);
  });

  // Stop watching
  provider.off('block');
  ```
</CodeGroup>

## Watching contract events

<CodeGroup>
  ```ts viem theme={null}
  import { parseAbiItem } from 'viem';

  const unwatch = client.watchEvent({
    address: '0xContractAddress',
    event: parseAbiItem('event Transfer(address indexed from, address indexed to, uint256 value)'),
    onLogs: (logs) => {
      console.log('Transfer events:', logs);
    },
  });
  ```

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

  const ERC20_ABI = ['event Transfer(address indexed from, address indexed to, uint256 value)'];
  const contract = new ethers.Contract('0xContractAddress', ERC20_ABI, provider);

  contract.on('Transfer', (from, to, value, event) => {
    console.log('Transfer:', { from, to, value });
  });

  // Stop watching
  contract.off('Transfer');
  ```
</CodeGroup>

## Watching ERC-20 transfers across all contracts

```ts viem theme={null}
import { parseAbiItem } from 'viem';

const unwatch = client.watchEvent({
  event: parseAbiItem('event Transfer(address indexed from, address indexed to, uint256 value)'),
  onLogs: (logs) => {
    logs.forEach((log) => {
      console.log(`Transfer on ${log.address}:`, log.args);
    });
  },
});
```

## Notes

* Sei has instant finality, so every block emitted over WebSocket is already final. You do not need to wait for more confirmations before you act on an event.
* Pending transaction subscriptions (`newPendingTransactions`) are supported at the RPC level, but Sei does not guarantee Ethereum-style pending state visibility.

## `newHeads` under Autobahn consensus

When a node runs under Autobahn consensus, `eth_subscribe("newHeads")` notifications come from an in-process notifier, not from the legacy consensus event bus. The notifier publishes committed-block headers directly. Subscribers still observe headers only for fully committed blocks. However, the header payload differs from the legacy path in a few ways:

* **`parentHash`, `receiptsRoot`, and `transactionsRoot` are returned as zero hashes** (`0x0000…0000`). The Autobahn block-execution path does not build a Tendermint-style hash chain, so there is no meaningful value to return in these fields.
* **`stateRoot`** comes from the finalized block's `AppHash` (the post-execution application hash), not from a pre-execution header field.
* **`hash`** is the Autobahn block-header hash. It is the same value that `eth_getBlockByNumber` and the receipt APIs report as `blockHash`. This keeps `newHeads` consistent with the rest of the EVM RPC surface.
* **`gasUsed`** is an approximation (summed from per-transaction results) that keeps the notification cheap.

Because of these differences:

* Subscribers that chain-validate the head stream by linking `parentHash` values cannot rely on `newHeads` under Autobahn. They need a different mechanism.
* If you need the exact `gasUsed` or the omitted hash fields, fetch the block explicitly with `eth_getBlockByNumber`.


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