# SDK Reference: EVM Log Trigger
Source: https://docs.chain.link/cre/reference/sdk/triggers/evm-log-trigger-ts
Last Updated: 2026-01-28


The EVM Log Trigger fires when a specific log (event) is emitted by an onchain smart contract.

## Creating the trigger

> **NOTE: Base64 Encoding Required**
>
> **All addresses and topic values must be base64 encoded** using the `hexToBase64()` helper function from the CRE SDK.
> While the workflow simulator accepts raw hex strings for convenience during development, **deployed workflows require
> base64 encoding**. Always use `hexToBase64()` on addresses and topic values to ensure your workflow works in both
> simulation and production.

```typescript
import { EVMClient, getNetwork, hexToBase64 } from "@chainlink/cre-sdk"
import { keccak256, toBytes } from "viem"

// Create an EVMClient instance with a chain selector
const network = getNetwork({
  chainFamily: "evm",
  chainSelectorName: "ethereum-testnet-sepolia",
})

const evmClient = new EVMClient(network.chainSelector.selector)

// Create a log trigger with address and event signature
const transferEventHash = keccak256(toBytes("Transfer(address,address,uint256)"))

const trigger = evmClient.logTrigger({
  addresses: [hexToBase64("0x123...abc")],
  topics: [
    {
      values: [hexToBase64(transferEventHash)],
    },
  ],
})
```

## Configuration

The `logTrigger()` method accepts a configuration object with the following fields:

| Field        | Type            | Description                                                                                                                                                                                                                                                                                                                                                                                               |
| ------------ | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `addresses`  | `string[]`      | **Required.** A list of contract addresses to monitor. **Must be base64 encoded** using `hexToBase64()`. At least one address is required.                                                                                                                                                                                                                                                                |
| `topics`     | `TopicValues[]` | **Required.** An array to filter event topics. The first element must contain at least one event signature. The next three elements can contain indexed argument values (optional). An empty array element acts as a wildcard for indexed arguments. **All topic values must be base64 encoded** using `hexToBase64()`.                                                                                   |
| `confidence` | `string`        | Optional. The block confirmation level to monitor. Can be: <ul><li>**`"CONFIDENCE_LEVEL_LATEST"`**: The most recent block (fastest but least secure).</li><li>**`"CONFIDENCE_LEVEL_SAFE"` (default)**: A block unlikely to be reorged but not yet irreversible.</li><li>**`"CONFIDENCE_LEVEL_FINALIZED"`**: A block considered irreversible (safest, but requires waiting longer for finality).</li></ul> |

> **NOTE: Finality details**
>
> For details on how each confidence level maps to specific chains and estimated wait times, see [Finality and
> Confidence Levels](/cre/concepts/finality).

### `TopicValues`

The `topics` array uses a special format for filtering events:

| Field    | Type       | Description                                                                             |
| -------- | ---------- | --------------------------------------------------------------------------------------- |
| `values` | `string[]` | Array of possible values for a topic. **Must be base64 encoded** using `hexToBase64()`. |

**Topic array structure:**

- **`topics[0]`**: Event signatures (keccak256 hash of the event name and indexed arg types). Must have at least one value.
- **`topics[1]`**: Optional. Values for the first indexed argument. Can be empty (wildcard).
- **`topics[2]`**: Optional. Values for the second indexed argument. Can be empty (wildcard).
- **`topics[3]`**: Optional. Values for the third indexed argument. Can be empty (wildcard).

> **CAUTION: Topic values must be padded to 32 bytes and base64 encoded**
>
> EVM logs always store indexed parameters as **32-byte values**. When filtering on topics 1, 2, or 3:

1. **Pad your values to 32 bytes** using `padHex(value, { size: 32 })` from viem (e.g., addresses are 20 bytes and must be padded)
2. **Convert to base64** using `hexToBase64()` from the CRE SDK

If you don't pad correctly, your filter won't match the actual log topics and the trigger will not fire.

Topic 0 (the event signature from `keccak256`) is already 32 bytes and doesn't need padding.

**Example:**

```typescript
import { hexToBase64 } from "@chainlink/cre-sdk"
import { keccak256, toBytes, padHex } from "viem"

const transferEventHash = keccak256(toBytes("Transfer(address,address,uint256)"))
const fromAddress = "0xabcdef..." as `0x${string}`

const trigger = evmClient.logTrigger({
  addresses: [hexToBase64("0x1234567890abcdef...")],
  topics: [
    // Topic 0: Event signature (Transfer event) - already 32 bytes
    {
      values: [hexToBase64(transferEventHash)],
    },
    // Topic 1: From address (indexed parameter 1) - must pad from 20 to 32 bytes
    {
      values: [hexToBase64(padHex(fromAddress, { size: 32 }))],
    },
    // Topic 2: Empty (wildcard for any "to" address)
    {
      values: [],
    },
  ],
  confidence: "CONFIDENCE_LEVEL_FINALIZED",
})
```

> **NOTE: Simplified configuration**
>
> In the demo workflow and for simple use cases, you can omit `topics` and `confidence`. The trigger will fire for any
> event from the specified addresses using the default "SAFE" confirmation level.

## Payload

The payload passed to your callback function is an `EVMLog` object containing the log data.

| Field         | Type           | Description                                                    |
| ------------- | -------------- | -------------------------------------------------------------- |
| `address`     | `Uint8Array`   | Address of the contract that emitted the log (20 bytes).       |
| `topics`      | `Uint8Array[]` | Indexed log fields, including event signature (32 bytes each). |
| `data`        | `Uint8Array`   | ABI-encoded non-indexed log data.                              |
| `txHash`      | `Uint8Array`   | Hash of the transaction (32 bytes).                            |
| `blockHash`   | `Uint8Array`   | Hash of the block (32 bytes).                                  |
| `blockNumber` | `bigint`       | The block number containing the log (optional).                |
| `txIndex`     | `number`       | Index of the transaction within the block.                     |
| `index`       | `number`       | Index of the log within the block.                             |
| `eventSig`    | `Uint8Array`   | Keccak256 hash of the event signature (32 bytes).              |
| `removed`     | `boolean`      | True if the log was removed during a reorg.                    |

**Working with log data:**

```typescript
import { bytesToHex } from "@chainlink/cre-sdk"

const onLogTrigger = (runtime: Runtime<Config>, log: EVMLog): string => {
  // Convert addresses and hashes to hex
  const contractAddress = bytesToHex(log.address)
  const txHash = bytesToHex(log.txHash)

  // Access topics (first topic is typically the event signature)
  const eventSignature = bytesToHex(log.topics[0])
  const firstIndexedParam = bytesToHex(log.topics[1])

  runtime.log(`Event from ${contractAddress}`)
  runtime.log(`Transaction: ${txHash}`)

  return "Success"
}
```

## Callback Function

Your callback function for EVM log triggers must conform to this signature:

```typescript
import { type Runtime, type EVMLog } from "@chainlink/cre-sdk"

const onLogTrigger = (runtime: Runtime<Config>, log: EVMLog): YourReturnType => {
  // Your workflow logic here
  return result
}
```

**Parameters:**

- `runtime`: The runtime object used to invoke capabilities and access configuration
- `log`: The EVM log payload containing all event data

**Example:**

```typescript
import { bytesToHex, type Runtime, type EVMLog } from "@chainlink/cre-sdk"

type Config = {
  contractAddress: string
}

const onLogTrigger = (runtime: Runtime<Config>, log: EVMLog): string => {
  const topics = log.topics

  if (topics.length < 3) {
    runtime.log(`Log payload does not contain enough topics: ${topics.length}`)
    throw new Error("Insufficient topics in log")
  }

  // Extract indexed parameters from topics
  // topics[0] is the event signature
  // topics[1], topics[2], etc. are indexed event parameters

  const eventSig = bytesToHex(topics[0])
  runtime.log(`Event signature: ${eventSig}`)

  // Access block information
  runtime.log(`Block number: ${log.blockNumber}`)
  runtime.log(`Transaction index: ${log.txIndex}`)

  return "Event processed successfully"
}
```

## Complete Example

```typescript
import {
  EVMClient,
  handler,
  bytesToHex,
  getNetwork,
  Runner,
  hexToBase64,
  type Runtime,
  type EVMLog,
} from "@chainlink/cre-sdk"

type Config = {
  chainSelectorName: string
  contractAddress: string
}

const onLogTrigger = (runtime: Runtime<Config>, log: EVMLog): string => {
  const topics = log.topics

  if (topics.length < 2) {
    throw new Error("Log missing required topics")
  }

  runtime.log(`Processing log from ${bytesToHex(log.address)}`)
  runtime.log(`Event signature: ${bytesToHex(topics[0])}`)

  // Decode the log data based on your event ABI
  // For this example, we just log the raw data
  runtime.log(`Data length: ${log.data.length} bytes`)

  return "Log processed"
}

const initWorkflow = (config: Config) => {
  const network = getNetwork({
    chainFamily: "evm",
    chainSelectorName: config.chainSelectorName,
  })

  if (!network) {
    throw new Error(`Network not found: ${config.chainSelectorName}`)
  }

  const evmClient = new EVMClient(network.chainSelector.selector)

  return [
    handler(
      evmClient.logTrigger({
        addresses: [hexToBase64(config.contractAddress)],
      }),
      onLogTrigger
    ),
  ]
}

export async function main() {
  const runner = await Runner.newRunner<Config>()
  await runner.run(initWorkflow)
}
```

## Decoding Log Data

For production workflows, you'll typically want to decode the log data based on the event's ABI. The TypeScript SDK uses [viem](https://viem.sh/) for ABI encoding/decoding:

```typescript
import { bytesToHex, type Runtime, type EVMLog } from "@chainlink/cre-sdk"

const onLogTrigger = (runtime: Runtime<Config>, log: EVMLog): string => {
  const topics = log.topics

  // topics[0] is the event signature
  // topics[1], topics[2], topics[3] are indexed event parameters

  // Example: Extract an address from topic 1 (last 20 bytes of 32-byte topic)
  const addressFromTopic = bytesToHex(topics[1].slice(12))
  runtime.log(`Address parameter: ${addressFromTopic}`)

  // For non-indexed parameters, you would decode log.data according to the ABI
  // The demo workflow uses viem for contract interactions and ABI handling

  return "Log decoded"
}
```