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

# Watch Token Roles \[token.watchRole]

Watches TIP-20 `RoleMembershipUpdated` events for a token. The event is emitted when a role is granted to, or revoked from, an account. [Learn more about roles](https://docs.tempo.xyz/protocol/tip20/spec#role-based-access-control)

## Usage

:::code-group
```ts twoslash [example.ts]
import { client } from './viem.config'

const watcher = client.token.watchRole({
  token: '0x20c0000000000000000000000000000000000000',
})

watcher.onLogs((logs) => {
  for (const log of logs) console.log(log.args)
  // @log: {
  // @log:   role: '0x114e74f6ea3bd819998f78687bfcb11b140da08e9b7d222fa9c1f1ba1f2aa122',
  // @log:   account: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
  // @log:   sender: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  // @log:   hasRole: true
  // @log: }
})

// Later, tear down the watcher.
watcher.off()
```

```ts twoslash [viem.config.ts] filename="viem.config.ts"
// [!include ~/snippets/tempo/viem.config.ts:setup]
```
:::

Also callable standalone: `Actions.token.watchRole(client, options)`, with `Actions` imported from `'viem/tempo'`.

## Recipes

### Alert on Unexpected issuer Grants

Filter `args.role` with `TokenRole.serialize` and page an operator when the issuer role is granted to an account outside your allowlist.

:::code-group
```ts twoslash [example.ts]
import { TokenRole } from 'viem/tempo'
import { client } from './viem.config'

// Accounts authorized to hold the issuer role.
const knownIssuers = new Set<string>([
  '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
])

const watcher = client.token.watchRole({
  args: { role: TokenRole.serialize('issuer') }, // [!code focus]
  token: '0x20c0000000000000000000000000000000000000',
})

watcher.onLogs((logs) => {
  for (const log of logs) {
    if (!log.args.hasRole || knownIssuers.has(log.args.account)) continue // [!code focus]
    console.log('Unexpected grant:', log.args.account, 'by', log.args.sender) // [!code focus]
    // @log: Unexpected grant: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 by 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
  }
})
```

```ts twoslash [viem.config.ts] filename="viem.config.ts"
// [!include ~/snippets/tempo/viem.config.ts:setup]
```
:::

### Pause Issuance When Your Service Loses the issuer Role

Filter `args.account` and `args.role` together to react when your own service account's issuer access is revoked, halting mint jobs before they fail onchain.

:::code-group
```ts twoslash [example.ts]
import { TokenRole } from 'viem/tempo'
import { client } from './viem.config'

const watcher = client.token.watchRole({
  args: { // [!code focus]
    account: client.account.address, // [!code focus]
    role: TokenRole.serialize('issuer'), // [!code focus]
  }, // [!code focus]
  token: '0x20c0000000000000000000000000000000000000',
})

watcher.onLogs((logs) => {
  for (const log of logs) {
    // Halt issuance jobs until access is restored.
    if (!log.args.hasRole) console.log('Issuer role revoked by', log.args.sender) // [!code focus]
  }
})
```

```ts twoslash [viem.config.ts] filename="viem.config.ts"
// [!include ~/snippets/tempo/viem.config.ts:setup]
```
:::

## Return Value

Returns a watcher handle. Watching starts when the first listener (or async iterator) attaches, and stops when `off` is called.

```ts
type ReturnType = {
  /** Registers a log listener. Starts the watcher on first registration. Returns an unregister function. */
  onLogs: (fn: (logs: readonly Log[]) => void) => () => void
  /** Registers an error listener. Returns an unregister function. */
  onError: (fn: (error: Error) => void) => () => void
  /** Tears down the watcher: removes listeners, ends iterators, stops the poll or subscription. */
  off: () => void
  /** Async-iterates emitted log batches (latest-only stream). */
  [Symbol.asyncIterator]: () => AsyncIterableIterator<{ logs: readonly Log[] }>
}
```

Each log is decoded, with the event arguments on `args`:

```ts
type Log = {
  /** Decoded `RoleMembershipUpdated` event arguments. */
  args: {
    /** Role that was updated (bytes32 role hash). */
    role: Hex
    /** Account whose membership changed. */
    account: Address
    /** Address that performed the update. */
    sender: Address
    /** Whether the account now holds the role. */
    hasRole: boolean
  }
  /** Name of the emitted event. */
  eventName: 'RoleMembershipUpdated'
  // ...standard log fields (address, blockHash, blockNumber, logIndex, transactionHash, ...)
}
```

Role values are bytes32 role hashes. Compare them against a named role (`defaultAdmin`, `pause`, `unpause`, `issuer`, `burnBlocked`) with `TokenRole.serialize` from `viem/tempo`.

## Parameters

### args

* **Type:** `object`

```ts
type Args = {
  /** Filter by role (bytes32 role hash). */
  role?: Hex | Hex[] | null
  /** Filter by account. */
  account?: Address | Address[] | null
  /** Filter by sender. */
  sender?: Address | Address[] | null
}
```

Indexed argument values to filter logs by. Derive a role hash from a named role with `TokenRole.serialize` from `viem/tempo`, for example `TokenRole.serialize('issuer')`.

### batch

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

Whether to batch the logs found within a poll interval into a single emission. When `false`, each log is emitted on its own.

### fromBlock

* **Type:** `bigint`

Block number from which to start watching for logs.

### poll

* **Type:** `boolean`

Whether to poll for new logs instead of using a subscription. Defaults to `true` when the transport cannot subscribe or `fromBlock` is provided.

### pollingInterval

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

Polling frequency (in ms).

### token

* **Type:** `Address | bigint`

Token to operate on: a TIP-20 token id or a contract address.
