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..c10f1f3a79f 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,27 @@ 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 +161,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 +179,12 @@ const initWorkflow = (config: Config) => { } ``` +{/* prettier-ignore */} #### Example 2: "AND" filtering @@ -168,7 +192,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 +208,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 +233,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 +249,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 +278,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 +294,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 +316,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 +369,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 +390,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 +515,7 @@ const onLogTrigger = (runtime: Runtime, log: EVMLog): string => {