> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://v3.viem.sh/api/mcp` to find what you need.

# Watching Contract Events

## Overview

Use [`contract.watchEvent.<event>`](#contractwatchevent) to receive decoded logs for one event in the contract's ABI.

The instance supplies `abi`, `address`, and `eventName` to [`Actions.contract.watchEvent`](/docs/actions/public/contract/watchEvent).

It returns a watcher that supports callbacks and asynchronous iteration.

The source starts when the first `onLogs` callback or asynchronous iterator attaches. Registering only `onError` does not start it.

## Recipes

### Watch Indexed Events

Pass `args` to watch indexed event fields. Call `off` when the watcher is no longer needed.

```ts twoslash
import { Client, Contract, http } from 'viem'
import { mainnet } from 'viem/chains'
import { Abis } from 'viem/utils'

const client = Client.create({ chain: mainnet, transport: http() })

const contract = Contract.from({
  abi: Abis.erc20,
  address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
  client,
})

const watch = contract.watchEvent.Transfer({
  args: { from: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e' }, // [!code focus]
})

watch.onLogs((logs) => console.log(logs)) // [!code focus]
// @log: [
// @log:   {
// @log:     args: { from: '0xA0Cf...251e', to: '0x7099...79C8', value: 1_000_000n },
// @log:     eventName: 'Transfer',
// @log:     ...
// @log:   },
// @log: ]

// Later, after the watcher receives logs:
watch.off() // [!code focus]
```

### Handle Watch Errors

Register `onError` before `onLogs` to observe early source failures. The `onLogs` registration starts the source; `onError` alone does not.

```ts twoslash
import { Client, Contract, http } from 'viem'
import { mainnet } from 'viem/chains'
import { Abis } from 'viem/utils'

const client = Client.create({ chain: mainnet, transport: http() })

const contract = Contract.from({
  abi: Abis.erc20,
  address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
  client,
})

const watch = contract.watchEvent.Transfer()

watch.onError((error) => console.error(error)) // [!code focus]
watch.onLogs((logs) => console.log(logs)) // [!code focus]

// Later, after the watcher receives logs or reports an error:
watch.off()
```

### Iterate Asynchronously

An asynchronous iterator starts the source and yields `{ logs }`. It retains only the newest unread batch, so slow consumers can skip intermediate batches.

Use `onLogs` when every emitted batch must reach a callback. Call `off` in `finally` to stop the source and end other iterators.

```ts twoslash
import { Client, Contract, http } from 'viem'
import { mainnet } from 'viem/chains'
import { Abis } from 'viem/utils'

const client = Client.create({ chain: mainnet, transport: http() })

const contract = Contract.from({
  abi: Abis.erc20,
  address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
  client,
})

const watch = contract.watchEvent.Transfer()

try {
  for await (const { logs } of watch) { // [!code focus]
    console.log(logs) // [!code focus]
    break // [!code focus]
  } // [!code focus]
} finally {
  watch.off() // [!code focus]
}
```

## `contract.watchEvent`

Returns a watcher for decoded logs from the selected ABI event.

### Usage

```ts twoslash
import { Client, Contract, http } from 'viem'
import { mainnet } from 'viem/chains'
import { Abis } from 'viem/utils'

const client = Client.create({ chain: mainnet, transport: http() })

const contract = Contract.from({
  abi: Abis.erc20,
  address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
  client,
})

const watch = contract.watchEvent.Transfer()

watch.onLogs((logs) => console.log(logs))
// @log: [
// @log:   {
// @log:     args: { from: '0xA0Cf...251e', to: '0x7099...79C8', value: 1_000_000n },
// @log:     eventName: 'Transfer',
// @log:     ...
// @log:   },
// @log: ]

// Later, after the watcher receives logs:
watch.off()
```

### Parameters

Options include `args`, `batch`, `fromBlock`, `poll`, `pollingInterval`, and `strict`. Logs are
batched by default, and `strict` defaults to `false`.

When `poll` is omitted, `fromBlock` selects polling. Without `fromBlock`, the method subscribes
when the [Transport](/docs/transports) supports subscriptions and polls otherwise.

#### options.args

* **Type:** Inferred from the selected ABI event
* **Optional**

Indexed argument values to match. Only indexed fields from the selected event are accepted.

```ts twoslash
import { Client, Contract, http } from 'viem'
import { mainnet } from 'viem/chains'
import { Abis } from 'viem/utils'

const client = Client.create({ chain: mainnet, transport: http() })
const contract = Contract.from({
  abi: Abis.erc20,
  address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
  client,
})
// ---cut---
const watch = contract.watchEvent.Transfer({
  args: { // [!code focus]
    from: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', // [!code focus]
  }, // [!code focus]
})

watch.onLogs((logs) => console.log(logs))

// Later, after the watcher receives logs:
watch.off()
```

#### options.batch

* **Type:** `boolean`
* **Default:** `true`

Whether to emit logs from one poll interval as a batch. When `false`, each log is emitted separately.

```ts twoslash
import { Client, Contract, http } from 'viem'
import { mainnet } from 'viem/chains'
import { Abis } from 'viem/utils'

const client = Client.create({ chain: mainnet, transport: http() })
const contract = Contract.from({
  abi: Abis.erc20,
  address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
  client,
})
// ---cut---
const watch = contract.watchEvent.Transfer({
  batch: false, // [!code focus]
})

watch.onLogs((logs) => console.log(logs))
watch.off()
```

#### options.fromBlock

* **Type:** `bigint`
* **Optional**

The block number from which to start watching. When `poll` is omitted, providing `fromBlock` selects polling.

```ts twoslash
import { Client, Contract, http } from 'viem'
import { mainnet } from 'viem/chains'
import { Abis } from 'viem/utils'

const client = Client.create({ chain: mainnet, transport: http() })
const contract = Contract.from({
  abi: Abis.erc20,
  address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
  client,
})
// ---cut---
const watch = contract.watchEvent.Transfer({
  fromBlock: 20_000_000n, // [!code focus]
})

watch.onLogs((logs) => console.log(logs))
watch.off()
```

#### options.poll

* **Type:** `boolean`
* **Default:** `true` with `fromBlock` or a Transport that cannot subscribe; `false` otherwise

Whether to poll for logs instead of using a subscription. An explicit value overrides the default derived from `fromBlock` and the Transport.

```ts twoslash
import { Client, Contract, webSocket } from 'viem'
import { mainnet } from 'viem/chains'
import { Abis } from 'viem/utils'

const client = Client.create({ chain: mainnet, transport: webSocket() })
const contract = Contract.from({
  abi: Abis.erc20,
  address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
  client,
})
// ---cut---
const watch = contract.watchEvent.Transfer({
  poll: true, // [!code focus]
})

watch.onLogs((logs) => console.log(logs))
watch.off()
```

#### options.pollingInterval

* **Type:** `number`
* **Default:** `client.pollingInterval`

The polling interval in milliseconds.

```ts twoslash
import { Client, Contract, http } from 'viem'
import { mainnet } from 'viem/chains'
import { Abis } from 'viem/utils'

const client = Client.create({ chain: mainnet, transport: http() })
const contract = Contract.from({
  abi: Abis.erc20,
  address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
  client,
})
// ---cut---
const watch = contract.watchEvent.Transfer({
  pollingInterval: 1_000, // [!code focus]
})

watch.onLogs((logs) => console.log(logs))
watch.off()
```

#### options.strict

* **Type:** `boolean`
* **Default:** `false`

Whether logs must match all indexed and non-indexed event arguments.

```ts twoslash
import { Client, Contract, http } from 'viem'
import { mainnet } from 'viem/chains'
import { Abis } from 'viem/utils'

const client = Client.create({ chain: mainnet, transport: http() })
const contract = Contract.from({
  abi: Abis.erc20,
  address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
  client,
})
// ---cut---
const watch = contract.watchEvent.Transfer({
  strict: true, // [!code focus]
})

watch.onLogs((logs) => console.log(logs))
watch.off()
```

### Return Value

`Actions.contract.watchEvent.Watcher`

The watcher provides these members:

* `onLogs(fn)` registers a log callback, starts the source on first registration, and returns a function that removes that listener.
* `onError(fn)` registers an error callback and returns a function that removes it. This registration does not start the source.
* `off()` removes listeners, ends iterators, stops the poll or subscription, and uninstalls an active filter. It is idempotent and terminal.
* `[Symbol.asyncIterator]()` yields a latest-only `{ logs }` stream and throws when the source reports an error.

### Errors

The watcher forwards RPC and Transport failures to `onError` and asynchronous iterators. Event setup can report these concrete errors:

| Error | Description |
| --- | --- |
| `AbiItem.NotFoundError` | The selected event is missing from the runtime ABI. |
| `AbiEvent.FilterTypeNotSupportedError` | `args` includes an indexed tuple or array, which event topic filters do not support. |
| `AbiParameters.InvalidTypeError` | `args` includes an indexed ABI type that the topic encoder does not support. |
| `Address.InvalidAddressError` | `args` includes an invalid indexed address. |
| `Hex.IntegerOutOfRangeError` | `args` includes an indexed integer outside the supported 256-bit range. |
| `Hex.SizeExceedsPaddingSizeError` | An indexed fixed-bytes value exceeds the 32-byte topic size. |
