Skip to content

Commit 52d6894

Browse files
committed
finality clarifications + custom block depth support for chain reads
1 parent 2ceb3bc commit 52d6894

File tree

10 files changed

+592
-184
lines changed

10 files changed

+592
-184
lines changed

reports/llms-report.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
{
2-
"startedAt": "2025-12-03T20:30:47.619Z",
2+
"startedAt": "2025-12-04T17:22:07.744Z",
33
"siteBase": "https://docs.chain.link",
44
"sections": [
55
{
66
"section": "cre-go",
77
"pagesProcessed": 84,
88
"outputPath": "src/content/cre/llms-full-go.txt",
9-
"bytes": 655924,
10-
"prevBytes": 655971,
11-
"deltaBytes": -47
9+
"bytes": 662040,
10+
"prevBytes": 655924,
11+
"deltaBytes": 6116
1212
},
1313
{
1414
"section": "cre-ts",
1515
"pagesProcessed": 79,
1616
"outputPath": "src/content/cre/llms-full-ts.txt",
17-
"bytes": 611343,
18-
"prevBytes": 611390,
19-
"deltaBytes": -47
17+
"bytes": 618373,
18+
"prevBytes": 611343,
19+
"deltaBytes": 7030
2020
},
2121
{
2222
"section": "vrf",
@@ -123,5 +123,5 @@
123123
"deltaBytes": 0
124124
}
125125
],
126-
"finishedAt": "2025-12-03T20:30:51.570Z"
126+
"finishedAt": "2025-12-04T17:22:11.997Z"
127127
}

src/config/sidebar.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,8 +419,9 @@ export const SIDEBAR: Partial<Record<Sections, SectionEntry[]>> = {
419419
url: "cre/concepts/typescript-wasm-runtime",
420420
},
421421
{
422-
title: "Finality and Confidence Levels",
422+
title: "Finality & Confidence Levels",
423423
url: "cre/concepts/finality",
424+
highlightAsCurrent: ["cre/concepts/finality-go", "cre/concepts/finality-ts"],
424425
},
425426
],
426427
},
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
---
2+
section: cre
3+
title: "Finality and Confidence Levels"
4+
date: Last Modified
5+
sdkLang: "go"
6+
pageId: "concepts-finality"
7+
metadata:
8+
description: "Understand how CRE handles blockchain finality across different chains, including confidence levels, finality tags, and block depth configurations."
9+
datePublished: "2025-12-04"
10+
lastModified: "2025-12-04"
11+
---
12+
13+
import { Aside, CopyText } from "@components"
14+
15+
Finality determines when a blockchain transaction is considered irreversible. Until a block is finalized, it could be reorganized (replaced by a different block if the chain temporarily forks), which means any data you read from it might change.
16+
17+
Different blockchains achieve finality in different ways and at different speeds. CRE abstracts these differences through **confidence levels**, allowing you to specify your finality requirements without needing to know the underlying chain-specific implementation.
18+
19+
You specify confidence levels in two places:
20+
21+
- **[EVM Log Triggers](/cre/reference/sdk/triggers/evm-log-trigger-go)** — The `confidence` parameter determines when the trigger fires based on block finality.
22+
- **[EVM Client contract reads](/cre/reference/sdk/evm-client-go)** — The `blockNumber` parameter lets you query data from `LATEST` or `FINALIZED` blocks, or any specific block number.
23+
24+
## Confidence levels
25+
26+
CRE provides three **abstraction levels** that work consistently across all chains. When you specify `FINALIZED` in your workflow, CRE automatically maps this to a chain-specific implementation—whether that's a native finality tag or a block depth threshold.
27+
28+
When reading from the blockchain or setting up triggers, you can specify one of three confidence levels:
29+
30+
| Confidence Level | Description | Use Case |
31+
| ---------------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
32+
| **`LATEST`** | The most recent block. No finality guarantees—the block could still be reorganized. | Non-critical, time-sensitive operations where speed matters more than certainty. |
33+
| **`SAFE`** | A block that is unlikely to be reorganized, but not yet fully finalized. | A balance between speed and security for most operations. |
34+
| **`FINALIZED`** | A block that is considered irreversible. This is the safest option. | Critical operations where you need absolute certainty the data won't change. |
35+
36+
<Aside type="note" title="Default behavior">
37+
If you don't specify a confidence level, CRE defaults to `SAFE` for triggers and `LATEST` for contract reads.
38+
</Aside>
39+
40+
### Choosing the right level
41+
42+
- **Use `FINALIZED`** when: Processing financial transactions, updating critical state, or when incorrect data could cause significant issues.
43+
- **Use `SAFE`** when: You need reasonable confidence without waiting for full finality. Good for most monitoring and alerting use cases.
44+
- **Use `LATEST`** when: Building real-time dashboards, price displays, or other applications where showing the most current data is more important than guaranteed accuracy.
45+
46+
### How CRE confidence levels map to chains
47+
48+
Here's how CRE determines which blocks to use for each confidence level:
49+
50+
#### SAFE and LATEST
51+
52+
When you specify `SAFE` or `LATEST` in your workflows, CRE uses the chain's native `safe` and `latest` block tags respectively for all supported chains.
53+
54+
#### FINALIZED
55+
56+
- **Finality tag**: Uses the chain's native `finalized` block tag
57+
- **Block depth**: Waits for a specified number of blocks to pass
58+
59+
| Chain | Finality Method | Block Depth |
60+
| ------------------------------- | --------------- | ----------- |
61+
| Arbitrum One / Arbitrum Sepolia | Finality tag ||
62+
| Avalanche / Avalanche Fuji | Finality tag ||
63+
| Base / Base Sepolia | Finality tag ||
64+
| BNB Chain / BNB Testnet | Finality tag ||
65+
| Ethereum / Ethereum Sepolia | Finality tag ||
66+
| OP Mainnet / OP Sepolia | Finality tag ||
67+
| Polygon / Polygon Amoy | Block depth | 500 |
68+
69+
## Custom block depths for chain reads
70+
71+
For chain reads, you're not limited to the three predefined confidence levels. The EVM Client also accepts **any explicit block number**, enabling you to:
72+
73+
- **Implement custom finality rules** tailored to your risk profile (e.g., "always wait 1,000 blocks")
74+
- **Meet regulatory requirements** that mandate fixed, auditable confirmation thresholds
75+
- **Query historical state** at specific past block heights for analysis or verification
76+
77+
This flexibility is available for all EVM read methods ([`CallContract`](/cre/reference/sdk/evm-client-go#callcontract), [`BalanceAt`](/cre/reference/sdk/evm-client-go#balanceat), [`FilterLogs`](/cre/reference/sdk/evm-client-go#filterlogs), etc.).
78+
79+
### When to use custom block depths
80+
81+
Use explicit block numbers when:
82+
83+
- **Regulatory compliance**: Your smart contract interactions require provable, fixed confirmation thresholds that must be documented for auditors
84+
- **Changing chain parameters**: The chain's finality definition may change over time, but your security model must remain constant
85+
- **Historical verification**: You need to verify state at a specific moment (e.g., for dispute resolution or forensic analysis)
86+
87+
### Implementation
88+
89+
You can pass any `*big.Int` directly as the `BlockNumber` parameter. The SDK accepts both special values (like `-2` for latest, `-3` for finalized) and positive integers for explicit block heights. See [Onchain Read](/cre/guides/workflow/using-evm-client/onchain-read-go#custom-block-depths) for examples.
90+
91+
<Aside type="note" title="Limitation: Chain reads only">
92+
Custom block numbers are **only available for EVM chain reads**. EVM Log Triggers must use the three confidence levels
93+
(`LATEST`, `SAFE`, `FINALIZED`).
94+
</Aside>
Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
section: cre
33
title: "Finality and Confidence Levels"
44
date: Last Modified
5+
sdkLang: "ts"
6+
pageId: "concepts-finality"
57
metadata:
68
description: "Understand how CRE handles blockchain finality across different chains, including confidence levels, finality tags, and block depth configurations."
7-
datePublished: "2025-12-03"
8-
lastModified: "2025-12-03"
9+
datePublished: "2025-12-04"
10+
lastModified: "2025-12-04"
911
---
1012

1113
import { Aside, CopyText } from "@components"
@@ -16,11 +18,13 @@ Different blockchains achieve finality in different ways and at different speeds
1618

1719
You specify confidence levels in two places:
1820

19-
- **[EVM Log Triggers](/cre/reference/sdk/triggers/evm-log-trigger)** — The `confidence` parameter determines when the trigger fires based on block finality.
20-
- **[EVM Client contract reads](/cre/reference/sdk/evm-client)** — The `blockNumber` parameter lets you query data from `LATEST` or `FINALIZED` blocks.
21+
- **[EVM Log Triggers](/cre/reference/sdk/triggers/evm-log-trigger-ts)** — The `confidence` parameter determines when the trigger fires based on block finality.
22+
- **[EVM Client contract reads](/cre/reference/sdk/evm-client-ts)** — The `blockNumber` parameter lets you query data from `LATEST` or `FINALIZED` blocks, or any specific block number.
2123

2224
## Confidence levels
2325

26+
CRE provides three **abstraction levels** that work consistently across all chains. When you specify `FINALIZED` in your workflow, CRE automatically maps this to a chain-specific implementation—whether that's a native finality tag or a block depth threshold.
27+
2428
When reading from the blockchain or setting up triggers, you can specify one of three confidence levels:
2529

2630
| Confidence Level | Description | Use Case |
@@ -39,15 +43,15 @@ When reading from the blockchain or setting up triggers, you can specify one of
3943
- **Use `SAFE`** when: You need reasonable confidence without waiting for full finality. Good for most monitoring and alerting use cases.
4044
- **Use `LATEST`** when: Building real-time dashboards, price displays, or other applications where showing the most current data is more important than guaranteed accuracy.
4145

42-
## How confidence levels work per chain
46+
### How CRE confidence levels map to chains
4347

4448
Here's how CRE determines which blocks to use for each confidence level:
4549

46-
### SAFE and LATEST
50+
#### SAFE and LATEST
4751

4852
When you specify `SAFE` or `LATEST` in your workflows, CRE uses the chain's native `safe` and `latest` block tags respectively for all supported chains.
4953

50-
### FINALIZED
54+
#### FINALIZED
5155

5256
- **Finality tag**: Uses the chain's native `finalized` block tag
5357
- **Block depth**: Waits for a specified number of blocks to pass
@@ -61,3 +65,30 @@ When you specify `SAFE` or `LATEST` in your workflows, CRE uses the chain's nati
6165
| Ethereum / Ethereum Sepolia | Finality tag ||
6266
| OP Mainnet / OP Sepolia | Finality tag ||
6367
| Polygon / Polygon Amoy | Block depth | 500 |
68+
69+
## Custom block depths for chain reads
70+
71+
For chain reads, you're not limited to the three predefined confidence levels. The EVM Client also accepts **any explicit block number**, enabling you to:
72+
73+
- **Implement custom finality rules** tailored to your risk profile (e.g., "always wait 1,000 blocks")
74+
- **Meet regulatory requirements** that mandate fixed, auditable confirmation thresholds
75+
- **Query historical state** at specific past block heights for analysis or verification
76+
77+
This flexibility is available for all EVM read methods ([`CallContract`](/cre/reference/sdk/evm-client-ts#callcontract), [`BalanceAt`](/cre/reference/sdk/evm-client-ts#balanceat), [`FilterLogs`](/cre/reference/sdk/evm-client-ts#filterlogs), etc.).
78+
79+
### When to use custom block depths
80+
81+
Use explicit block numbers when:
82+
83+
- **Regulatory compliance**: Your smart contract interactions require provable, fixed confirmation thresholds that must be documented for auditors
84+
- **Changing chain parameters**: The chain's finality definition may change over time, but your security model must remain constant
85+
- **Historical verification**: You need to verify state at a specific moment
86+
87+
### Implementation
88+
89+
Block numbers must be provided as `BigIntJson` objects. See [Onchain Read](/cre/guides/workflow/using-evm-client/onchain-read-ts#custom-block-depths) for the conversion pattern and examples.
90+
91+
<Aside type="note" title="Limitation: Chain reads only">
92+
Custom block numbers are **only available for EVM chain reads**. EVM Log Triggers must use the three confidence levels
93+
(`LATEST`, `SAFE`, `FINALIZED`).
94+
</Aside>

src/content/cre/guides/workflow/using-evm-client/onchain-read-go.mdx

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,9 @@ When calling contract read methods, you must specify a block number. There are t
153153

154154
### Using magic numbers
155155

156-
- **Finalized block**: Use `big.NewInt(-3)` to read from the latest finalized block
156+
- **Finalized block**: Use `big.NewInt(-3)` to read from the latest finalized block (recommended for production)
157157
- **Latest block**: Use `big.NewInt(-2)` to read from the latest block
158-
- **Specific block**: Use `big.NewInt(blockNumber)` to read from a specific block
158+
- **Specific block**: Use `big.NewInt(blockNumber)` to read from a specific historical block
159159

160160
### Using rpc constants (alternative)
161161

@@ -174,7 +174,57 @@ reqBlockNumber := big.NewInt(rpc.LatestBlockNumber.Int64())
174174
reqBlockNumber := big.NewInt(rpc.FinalizedBlockNumber.Int64())
175175
```
176176

177-
Both approaches are equivalent - use whichever you find more readable in your code.
177+
### Custom block depths
178+
179+
For use cases requiring fixed confirmation thresholds (e.g., regulatory compliance), you can calculate a custom block depth:
180+
181+
```go
182+
import (
183+
pb "github.com/smartcontractkit/chainlink-protos/cre/go/values/pb"
184+
)
185+
186+
// Helper to convert protobuf BigInt to standard library big.Int
187+
func pbBigIntToBigInt(pb *pb.BigInt) *big.Int {
188+
if pb == nil {
189+
return nil
190+
}
191+
result := new(big.Int).SetBytes(pb.AbsVal)
192+
if pb.Sign < 0 {
193+
result.Neg(result)
194+
}
195+
return result
196+
}
197+
198+
// Read from 500 blocks ago for custom finality
199+
latestHeaderPromise := evmClient.HeaderByNumber(runtime, &evm.HeaderByNumberRequest{})
200+
latestHeader, err := latestHeaderPromise.Await()
201+
if err != nil {
202+
return nil, fmt.Errorf("failed to get latest block: %w", err)
203+
}
204+
205+
// Convert protobuf BigInt to standard library big.Int
206+
latestBlockNum := pbBigIntToBigInt(latestHeader.Header.BlockNumber)
207+
customDepth := big.NewInt(500)
208+
customBlock := new(big.Int).Sub(latestBlockNum, customDepth)
209+
210+
// Use the custom block number with the contract binding
211+
valuePromise := storageContract.Get(runtime, customBlock)
212+
value, err := valuePromise.Await()
213+
if err != nil {
214+
return nil, fmt.Errorf("failed to read from custom block: %w", err)
215+
}
216+
```
217+
218+
**Understanding the conversion:**
219+
220+
The `pbBigIntToBigInt` helper converts the SDK's protobuf `*pb.BigInt` type (which stores the value as `AbsVal []byte` and `Sign int64`) to Go's standard library `*big.Int`. This allows you to perform arithmetic operations like subtracting 500 blocks.
221+
222+
<Aside type="note" title="SDK enhancement planned">
223+
The SDK team is working on a built-in helper to simplify this conversion. Until then, use the `pbBigIntToBigInt`
224+
helper shown above.
225+
</Aside>
226+
227+
See [Finality and Confidence Levels](/cre/concepts/finality-go) for more details on when to use custom block depths.
178228

179229
## Complete example
180230

src/content/cre/guides/workflow/using-evm-client/onchain-read-ts.mdx

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ When calling `callContract()`, you can specify which block to read from:
168168

169169
- **`LAST_FINALIZED_BLOCK_NUMBER`**: Read from the last finalized block (recommended for production)
170170
- **`LATEST_BLOCK_NUMBER`**: Read from the latest block
171-
- **Specific block**: Use an object with `{ absVal: "base64EncodedNumber", sign: "1" }` format
171+
- **Custom block number**: Use a `BigIntJson` object for custom finality depths or historical queries
172172

173173
```typescript
174174
import { LAST_FINALIZED_BLOCK_NUMBER, LATEST_BLOCK_NUMBER } from "@chainlink/cre-sdk"
@@ -186,6 +186,68 @@ const contractCall = evmClient.callContract(runtime, {
186186
}).result()
187187
```
188188

189+
#### Custom block depths
190+
191+
For use cases requiring fixed confirmation thresholds (e.g., regulatory compliance) or historical state verification, you can specify an exact block number.
192+
193+
**Example 1 - Read from a specific historical block**:
194+
195+
```typescript
196+
const historicalBlock = 9767655n
197+
const contractCall = evmClient.callContract(runtime, {
198+
call: encodeCallMsg({...}),
199+
blockNumber: {
200+
absVal: Buffer.from(historicalBlock.toString(16).padStart(2, '0'), 'hex').toString('base64'),
201+
},
202+
}).result()
203+
```
204+
205+
**Example 2 - Read from 500 blocks ago for latest block**:
206+
207+
```typescript
208+
// Helper to convert protobuf BigInt to native bigint
209+
const protoBigIntToBigInt = (pb: { absVal: Uint8Array; sign: bigint }): bigint => {
210+
let result = 0n
211+
for (const byte of pb.absVal) {
212+
result = (result << 8n) + BigInt(byte)
213+
}
214+
return pb.sign < 0n ? -result : result
215+
}
216+
217+
// Example: Read from 500 blocks ago for custom finality
218+
const latestHeader = evmClient.headerByNumber(runtime, {}).result()
219+
if (!latestHeader.header?.blockNumber) {
220+
throw new Error("Failed to get latest block number")
221+
}
222+
223+
const latestBlockNum = protoBigIntToBigInt(latestHeader.header.blockNumber)
224+
const customBlock = latestBlockNum - 500n
225+
226+
const contractCall = evmClient.callContract(runtime, {
227+
call: encodeCallMsg({...}),
228+
blockNumber: {
229+
absVal: Buffer.from(customBlock.toString(16).padStart(2, '0'), 'hex').toString('base64'),
230+
},
231+
}).result()
232+
```
233+
234+
**Understanding the conversions:**
235+
236+
1. **Protobuf `BigInt` to native `bigint`:** The `protoBigIntToBigInt` helper converts the SDK's protobuf `BigInt` type (which stores the value as `absVal: Uint8Array`) to JavaScript's native `bigint`. This allows you to perform arithmetic like subtracting 500 blocks.
237+
238+
1. **Native `bigint` to `BigIntJson`:** To pass a native `bigint` back to the SDK:
239+
- `blockNum.toString(16)` converts to hexadecimal
240+
- `.padStart(2, '0')` ensures even-length hex string (required by `Buffer.from`)
241+
- `Buffer.from(..., 'hex')` creates a byte array from the hex
242+
- `.toString('base64')` converts to base64 format required by `BigIntJson.absVal`
243+
244+
<Aside type="note" title="SDK enhancements planned">
245+
The SDK team is working on built-in helpers for both conversions (protobuf `BigInt` ↔ native `bigint` and `bigint`
246+
`BigIntJson`). Until then, use the patterns shown above.
247+
</Aside>
248+
249+
See [Finality and Confidence Levels](/cre/concepts/finality-ts) for more details on when to use custom block depths.
250+
189251
### Encoding call messages with `encodeCallMsg()`
190252

191253
The `encodeCallMsg()` helper converts your hex-formatted call data into the base64 format required by the EVM capability:

0 commit comments

Comments
 (0)