# EVM Log Trigger
Source: https://docs.chain.link/cre/guides/workflow/using-triggers/evm-log-trigger-go
Last Updated: 2025-11-04

> For the complete documentation index, see [llms.txt](/llms.txt).

The EVM Log trigger fires when a specific log (event) is emitted by a smart contract on an EVM-compatible blockchain. This capability allows you to build powerful, event-driven workflows that react to onchain activity.

This guide explains the two key parts of working with log triggers:

- **[How to configure your workflow to listen for specific events](#configuring-your-trigger)**
- **[How to decode the event data your workflow receives](#decoding-the-event-payload)**

## Configuring your trigger

There are two methods for configuring a log trigger. Choose the one that best fits your use case:

- **[Method 1: Using Binding Helpers](#method-1-using-binding-helpers-recommended):** Use this approach if you are listening for a **single event from a single contract**. This is the recommended and simplest approach for most scenarios.
- **[Method 2: Manual Configuration](#method-2-manual-configuration-advanced):** Use this approach when you need to listen for **multiple events** at once, or for the **same event from multiple different contracts**.

This guide covers both methods in detail below.

### Method 1: Using binding helpers (recommended)

For the most common use case—listening to a single event from a single contract—the recommended approach is to use the helper functions generated by `cre generate-bindings evm`. The generator creates a `LogTrigger<EventName>Log` method for each event in your contract's ABI. This method is simple, readable, and type-safe.

The following example assumes you have generated bindings for a contract that emits a standard ERC20 `Transfer(address,address,uint256)` event.

```go
import (
    "fmt"
    "log/slog"

    "github.com/ethereum/go-ethereum/common"
    "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm"
    "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm/bindings"
    "github.com/smartcontractkit/cre-sdk-go/cre"
    "your-module-name/contracts/evm/src/generated/my_token" // Replace with your module name from go.mod
)

// When using binding helpers, your handler receives a *bindings.DecodedLog with the decoded event data
func onEvmTrigger(config *Config, runtime cre.Runtime, payload *bindings.DecodedLog[my_token.TransferDecoded]) (*MyResult, error) {
    logger := runtime.Logger()

    // Access the decoded event fields directly from payload.Data
    from := payload.Data.From
    to := payload.Data.To
    value := payload.Data.Value

    logger.Info("Transfer detected",
        "from", from.Hex(),
        "to", to.Hex(),
        "value", value.String(),
        "blockNumber", payload.Log.BlockNumber.String(),
    )

    return &MyResult{}, nil
}

func InitWorkflow(config *Config, logger *slog.Logger, secretsProvider cre.SecretsProvider) (cre.Workflow[*Config], error) {
    // Create an EVM client for the chain you want to monitor
    chainSelector, err := evm.ChainSelectorFromName(config.ChainName)
    if err != nil {
        return nil, fmt.Errorf("failed to get chain selector: %w", err)
    }

    evmClient := &evm.Client{
        ChainSelector: chainSelector,
    }

    // Initialize your contract binding (note: returns 2 values)
    contractAddress := common.HexToAddress(config.TokenAddress)
    myTokenContract, err := my_token.NewMyToken(evmClient, contractAddress, nil)
    if err != nil {
        return nil, fmt.Errorf("failed to create contract binding: %w", err)
    }

    // Use the generated helper to create the trigger for the Transfer event
    logTrigger, err := myTokenContract.LogTriggerTransferLog(
        chainSelector, // The chain to monitor (as uint64)
        evm.ConfidenceLevel_CONFIDENCE_LEVEL_FINALIZED, // The confidence level
        []my_token.Transfer{}, // Empty slice = listen to all Transfer events
    )
    if err != nil {
        return nil, fmt.Errorf("failed to create log trigger: %w", err)
    }

    // Register the handler that will be called when the event is detected
    return cre.Workflow[*Config]{
        cre.Handler(logTrigger, onEvmTrigger),
    }, nil
}
```

> \*\*NOTE: Decoded Events with Binding Helpers\*\*
>
>
>
> When you use binding helpers like `LogTrigger<EventName>Log`, your handler automatically receives a
> `*bindings.DecodedLog[<EventName>Decoded]` with the event data already decoded. You don't need to manually parse
> topics or call `Codec.Decode<EventName>()`. This is different from [manual configuration](#method-2-manual-configuration-advanced),
> where your handler receives a raw `*evm.Log` that you must decode yourself.

#### Understanding the `DecodedLog` payload

The `*bindings.DecodedLog[T]` payload has two main parts:

- **`payload.Data`**: The decoded event struct with your event fields (e.g., `From`, `To`, `Value`)
- **`payload.Log`**: The raw log metadata (block number, transaction hash, log index, etc.)

**Example: Accessing decoded event data**

```go
func onEvmTrigger(config *Config, runtime cre.Runtime, payload *bindings.DecodedLog[my_token.TransferDecoded]) (*MyResult, error) {
    logger := runtime.Logger()

    // Access decoded event fields from payload.Data
    from := payload.Data.From        // address (common.Address)
    to := payload.Data.To            // address (common.Address)
    value := payload.Data.Value      // uint256 (*big.Int)

    // Access raw log metadata from payload.Log
    blockNumber := payload.Log.BlockNumber  // *pb.BigInt
    txHash := payload.Log.TxHash            // []byte (32-byte transaction hash)
    logIndex := payload.Log.Index           // uint32

    logger.Info("Transfer detected",
        "from", from.Hex(),
        "to", to.Hex(),
        "value", value.String(),
        "blockNumber", blockNumber.String(),
        "txHash", common.BytesToHash(txHash).Hex(),
    )

    return &MyResult{}, nil
}
```

#### Filtering by indexed parameters with the helper

The third parameter of the `LogTrigger<EventName>Log` function (`filters`) allows you to create filters based on the values of `indexed` event parameters.

- **`indexed` Parameters Only:** You can only filter on parameters marked as `indexed` in the Solidity event definition. Filtering on non-indexed parameters must be done inside your workflow after the event is decoded.
- **Filter Logic:** The helper supports both **AND** and **OR** conditions:
  - It creates an **AND** condition between *different* indexed parameters (e.g., `From` AND `To`).
  - It creates an **OR** condition for *multiple values* provided for the *same* indexed parameter.

**Example 1: "AND" Filtering (`from` Alice "AND" `to` Bob)**

To trigger only on transfers from a specific sender to a specific receiver, provide both values in the same struct literal.

```go
// This trigger will only fire for transfers FROM Alice AND TO Bob.
logTrigger, err := myTokenContract.LogTriggerTransferLog(
    config.ChainSelector,
    evm.ConfidenceLevel_CONFIDENCE_LEVEL_FINALIZED,
    []my_token.Transfer{
        { From: common.HexToAddress("0xAlice"), To: common.HexToAddress("0xBob") },
    },
)
```

**Example 2: "OR" Filtering (`from` Alice "OR" `from` Charlie)**

To trigger on a transfer from one of several possible senders, provide multiple struct literals, each with a value for the same field.

```go
// This trigger will fire for any transfer sent by Alice OR Charlie.
logTrigger, err := myTokenContract.LogTriggerTransferLog(
    config.ChainSelector,
    evm.ConfidenceLevel_CONFIDENCE_LEVEL_FINALIZED,
    []my_token.Transfer{
        { From: common.HexToAddress("0xAlice") },
        { From: common.HexToAddress("0xCharlie") },
    },
)
```

**Example 3: "AND/OR" Filtering**

You can combine **AND** and **OR** conditions for even more precise filtering. The following example triggers if a `Transfer` is (`from` Alice **AND** `to` Bob) **OR** (`from` Charlie **AND** `to` David).

```go
logTrigger, err := myTokenContract.LogTriggerTransferLog(
    config.ChainSelector,
    evm.ConfidenceLevel_CONFIDENCE_LEVEL_FINALIZED,
    []my_token.Transfer{
        { From: common.HexToAddress("0xAlice"), To: common.HexToAddress("0xBob") },
        { From: common.HexToAddress("0xCharlie"), To: common.HexToAddress("0xDavid") },
    },
)
```

For more complex, grouped **AND/OR** conditions (e.g., (`from` Alice **AND** `to` Bob) **OR** (`from` Charlie **AND** `to` David), you must use the manual configuration method below.

#### Confidence Level

The second parameter of the `LogTrigger<EventName>Log` function specifies the block confirmation level to wait for before triggering. For more details on the available levels, see the [`evm.FilterLogTriggerRequest`](/cre/reference/sdk/triggers/evm-log-trigger-go#evmfilterlogtriggerrequest) reference.

### Method 2: Manual configuration (advanced)

For more complex scenarios, you can manually construct the `evm.FilterLogTriggerRequest`. This method is necessary when you need to listen to **multiple events** or the **same event from multiple contracts** with a single trigger.

| Feature                      | Using Binding Helpers                                           | Manual Configuration                                                                    |
| :--------------------------- | :-------------------------------------------------------------- | :-------------------------------------------------------------------------------------- |
| **Primary use case**         | Filtering on a **single event type** from a **single contract** | Filtering across **multiple event types** or the **same event from multiple contracts** |
| **Simplicity & readability** | ✅ High                                                          | ❌ Low                                                                                   |
| **Type safety**              | ✅ High                                                          | ❌ Low (manual hash management)                                                          |

The main advantage of manual configuration is its filtering capability. This is achieved by directly manipulating the fields of the `evm.FilterLogTriggerRequest` struct.

Here is a simplified view of its structure:

```go
logTriggerCfg := &evm.FilterLogTriggerRequest{
    Addresses: [][]byte{ ... },
    Topics:    []*evm.TopicValues{ ... },
}
```

To manually configure a trigger, you specify the contract addresses and the event topics to filter by. The `Topics` array is where you define both the **event type** you want to listen for, and any conditions on its `indexed` parameters.

#### Understanding topic filtering

An EVM log filter uses these fields to create precise rules:

- **The `Addresses` List:** The trigger will fire if the event is emitted from **any** contract in this list (**OR** logic).
- **The `Topics` Array:** An event must match the conditions for **all** defined topic slots (**AND** logic between topics). Within a single topic, you can provide a list of values, and it will match if the event's topic is **any** of those values (**OR** logic within a topic).

This **AND**/**OR** logic is what enables advanced patterns. The first and most important topic, `Topics[0]`, is used to filter by **event type**. Its value should be the Keccak-256 hash of the event's signature. For example, the signature for a standard ERC20 transfer is `"Transfer(address,address,uint256)"`. You provide the hash of this string as `Topics[0]` to filter for only `Transfer` events. The subsequent topics, `Topics[1]`, `Topics[2]`, and `Topics[3]`, are then used to filter on that event's `indexed` parameters.

> \*\*NOTE: Best Practice for Topic Hashes\*\*
>
>
>
> The following examples use `<myContract>.Codec.<EventName>LogHash()`, which is a method on the generated binding that
> returns the event's signature hash. This is the recommended, type-safe way to get an event's topic. If you are working
> with a contract for which you do not have bindings, you would need to provide the raw keccak-256 hash of the event
> signature directly:
>
> <br />
>
> `common.HexToHash("0x<event-signature-hash>").Bytes()`

#### Filtering by indexed parameters

**Example 1: "AND" filtering**

To trigger only on a `Transfer` from Alice **AND** to Bob, you must manually set `Topics[1]` (for the first indexed parameter, `from`) and `Topics[2]` (for the second, `to`).

```go
logTriggerCfg := &evm.FilterLogTriggerRequest{
    Addresses: [][]byte{ common.HexToAddress("0xYourTokenContract").Bytes() },
    Topics: []*evm.TopicValues{
        { Values: [][]byte{ myTokenContract.Codec.TransferLogHash() } },       // Topics[0]: Event signature must be Transfer
        { Values: [][]byte{ common.HexToAddress("0xAlice").Bytes() } }, // Topics[1]: `from` must be Alice
        { Values: [][]byte{ common.HexToAddress("0xBob").Bytes() } },  // Topics[2]: `to` must be Bob
    },
}
```

**Examples 2: "OR" filtering**

You can use the **OR** logic within a topic or across the `Addresses` list to monitor a broader set of onchain activities.

**Example 2.A: Listening to multiple event types**

To trigger on either a `Transfer` **OR** an `Approval` from a single contract, you provide multiple values for `Topics[0]`:

```go
logTriggerCfg := &evm.FilterLogTriggerRequest{
    Addresses: [][]byte{ common.HexToAddress("0xYourContractAddress").Bytes() },
    Topics: []*evm.TopicValues{
        { // Topic 0: The event signature
            Values: [][]byte{
                myTokenContract.Codec.TransferLogHash(), // either a Transfer...
                myTokenContract.Codec.ApprovalLogHash(), // ...OR an Approval.
            },
        },
    },
}
```

**Example 2.B: Listening to the same event from multiple contracts**

To trigger on a `Transfer` event from `TokenA` OR `TokenB` OR `TokenC`, you provide multiple addresses in the `Addresses` list:

```go
logTriggerCfg := &evm.FilterLogTriggerRequest{
    // Listen for events from ANY of these addresses
    Addresses: [][]byte{
        common.HexToAddress("0xTokenContract_A").Bytes(),
        common.HexToAddress("0xTokenContract_B").Bytes(),
        common.HexToAddress("0xTokenContract_C").Bytes(),
    },
    Topics: []*evm.TopicValues{
        { // Topic for the Transfer event signature
            Values: [][]byte{ myTokenContract.Codec.TransferLogHash() },
        },
    },
}
```

**Example 3: "AND/OR" filtering**

You can combine all of these techniques to create highly specific filters. For example, to trigger a workflow if a `Transfer` event is emitted by:

- either `TokenA` **OR** `TokenB`,
- **AND** that transfer is TO your Vault,
- **AND** it is FROM either `Alice` **OR** `Charlie`:

```go
logTriggerCfg := &evm.FilterLogTriggerRequest{
    // OR condition on addresses
    Addresses: [][]byte{
        common.HexToAddress("0xTokenContract_A").Bytes(),
        common.HexToAddress("0xTokenContract_B").Bytes(),
    },
    Topics: []*evm.TopicValues{
        // AND condition for Topic 0 (must be a Transfer)
        { Values: [][]byte{ myTokenContract.Codec.TransferLogHash() } },
        // AND condition for Topic 1 (`from` must be...)
        { Values: [][]byte{
            common.HexToAddress("0xAlice").Bytes(),   // ...Alice OR
            common.HexToAddress("0xCharlie").Bytes(), // ...Charlie
        }},
        // AND condition for Topic 2 (`to` must be your vault)
        { Values: [][]byte{ common.HexToAddress("0xYourVaultAddress").Bytes() } },
    },
}
```

#### Confidence Level

You can set the block confirmation level by adding the `Confidence` field to the `evm.FilterLogTriggerRequest` struct. See the [reference](/cre/reference/sdk/triggers/evm-log-trigger-go#evmfilterlogtriggerrequest) for more details on the available levels.

## Decoding the event payload

What your handler receives depends on how you configured your trigger:

- **If you used [binding helpers](#method-1-using-binding-helpers-recommended):** Your handler automatically receives `*bindings.DecodedLog[<EventName>Decoded]` with the event data already decoded. **You don't need to do anything else** - just access `payload.Data.<FieldName>`.

- **If you used [manual configuration](#method-2-manual-configuration-advanced):** Your handler receives a raw `*evm.Log` struct that you must decode yourself. The sections below show you how.

For the full type definition of `*evm.Log` and all available fields, see the [EVM Log Trigger SDK Reference](/cre/reference/sdk/triggers/evm-log-trigger-go#evmlog).

### Method 1: Using the binding codec (for manual configuration)

> **CAUTION**
>
> This section is only relevant if you used **manual configuration** (not binding helpers). If you used binding helpers,
> your event is already decoded - skip this section.

If you used manual configuration but have bindings for the contract that emitted the event, you should use the generated `Codec` to decode the `*evm.Log` payload your handler receives. The Codec provides a safe, simple, and type-safe way to get your data.

```go
import (
    "your-module-name/contracts/evm/src/generated/my_token"
    "github.com/ethereum/go-ethereum/common"
    "github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm"
    "github.com/smartcontractkit/cre-sdk-go/cre"
)

// The `onEvmTrigger` handler receives the raw event log.
func onEvmTrigger(config *Config, runtime cre.Runtime, log *evm.Log) (*MyResult, error) {
    logger := runtime.Logger()
    // Assume `myTokenContract` is an initialized instance of your contract binding.

    // Check which event was received by comparing the first topic to known event topics.
    eventSignature := common.BytesToHash(log.Topics[0])

    switch eventSignature {
    case common.BytesToHash(myTokenContract.Codec.TransferLogHash()):
        // It's a Transfer! Use the safe, generated decoder.
        transferEvent, err := myTokenContract.Codec.DecodeTransfer(log)
        if err != nil { /* handle error */ }
        logger.Info("Transfer detected", "from", transferEvent.From, "to", transferEvent.To)

    case common.BytesToHash(myTokenContract.Codec.ApprovalLogHash()):
        // It's an Approval! Use the safe, generated decoder.
        approvalEvent, err := myTokenContract.Codec.DecodeApproval(log)
        if err != nil { /* handle error */ }
        logger.Info("Approval detected", "owner", approvalEvent.Owner, "spender", approvalEvent.Spender)
    }

    return &MyResult{}, nil
}
```

### Method 2: Manual decoding (for manual configuration without bindings)

> **CAUTION**
>
> This section is only relevant if you used **manual configuration** and don't have bindings for the contract. If you
> used binding helpers, skip this section.

If you used manual configuration and are interacting with a third-party contract for which you do not have bindings, you must manually parse the raw byte arrays in `log.Topics` (for `indexed` fields) and `log.Data` (for non-indexed fields).

The following example shows how to manually parse a standard ERC20 `Transfer(address indexed from, address indexed to, uint256 value)` event.

```go
import "github.com/ethereum/go-ethereum/accounts/abi"
import "github.com/ethereum/go-ethereum/common"
import "math/big"
import "fmt"

type MyResult struct{}

func onEvmTrigger(config *Config, runtime cre.Runtime, log *evm.Log) (*MyResult, error) {
    logger := runtime.Logger()
    // Manually parse the indexed topics. `Topics[0]` is the event signature.
    // An indexed `address` is a 32-byte value; we slice the last 20 bytes to get the actual address.
    fromAddress := common.BytesToAddress(log.Topics[1][12:])
    toAddress := common.BytesToAddress(log.Topics[2][12:])

    // Manually parse the non-indexed data using the go-ethereum ABI package.
    var value *big.Int
    uint256Type, _ := abi.NewType("uint256", "", nil)
    decodedData, err := abi.Arguments{{Type: uint256Type}}.Unpack(log.Data)
    if err != nil {
        return nil, fmt.Errorf("failed to unpack log data: %w", err)
    }
    value = decodedData[0].(*big.Int)

    logger.Info(
        "Manual transfer decode successful",
        "from", fromAddress.Hex(),
        "to", toAddress.Hex(),
        "value", value.String(),
    )

    // ... Your logic here ...
    return &MyResult{}, nil
}
```

## Testing log triggers in simulation

To test your EVM log trigger during development, you can use the workflow simulator with a transaction hash and event index. The simulator fetches the log from your configured RPC and passes it to your callback function.

For detailed instructions on simulating EVM log triggers, including interactive and non-interactive modes, see the [EVM Log Trigger section in the Simulating Workflows guide](/cre/guides/operations/simulating-workflows#evm-log-trigger).