Skip to content

Commit d4d15b1

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 d4d15b1

File tree

2 files changed

+176
-64
lines changed

2 files changed

+176
-64
lines changed

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

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,13 @@ 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.,
509+
`"0x7b79..."`) directly without `encodeCallMsg()`, the hex characters are silently interpreted as base64, producing
510+
**incorrect bytes with no error**. Always use this helper to ensure correct encoding.
511+
</Aside>
506512

507513
**Signature:**
508514

@@ -520,6 +526,12 @@ interface EncodeCallMsgPayload {
520526
}
521527
```
522528

529+
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:
530+
531+
```
532+
Invalid hex in 'to' field of CallMsg: Invalid hex string: missing '0x' prefix...
533+
```
534+
523535
**Usage:**
524536
525537
```typescript
@@ -708,6 +720,45 @@ const report = runtime
708720

709721
---
710722

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

713764
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: 124 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,35 @@ 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
47+
directly without encoding, the hex characters are silently interpreted as base64, producing **incorrect bytes with no
48+
error**. Use `logTriggerConfig()` (recommended) or `hexToBase64()` to ensure correct encoding.
49+
</Aside>
50+
51+
<details>
52+
<summary>Manual configuration with hexToBase64()</summary>
53+
54+
If you need more control, you can encode values manually using `hexToBase64()`:
55+
56+
```typescript
57+
import { EVMClient, getNetwork, hexToBase64 } from "@chainlink/cre-sdk"
58+
import { keccak256, toBytes } from "viem"
59+
60+
const evmClient = new EVMClient(network.chainSelector.selector)
61+
62+
const transferEventHash = keccak256(toBytes("Transfer(address,address,uint256)"))
63+
4264
const trigger = evmClient.logTrigger({
43-
addresses: [hexToBase64("0x123...abc")],
65+
addresses: [hexToBase64("0x1234567890123456789012345678901234567890")],
4466
topics: [
4567
{
4668
values: [hexToBase64(transferEventHash)],
@@ -49,14 +71,95 @@ const trigger = evmClient.logTrigger({
4971
})
5072
```
5173

52-
## Configuration
74+
</details>
5375

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

56159
| <div style="width: 100px;">Field</div> | <div style="width: 140px;">Type</div> | Description |
57160
| -------------------------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
58161
| `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()`. |
162+
| `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()`. |
60163
| `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> |
61164

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

67170
### `TopicValues`
68171

69-
The `topics` array uses a special format for filtering events:
70-
71172
| Field | Type | Description |
72173
| -------- | ---------- | --------------------------------------------------------------------------------------- |
73174
| `values` | `string[]` | Array of possible values for a topic. **Must be base64 encoded** using `hexToBase64()`. |
@@ -79,52 +180,6 @@ The `topics` array uses a special format for filtering events:
79180
- **`topics[2]`**: Optional. Values for the second indexed argument. Can be empty (wildcard).
80181
- **`topics[3]`**: Optional. Values for the third indexed argument. Can be empty (wildcard).
81182

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-
128183
## Payload
129184

130185
The payload passed to your callback function is an `EVMLog` object containing the log data.
@@ -221,11 +276,12 @@ import {
221276
handler,
222277
bytesToHex,
223278
getNetwork,
279+
logTriggerConfig,
224280
Runner,
225-
hexToBase64,
226281
type Runtime,
227282
type EVMLog,
228283
} from "@chainlink/cre-sdk"
284+
import { keccak256, toBytes } from "viem"
229285
230286
type Config = {
231287
chainSelectorName: string
@@ -262,11 +318,16 @@ const initWorkflow = (config: Config) => {
262318
263319
const evmClient = new EVMClient(network.chainSelector.selector)
264320
321+
const transferEventHash = keccak256(toBytes("Transfer(address,address,uint256)"))
322+
265323
return [
266324
handler(
267-
evmClient.logTrigger({
268-
addresses: [hexToBase64(config.contractAddress)],
269-
}),
325+
evmClient.logTrigger(
326+
logTriggerConfig({
327+
addresses: [config.contractAddress as `0x${string}`],
328+
topics: [[transferEventHash]],
329+
})
330+
),
270331
onLogTrigger
271332
),
272333
]

0 commit comments

Comments
 (0)