Skip to content

Commit e2095a5

Browse files
committed
docs: document encodeCallMsg validation and logTriggerConfig helper
- Add caution about silent data corruption when bypassing encodeCallMsg - Document field-context error messages in encodeCallMsg - Add logTriggerConfig() as recommended approach for log trigger setup - Restructure EVM log trigger docs to prioritize logTriggerConfig over manual hexToBase64
1 parent 4fc348c commit e2095a5

2 files changed

Lines changed: 173 additions & 64 deletions

File tree

src/content/cre/reference/sdk/evm-client-ts.mdx

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,11 @@ The TypeScript SDK provides several helper functions for working with the EVM Cl
502502

503503
### `encodeCallMsg()`
504504

505-
Encodes a call message payload into a `CallMsgJson`, expected by the EVM capability.
505+
Encodes a call message payload into a `CallMsgJson`, expected by the EVM capability. This helper converts `0x`-prefixed hex strings to the base64 format required by the protobuf `CallMsg` structure.
506+
507+
<Aside type="caution" title="Always use encodeCallMsg">
508+
The `CallMsg` proto uses `bytes` fields, which are base64-encoded in JSON form. If you pass raw hex strings (e.g., `"0x7b79..."`) directly without `encodeCallMsg()`, the hex characters are silently interpreted as base64, producing **incorrect bytes with no error**. Always use this helper to ensure correct encoding.
509+
</Aside>
506510

507511
**Signature:**
508512

@@ -520,6 +524,12 @@ interface EncodeCallMsgPayload {
520524
}
521525
```
522526

527+
All fields must be valid `0x`-prefixed hex strings. If any field contains invalid hex, the function throws an error identifying which field is invalid:
528+
529+
```
530+
Invalid hex in 'to' field of CallMsg: Invalid hex string: missing '0x' prefix...
531+
```
532+
523533
**Usage:**
524534
525535
```typescript
@@ -708,6 +718,45 @@ const report = runtime
708718

709719
---
710720

721+
### `logTriggerConfig()`
722+
723+
Creates a validated log trigger configuration from hex-encoded addresses and topics. This helper converts hex values to the base64-encoded format expected by `evmClient.logTrigger()`, validates byte lengths (20 bytes for addresses, 32 bytes for topics), and formats the confidence level.
724+
725+
**Signature:**
726+
727+
```typescript
728+
function logTriggerConfig(opts: LogTriggerConfigOptions): FilterLogTriggerRequestJson
729+
```
730+
731+
**Parameters:**
732+
733+
| Field | Type | Description |
734+
| ------------ | --------------------------------------- | ------------------------------------------------------------------------------ |
735+
| `addresses` | `Hex[]` | **Required.** EVM addresses to monitor (`0x`-prefixed hex, 20 bytes each). |
736+
| `topics` | `Hex[][]` | Optional. Up to 4 arrays of topic values (`0x`-prefixed hex, 32 bytes each). |
737+
| `confidence` | `'SAFE'` \| `'LATEST'` \| `'FINALIZED'` | Optional. Block confirmation level. Defaults to `SAFE`. |
738+
739+
**Usage:**
740+
741+
```typescript
742+
import { logTriggerConfig } from "@chainlink/cre-sdk"
743+
import { keccak256, toBytes } from "viem"
744+
745+
const transferEvent = keccak256(toBytes("Transfer(address,address,uint256)"))
746+
747+
const trigger = evmClient.logTrigger(
748+
logTriggerConfig({
749+
addresses: ["0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9"],
750+
topics: [[transferEvent]],
751+
confidence: "LATEST",
752+
})
753+
)
754+
```
755+
756+
See [EVM Log Trigger](/cre/reference/sdk/triggers/evm-log-trigger-ts) for the full configuration reference.
757+
758+
---
759+
711760
### `LAST_FINALIZED_BLOCK_NUMBER`
712761

713762
A constant representing the last finalized block number for use in `callContract()` and similar methods.

src/content/cre/reference/sdk/triggers/evm-log-trigger-ts.mdx

Lines changed: 123 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,10 @@ The EVM Log Trigger fires when a specific log (event) is emitted by an onchain s
1616

1717
## Creating the trigger
1818

19-
<Aside type="note" title="Base64 Encoding Required">
20-
**All addresses and topic values must be base64 encoded** using the `hexToBase64()` helper function from the CRE SDK.
21-
While the workflow simulator accepts raw hex strings for convenience during development, **deployed workflows require
22-
base64 encoding**. Always use `hexToBase64()` on addresses and topic values to ensure your workflow works in both
23-
simulation and production.
24-
</Aside>
19+
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:
2520

2621
```typescript
27-
import { EVMClient, getNetwork, hexToBase64 } from "@chainlink/cre-sdk"
22+
import { EVMClient, getNetwork, logTriggerConfig } from "@chainlink/cre-sdk"
2823
import { keccak256, toBytes } from "viem"
2924

3025
// Create an EVMClient instance with a chain selector
@@ -39,8 +34,33 @@ const evmClient = new EVMClient(network.chainSelector.selector)
3934
// Create a log trigger with address and event signature
4035
const transferEventHash = keccak256(toBytes("Transfer(address,address,uint256)"))
4136

37+
const trigger = evmClient.logTrigger(
38+
logTriggerConfig({
39+
addresses: ["0x1234567890123456789012345678901234567890"],
40+
topics: [[transferEventHash]],
41+
})
42+
)
43+
```
44+
45+
<Aside type="caution" title="Base64 encoding is required by logTrigger()">
46+
The `logTrigger()` method expects base64-encoded addresses and topics, not hex strings. If you pass raw hex strings directly without encoding, the hex characters are silently interpreted as base64, producing **incorrect bytes with no error**. Use `logTriggerConfig()` (recommended) or `hexToBase64()` to ensure correct encoding.
47+
</Aside>
48+
49+
<details>
50+
<summary>Manual configuration with hexToBase64()</summary>
51+
52+
If you need more control, you can encode values manually using `hexToBase64()`:
53+
54+
```typescript
55+
import { EVMClient, getNetwork, hexToBase64 } from "@chainlink/cre-sdk"
56+
import { keccak256, toBytes } from "viem"
57+
58+
const evmClient = new EVMClient(network.chainSelector.selector)
59+
60+
const transferEventHash = keccak256(toBytes("Transfer(address,address,uint256)"))
61+
4262
const trigger = evmClient.logTrigger({
43-
addresses: [hexToBase64("0x123...abc")],
63+
addresses: [hexToBase64("0x1234567890123456789012345678901234567890")],
4464
topics: [
4565
{
4666
values: [hexToBase64(transferEventHash)],
@@ -49,14 +69,94 @@ const trigger = evmClient.logTrigger({
4969
})
5070
```
5171

52-
## Configuration
72+
</details>
73+
74+
## `logTriggerConfig()` helper
75+
76+
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:
77+
78+
- **Base64 conversion**: Converts `0x`-prefixed hex to the base64 encoding required by the proto
79+
- **Byte-length validation**: Verifies addresses are 20 bytes and topics are 32 bytes
80+
- **Confidence formatting**: Accepts short names (`'LATEST'`, `'SAFE'`, `'FINALIZED'`) instead of the full `CONFIDENCE_LEVEL_` prefix
81+
82+
**Signature:**
83+
84+
```typescript
85+
function logTriggerConfig(opts: LogTriggerConfigOptions): FilterLogTriggerRequestJson
86+
```
87+
88+
**Parameters:**
89+
90+
```typescript
91+
interface LogTriggerConfigOptions {
92+
/** EVM addresses to monitor — hex strings with 0x prefix (20 bytes each) */
93+
addresses: Hex[]
94+
/** Topic filters — array of up to 4 arrays of hex topic values (32 bytes each).
95+
* - topics[0]: event signatures (keccak256 hashes), at least one required
96+
* - topics[1]: possible values for first indexed arg (optional)
97+
* - topics[2]: possible values for second indexed arg (optional)
98+
* - topics[3]: possible values for third indexed arg (optional)
99+
*/
100+
topics?: Hex[][]
101+
/** Confidence level for log finality. Defaults to SAFE. */
102+
confidence?: "SAFE" | "LATEST" | "FINALIZED"
103+
}
104+
```
105+
106+
**Validation errors:**
107+
108+
The helper throws descriptive errors for common mistakes:
109+
110+
- Missing `0x` prefix on addresses or topics
111+
- Addresses that aren't exactly 20 bytes
112+
- Topics that aren't exactly 32 bytes
113+
- Error messages include the index of the invalid value (e.g., `"Invalid address at index 1: ..."`)
114+
115+
**Example with topic filtering:**
116+
117+
```typescript
118+
import { logTriggerConfig } from "@chainlink/cre-sdk"
119+
import { keccak256, toBytes, padHex } from "viem"
120+
121+
const transferEventHash = keccak256(toBytes("Transfer(address,address,uint256)"))
122+
const fromAddress = padHex("0xabcdef1234567890abcdef1234567890abcdef12", { size: 32 })
123+
124+
const trigger = evmClient.logTrigger(
125+
logTriggerConfig({
126+
addresses: ["0x1234567890123456789012345678901234567890"],
127+
topics: [
128+
// Topic 0: Event signature (Transfer event) - already 32 bytes
129+
[transferEventHash],
130+
// Topic 1: From address (indexed parameter 1) - must pad from 20 to 32 bytes
131+
[fromAddress],
132+
// Topic 2: Omit for wildcard (any "to" address)
133+
],
134+
confidence: "FINALIZED",
135+
})
136+
)
137+
```
138+
139+
<Aside type="note" title="Simplified configuration">
140+
For simple use cases, you can omit `topics` and `confidence`. The trigger will fire for any
141+
event from the specified addresses using the default "SAFE" confirmation level.
142+
</Aside>
143+
144+
<Aside type="caution" title="Topic values must be 32 bytes">
145+
EVM logs always store indexed parameters as **32-byte values**. When filtering on topics 1, 2, or 3, pad your values to 32 bytes using `padHex(value, { size: 32 })` from viem (e.g., addresses are 20 bytes and must be padded). `logTriggerConfig()` validates the byte length and throws an error if a topic is not exactly 32 bytes.
53146

54-
The `logTrigger()` method accepts a configuration object with the following fields:
147+
Topic 0 (the event signature from `keccak256`) is already 32 bytes and doesn't need padding.
148+
</Aside>
149+
150+
---
151+
152+
## Raw configuration reference
153+
154+
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:
55155

56156
| <div style="width: 100px;">Field</div> | <div style="width: 140px;">Type</div> | Description |
57157
| -------------------------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
58158
| `addresses` | `string[]` | **Required.** A list of contract addresses to monitor. **Must be base64 encoded** using `hexToBase64()`. At least one address is required. |
59-
| `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()`. |
159+
| `topics` | `TopicValues[]` | 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()`. |
60160
| `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> |
61161

62162
<Aside type="note" title="Finality details">
@@ -66,8 +166,6 @@ The `logTrigger()` method accepts a configuration object with the following fiel
66166

67167
### `TopicValues`
68168

69-
The `topics` array uses a special format for filtering events:
70-
71169
| Field | Type | Description |
72170
| -------- | ---------- | --------------------------------------------------------------------------------------- |
73171
| `values` | `string[]` | Array of possible values for a topic. **Must be base64 encoded** using `hexToBase64()`. |
@@ -79,52 +177,6 @@ The `topics` array uses a special format for filtering events:
79177
- **`topics[2]`**: Optional. Values for the second indexed argument. Can be empty (wildcard).
80178
- **`topics[3]`**: Optional. Values for the third indexed argument. Can be empty (wildcard).
81179

82-
<Aside type="caution" title="Topic values must be padded to 32 bytes and base64 encoded">
83-
EVM logs always store indexed parameters as **32-byte values**. When filtering on topics 1, 2, or 3:
84-
85-
1. **Pad your values to 32 bytes** using `padHex(value, { size: 32 })` from viem (e.g., addresses are 20 bytes and must be padded)
86-
1. **Convert to base64** using `hexToBase64()` from the CRE SDK
87-
88-
If you don't pad correctly, your filter won't match the actual log topics and the trigger will not fire.
89-
90-
Topic 0 (the event signature from `keccak256`) is already 32 bytes and doesn't need padding.
91-
92-
</Aside>
93-
94-
**Example:**
95-
96-
```typescript
97-
import { hexToBase64 } from "@chainlink/cre-sdk"
98-
import { keccak256, toBytes, padHex } from "viem"
99-
100-
const transferEventHash = keccak256(toBytes("Transfer(address,address,uint256)"))
101-
const fromAddress = "0xabcdef..." as `0x${string}`
102-
103-
const trigger = evmClient.logTrigger({
104-
addresses: [hexToBase64("0x1234567890abcdef...")],
105-
topics: [
106-
// Topic 0: Event signature (Transfer event) - already 32 bytes
107-
{
108-
values: [hexToBase64(transferEventHash)],
109-
},
110-
// Topic 1: From address (indexed parameter 1) - must pad from 20 to 32 bytes
111-
{
112-
values: [hexToBase64(padHex(fromAddress, { size: 32 }))],
113-
},
114-
// Topic 2: Empty (wildcard for any "to" address)
115-
{
116-
values: [],
117-
},
118-
],
119-
confidence: "CONFIDENCE_LEVEL_FINALIZED",
120-
})
121-
```
122-
123-
<Aside type="note" title="Simplified configuration">
124-
In the demo workflow and for simple use cases, you can omit `topics` and `confidence`. The trigger will fire for any
125-
event from the specified addresses using the default "SAFE" confirmation level.
126-
</Aside>
127-
128180
## Payload
129181

130182
The payload passed to your callback function is an `EVMLog` object containing the log data.
@@ -221,11 +273,12 @@ import {
221273
handler,
222274
bytesToHex,
223275
getNetwork,
276+
logTriggerConfig,
224277
Runner,
225-
hexToBase64,
226278
type Runtime,
227279
type EVMLog,
228280
} from "@chainlink/cre-sdk"
281+
import { keccak256, toBytes } from "viem"
229282
230283
type Config = {
231284
chainSelectorName: string
@@ -262,11 +315,18 @@ const initWorkflow = (config: Config) => {
262315
263316
const evmClient = new EVMClient(network.chainSelector.selector)
264317
318+
const transferEventHash = keccak256(
319+
toBytes("Transfer(address,address,uint256)")
320+
)
321+
265322
return [
266323
handler(
267-
evmClient.logTrigger({
268-
addresses: [hexToBase64(config.contractAddress)],
269-
}),
324+
evmClient.logTrigger(
325+
logTriggerConfig({
326+
addresses: [config.contractAddress as `0x${string}`],
327+
topics: [[transferEventHash]],
328+
})
329+
),
270330
onLogTrigger
271331
),
272332
]

0 commit comments

Comments
 (0)