SDK Reference: EVM Log Trigger

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

Creating the trigger

The recommended way to create a log trigger is with the logTriggerConfig() helper, which accepts hex-encoded addresses and topics and handles base64 conversion, byte-length validation, and confidence level formatting automatically:

import { EVMClient, getNetwork, logTriggerConfig } 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",
  isTestnet: true,
})

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(
  logTriggerConfig({
    addresses: ["0x1234567890123456789012345678901234567890"],
    topics: [[transferEventHash]],
  })
)
Manual configuration with hexToBase64()

If you need more control, you can encode values manually using hexToBase64():

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

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

const transferEventHash = keccak256(toBytes("Transfer(address,address,uint256)"))

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

logTriggerConfig() helper

The logTriggerConfig() helper is the recommended way to build the configuration object for logTrigger(). It accepts hex-encoded addresses and topics (the format you get from viem) and handles:

  • Base64 conversion: Converts 0x-prefixed hex to the base64 encoding required by the proto
  • Byte-length validation: Verifies addresses are 20 bytes and topics are 32 bytes
  • Confidence formatting: Accepts short names ('LATEST', 'SAFE', 'FINALIZED') instead of the full CONFIDENCE_LEVEL_ prefix

Signature:

function logTriggerConfig(opts: LogTriggerConfigOptions): FilterLogTriggerRequestJson

Parameters:

interface LogTriggerConfigOptions {
  /** EVM addresses to monitor — hex strings with 0x prefix (20 bytes each) */
  addresses: Hex[]
  /** Topic filters — array of up to 4 arrays of hex topic values (32 bytes each).
   *  - topics[0]: event signatures (keccak256 hashes), at least one required
   *  - topics[1]: possible values for first indexed arg (optional)
   *  - topics[2]: possible values for second indexed arg (optional)
   *  - topics[3]: possible values for third indexed arg (optional)
   */
  topics?: Hex[][]
  /** Confidence level for log finality. Defaults to SAFE. */
  confidence?: "SAFE" | "LATEST" | "FINALIZED"
}

Validation errors:

The helper throws descriptive errors for common mistakes:

  • Missing 0x prefix on addresses or topics
  • Addresses that aren't exactly 20 bytes
  • Topics that aren't exactly 32 bytes
  • Error messages include the index of the invalid value (e.g., "Invalid address at index 1: ...")

Example with topic filtering:

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

const transferEventHash = keccak256(toBytes("Transfer(address,address,uint256)"))
const fromAddress = padHex("0xabcdef1234567890abcdef1234567890abcdef12", { size: 32 })

const trigger = evmClient.logTrigger(
  logTriggerConfig({
    addresses: ["0x1234567890123456789012345678901234567890"],
    topics: [
      // Topic 0: Event signature (Transfer event) - already 32 bytes
      [transferEventHash],
      // Topic 1: From address (indexed parameter 1) - must pad from 20 to 32 bytes
      [fromAddress],
      // Topic 2: Omit for wildcard (any "to" address)
    ],
    confidence: "FINALIZED",
  })
)

Raw configuration reference

The logTrigger() method accepts a FilterLogTriggerRequestJson object directly. When using logTriggerConfig(), the helper builds this object for you. If you need to construct it manually, the fields are:

Field
Type
Description
addressesstring[]Required. A list of contract addresses to monitor. Must be base64 encoded using hexToBase64(). At least one address is required.
topicsTopicValues[]Optional. 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().
confidencestringOptional. The block confirmation level to monitor. Can be:
  • "CONFIDENCE_LEVEL_LATEST": The most recent block (fastest but least secure).
  • "CONFIDENCE_LEVEL_SAFE" (default): A block unlikely to be reorged but not yet irreversible.
  • "CONFIDENCE_LEVEL_FINALIZED": A block considered irreversible (safest, but requires waiting longer for finality).

TopicValues

FieldTypeDescription
valuesstring[]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).

Payload

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

FieldTypeDescription
addressUint8ArrayAddress of the contract that emitted the log (20 bytes).
topicsUint8Array[]Indexed log fields, including event signature (32 bytes each).
dataUint8ArrayABI-encoded non-indexed log data.
txHashUint8ArrayHash of the transaction (32 bytes).
blockHashUint8ArrayHash of the block (32 bytes).
blockNumberbigintThe block number containing the log (optional).
txIndexnumberIndex of the transaction within the block.
indexnumberIndex of the log within the block.
eventSigUint8ArrayKeccak256 hash of the event signature (32 bytes).
removedbooleanTrue if the log was removed during a reorg.

Working with log data:

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:

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:

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

import {
  EVMClient,
  handler,
  bytesToHex,
  getNetwork,
  logTriggerConfig,
  Runner,
  type Runtime,
  type EVMLog,
} from "@chainlink/cre-sdk"
import { keccak256, toBytes } from "viem"

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,
    isTestnet: true,
  })

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

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

  const transferEventHash = keccak256(
    toBytes("Transfer(address,address,uint256)")
  )

  return [
    handler(
      evmClient.logTrigger(
        logTriggerConfig({
          addresses: [config.contractAddress as `0x${string}`],
          topics: [[transferEventHash]],
        })
      ),
      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 for ABI encoding/decoding:

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"
}

Get the latest Chainlink content straight to your inbox.