From 977ada77bf542514e80dfac84753ad656438f3a6 Mon Sep 17 00:00:00 2001
From: Karim <98668332+khadni@users.noreply.github.com>
Date: Thu, 8 Jan 2026 16:07:46 -0500
Subject: [PATCH 1/4] TS EVM log triggers to use base64 encoding
---
.../using-triggers/evm-log-trigger-ts.mdx | 163 +++++++++++++-----
1 file changed, 117 insertions(+), 46 deletions(-)
diff --git a/src/content/cre/guides/workflow/using-triggers/evm-log-trigger-ts.mdx b/src/content/cre/guides/workflow/using-triggers/evm-log-trigger-ts.mdx
index dba998b53d7..012175d85c6 100644
--- a/src/content/cre/guides/workflow/using-triggers/evm-log-trigger-ts.mdx
+++ b/src/content/cre/guides/workflow/using-triggers/evm-log-trigger-ts.mdx
@@ -23,12 +23,19 @@ This guide explains the two key parts of working with log triggers:
You create an EVM Log trigger by calling the `EVMClient.logTrigger()` method with a `FilterLogTriggerRequest` configuration. This configuration specifies which contract addresses and event topics to listen for.
+
+
### Basic configuration
The simplest configuration listens for **all events** from specific contract addresses:
```typescript
-import { cre, getNetwork, type Runtime, type EVMLog, Runner, bytesToHex } from "@chainlink/cre-sdk"
+import { cre, getNetwork, type Runtime, type EVMLog, Runner, bytesToHex, hexToBase64 } from "@chainlink/cre-sdk"
type Config = {
chainSelectorName: string
@@ -58,7 +65,7 @@ const initWorkflow = (config: Config) => {
return [
cre.handler(
evmClient.logTrigger({
- addresses: [config.contractAddress],
+ addresses: [hexToBase64(config.contractAddress)],
}),
onLogTrigger
),
@@ -75,10 +82,11 @@ main()
### Filtering by event type
-To listen for **specific event types**, you need to provide the event's signature hash as the first topic (`Topics[0]`). You can compute this using viem's `keccak256` and `toHex` functions:
+To listen for **specific event types**, you need to provide the event's signature hash as the first topic (`Topics[0]`). You can compute this using viem's `keccak256` and `toBytes` functions:
```typescript
-import { keccak256, toHex } from "viem"
+import { keccak256, toBytes } from "viem"
+import { hexToBase64 } from "@chainlink/cre-sdk"
const initWorkflow = (config: Config) => {
const network = getNetwork({
@@ -94,14 +102,14 @@ const initWorkflow = (config: Config) => {
const evmClient = new cre.capabilities.EVMClient(network.chainSelector.selector)
// Compute the event signature hash for Transfer(address,address,uint256)
- const transferEventHash = keccak256(toHex("Transfer(address,address,uint256)"))
+ const transferEventHash = keccak256(toBytes("Transfer(address,address,uint256)"))
return [
cre.handler(
evmClient.logTrigger({
- addresses: [config.contractAddress],
+ addresses: [hexToBase64(config.contractAddress)],
topics: [
- { values: [transferEventHash] }, // Listen only for Transfer events
+ { values: [hexToBase64(transferEventHash)] }, // Listen only for Transfer events
],
}),
onLogTrigger
@@ -118,13 +126,15 @@ EVM events can have up to 3 `indexed` parameters (in addition to the event signa
- **`addresses`**: The trigger fires if the event is emitted from **any** contract in this list (**OR** logic).
- **`topics`**: An event must match the conditions for **all** defined topic slots (**AND** logic between topics). Within a single topic, you can provide multiple values, and it will match if the event's topic is **any** of those values (**OR** logic within a topic).
+- **Wildcarding topics**: To skip filtering on a specific topic position, omit it from the topics array or provide an empty values array `{ values: [] }`. For example, to filter on topic 1 and topic 3 but not topic 2, you would provide `[topic0, topic1, { values: [] }, topic3]`.
#### Example 1: Filtering on a single indexed parameter
To trigger only on `Transfer` events where the `from` address is a specific value:
```typescript
-import { keccak256, toHex, pad } from "viem"
+import { keccak256, toBytes, padHex } from "viem"
+import { hexToBase64 } from "@chainlink/cre-sdk"
const initWorkflow = (config: Config) => {
const network = getNetwork({
@@ -139,16 +149,16 @@ const initWorkflow = (config: Config) => {
const evmClient = new cre.capabilities.EVMClient(network.chainSelector.selector)
- const transferEventHash = keccak256(toHex("Transfer(address,address,uint256)"))
- const aliceAddress = "0xAlice..."
+ const transferEventHash = keccak256(toBytes("Transfer(address,address,uint256)"))
+ const aliceAddress = "0xAlice..." as `0x${string}`
return [
cre.handler(
evmClient.logTrigger({
- addresses: [config.contractAddress],
+ addresses: [hexToBase64(config.contractAddress)],
topics: [
- { values: [transferEventHash] }, // Topic 0: Event signature (Transfer)
- { values: [pad(aliceAddress)] }, // Topic 1: from = Alice
+ { values: [hexToBase64(transferEventHash)] }, // Topic 0: Event signature (Transfer)
+ { values: [hexToBase64(padHex(aliceAddress, { size: 32 }))] }, // Topic 1: from = Alice
],
}),
onLogTrigger
@@ -157,10 +167,12 @@ const initWorkflow = (config: Config) => {
}
```
+{/* prettier-ignore */}
#### Example 2: "AND" filtering
@@ -168,7 +180,8 @@ const initWorkflow = (config: Config) => {
To trigger on `Transfer` events where `from` is Alice **AND** `to` is Bob:
```typescript
-import { keccak256, toHex, pad } from "viem"
+import { keccak256, toBytes, padHex } from "viem"
+import { hexToBase64 } from "@chainlink/cre-sdk"
const initWorkflow = (config: Config) => {
const network = getNetwork({
@@ -183,18 +196,18 @@ const initWorkflow = (config: Config) => {
const evmClient = new cre.capabilities.EVMClient(network.chainSelector.selector)
- const transferEventHash = keccak256(toHex("Transfer(address,address,uint256)"))
- const aliceAddress = "0xAlice..."
- const bobAddress = "0xBob..."
+ const transferEventHash = keccak256(toBytes("Transfer(address,address,uint256)"))
+ const aliceAddress = "0xAlice..." as `0x${string}`
+ const bobAddress = "0xBob..." as `0x${string}`
return [
cre.handler(
evmClient.logTrigger({
- addresses: [config.contractAddress],
+ addresses: [hexToBase64(config.contractAddress)],
topics: [
- { values: [transferEventHash] }, // Topic 0: Event signature (Transfer)
- { values: [pad(aliceAddress)] }, // Topic 1: from = Alice
- { values: [pad(bobAddress)] }, // Topic 2: to = Bob
+ { values: [hexToBase64(transferEventHash)] }, // Topic 0: Event signature (Transfer)
+ { values: [hexToBase64(padHex(aliceAddress, { size: 32 }))] }, // Topic 1: from = Alice
+ { values: [hexToBase64(padHex(bobAddress, { size: 32 }))] }, // Topic 2: to = Bob
],
}),
onLogTrigger
@@ -208,7 +221,8 @@ const initWorkflow = (config: Config) => {
To trigger on `Transfer` events where `from` is **either** Alice **OR** Charlie:
```typescript
-import { keccak256, toHex, pad } from "viem"
+import { keccak256, toBytes, padHex } from "viem"
+import { hexToBase64 } from "@chainlink/cre-sdk"
const initWorkflow = (config: Config) => {
const network = getNetwork({
@@ -223,17 +237,22 @@ const initWorkflow = (config: Config) => {
const evmClient = new cre.capabilities.EVMClient(network.chainSelector.selector)
- const transferEventHash = keccak256(toHex("Transfer(address,address,uint256)"))
- const aliceAddress = "0xAlice..."
- const charlieAddress = "0xCharlie..."
+ const transferEventHash = keccak256(toBytes("Transfer(address,address,uint256)"))
+ const aliceAddress = "0xAlice..." as `0x${string}`
+ const charlieAddress = "0xCharlie..." as `0x${string}`
return [
cre.handler(
evmClient.logTrigger({
- addresses: [config.contractAddress],
+ addresses: [hexToBase64(config.contractAddress)],
topics: [
- { values: [transferEventHash] }, // Topic 0: Event signature (Transfer)
- { values: [pad(aliceAddress), pad(charlieAddress)] }, // Topic 1: from = Alice OR Charlie
+ { values: [hexToBase64(transferEventHash)] }, // Topic 0: Event signature (Transfer)
+ {
+ values: [
+ hexToBase64(padHex(aliceAddress, { size: 32 })),
+ hexToBase64(padHex(charlieAddress, { size: 32 })),
+ ],
+ }, // Topic 1: from = Alice OR Charlie
],
}),
onLogTrigger
@@ -247,7 +266,8 @@ const initWorkflow = (config: Config) => {
To listen for **multiple event types** from a single contract, provide multiple event signature hashes in `Topics[0]`:
```typescript
-import { keccak256, toHex } from "viem"
+import { keccak256, toBytes } from "viem"
+import { hexToBase64 } from "@chainlink/cre-sdk"
const initWorkflow = (config: Config) => {
const network = getNetwork({
@@ -262,15 +282,15 @@ const initWorkflow = (config: Config) => {
const evmClient = new cre.capabilities.EVMClient(network.chainSelector.selector)
- const transferEventHash = keccak256(toHex("Transfer(address,address,uint256)"))
- const approvalEventHash = keccak256(toHex("Approval(address,address,uint256)"))
+ const transferEventHash = keccak256(toBytes("Transfer(address,address,uint256)"))
+ const approvalEventHash = keccak256(toBytes("Approval(address,address,uint256)"))
return [
cre.handler(
evmClient.logTrigger({
- addresses: [config.contractAddress],
+ addresses: [hexToBase64(config.contractAddress)],
topics: [
- { values: [transferEventHash, approvalEventHash] }, // Listen for Transfer OR Approval
+ { values: [hexToBase64(transferEventHash), hexToBase64(approvalEventHash)] }, // Listen for Transfer OR Approval
],
}),
onLogTrigger
@@ -284,6 +304,46 @@ const initWorkflow = (config: Config) => {
To listen for the **same event from multiple contracts**, provide multiple addresses:
```typescript
+import { keccak256, toBytes } from "viem"
+import { hexToBase64 } from "@chainlink/cre-sdk"
+
+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 cre.capabilities.EVMClient(network.chainSelector.selector)
+
+ const transferEventHash = keccak256(toBytes("Transfer(address,address,uint256)"))
+
+ return [
+ cre.handler(
+ evmClient.logTrigger({
+ addresses: [hexToBase64("0xTokenA..."), hexToBase64("0xTokenB..."), hexToBase64("0xTokenC...")],
+ topics: [
+ { values: [hexToBase64(transferEventHash)] }, // Listen for Transfer events from any of these contracts
+ ],
+ }),
+ onLogTrigger
+ ),
+ ]
+}
+```
+
+#### Example 6: Filtering on uint256 indexed parameter
+
+To filter on indexed `uint256` or other numeric types, convert them to a 32-byte hex value:
+
+```typescript
+import { keccak256, toBytes, numberToHex, padHex } from "viem"
+import { hexToBase64 } from "@chainlink/cre-sdk"
+
const initWorkflow = (config: Config) => {
const network = getNetwork({
chainFamily: "evm",
@@ -297,14 +357,19 @@ const initWorkflow = (config: Config) => {
const evmClient = new cre.capabilities.EVMClient(network.chainSelector.selector)
- const transferEventHash = keccak256(toHex("Transfer(address,address,uint256)"))
+ // Example: event ValueChanged(address indexed user, uint256 indexed newValue)
+ const eventHash = keccak256(toBytes("ValueChanged(address,uint256)"))
+ const userAddress = padHex("0xUser..." as `0x${string}`, { size: 32 })
+ const targetValue = padHex(numberToHex(12345), { size: 32 })
return [
cre.handler(
evmClient.logTrigger({
- addresses: ["0xTokenA...", "0xTokenB...", "0xTokenC..."],
+ addresses: [hexToBase64(config.contractAddress)],
topics: [
- { values: [transferEventHash] }, // Listen for Transfer events from any of these contracts
+ { values: [hexToBase64(eventHash)] }, // Topic 0: Event signature
+ { values: [hexToBase64(userAddress)] }, // Topic 1: user address
+ { values: [hexToBase64(targetValue)] }, // Topic 2: newValue = 12345
],
}),
onLogTrigger
@@ -313,13 +378,19 @@ const initWorkflow = (config: Config) => {
}
```
+
+
### Confidence level
You can set the block confirmation level by adding the `confidence` field to the trigger configuration:
```typescript
evmClient.logTrigger({
- addresses: [config.contractAddress],
+ addresses: [hexToBase64(config.contractAddress)],
confidence: "CONFIDENCE_LEVEL_FINALIZED", // Wait for finalized blocks
})
```
@@ -432,7 +503,7 @@ const onLogTrigger = (runtime: Runtime, log: EVMLog): string => {