Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions docs/components/FinalityTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { useState, useEffect } from 'react'
import { GITHUB_REPOSITORIES } from '../constants/config'

interface NetworkEntry {
name: string
chainSelector: number
finalityTagEnabled?: boolean
finalityConfirmations?: number
minBlockConfirmations?: number
}

interface FinalityRow {
name: string
chainSelector: string
finalityMethod: string
minBlockConfirmations: number
}

function parseFinalityMethod(entry: NetworkEntry): string {
if (entry.finalityTagEnabled === true) {
return 'finalized'
}
if (typeof entry.finalityConfirmations === 'number' && entry.finalityConfirmations > 0) {
return String(entry.finalityConfirmations)
}
return 'none'
}

function parseRows(data: unknown): FinalityRow[] {
if (typeof data !== 'object' || data === null || Array.isArray(data)) return []

const rows: FinalityRow[] = []

for (const item of Object.values(data as Record<string, unknown>)) {
if (typeof item !== 'object' || item === null) continue

const entry = item as Record<string, unknown>
const name = typeof entry.name === 'string' ? entry.name.trim() : null
if (!name) continue

const chainSelector = entry.chainSelector != null ? String(entry.chainSelector) : '—'
const minBlockConfirmations = typeof entry.minBlockConfirmations === 'number' ? entry.minBlockConfirmations : 1

rows.push({
name,
chainSelector,
finalityMethod: parseFinalityMethod(entry as unknown as NetworkEntry),
minBlockConfirmations,
})
}

return rows.filter(row => row.finalityMethod !== 'none').sort((a, b) => a.name.localeCompare(b.name))
}

export function FinalityTable() {
const [rows, setRows] = useState<FinalityRow[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)

useEffect(() => {
let isActive = true
const controller = new AbortController()

const fetchData = async () => {
try {
const response = await fetch(GITHUB_REPOSITORIES.V2_NETWORKS.MAINNET_NETWORKS_URL, {
signal: controller.signal,
})

if (!response.ok) {
throw new Error(`Failed to fetch: ${response.status} ${response.statusText}`)
}

const data = await response.json()

if (!isActive) return

setRows(parseRows(data))
setError(null)
} catch (err) {
if (err instanceof DOMException && err.name === 'AbortError') return
if (isActive) {
setError(err instanceof Error ? err.message : 'An unknown error occurred')
}
} finally {
if (isActive) setLoading(false)
}
}

fetchData()

return () => {
isActive = false
controller.abort()
}
}, [])

if (loading) {
return <div style={{ color: 'gray', padding: '8px 0' }}>Loading finality data...</div>
}

if (error) {
return <div style={{ color: '#dc2626', padding: '8px 0' }}>Error loading finality data: {error}</div>
}

if (rows.length === 0) {
return <div style={{ color: 'gray', padding: '8px 0' }}>No chain data available.</div>
}

return (
<div style={{ overflowX: 'auto' }}>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr>
{[
'SRC Blockchain',
'Chain selector',
'Finality (tag/finalityConfirmations)',
'minBlockConfirmations',
].map(col => (
<th
key={col}
style={{
textAlign: 'left',
padding: '8px 12px',
borderBottom: '1px solid var(--vocs-color_tableBorder)',
fontSize: '0.88rem',
fontWeight: 600,
color: 'var(--vocs-color_text)',
whiteSpace: 'nowrap',
}}
>
{col}
</th>
))}
</tr>
</thead>
<tbody>
{rows.map(row => (
<tr key={row.name}>
<td style={cellStyle}>{row.name}</td>
<td style={cellStyle}>{row.chainSelector}</td>
<td style={{ ...cellStyle, fontFamily: 'monospace' }}>{row.finalityMethod}</td>
<td style={cellStyle}>{row.minBlockConfirmations}</td>
</tr>
))}
</tbody>
</table>
</div>
)
}

const cellStyle: React.CSSProperties = {
padding: '8px 12px',
borderBottom: '1px solid var(--vocs-color_tableBorder)',
fontSize: '0.9rem',
color: 'var(--vocs-color_text)',
}
69 changes: 69 additions & 0 deletions docs/pages/integrate-concero/finality.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
title: Finality
description: Learn about finality in Concero and how to enable it for secure message delivery across chains.
---

import { FinalityTable } from '../../components/FinalityTable'

# Finality

Finality determines how many block confirmations the Relayer waits for before forwarding a message to the destination chain. Many blockchains support the `finalized` RPC tag. Where it is not supported, block depth is used to ensure safe finalization of L1 blocks and protection against [reorgs](https://blog.trailofbits.com/2023/08/23/the-engineers-guide-to-blockchain-finality/).

## How to enable finality

It is controlled via the `srcBlockConfirmations` field in `MessageRequest`.

```solidity
IConceroRouter.MessageRequest({
dstChainSelector: dstChainSelector,
srcBlockConfirmations: type(uint64).max, // enable finality
// ...
});
```

:::danger
Be careful when configuring the message. If the message is configured incorrectly (invalid `dstChainSelector` or finality specified for chains that do not support it), it will not be reverted but will simply not reach the dst chain.
:::

**There are three possible ways to specify finality when sending a message:**

- If `srcBlockConfirmations` is specified as 0, we use `minBlockConfirmations`
- If `srcBlockConfirmations` is specified as `type(uint64).max`, then we rely on the `finality` tag or on `finalityConfirmations`
- If `srcBlockConfirmations` is specified explicitly, we use this value `[1 : type(uint64).max - 1]`

:::info
`finalityConfirmations`: the number of blocks after which an L1 transaction is considered final (cannot be reverted and is protected against block reorganizations). Not all blockchains support the `finality` tag.
:::

:::info
`minBlockConfirmations`: the minimum number of block confirmations required before a log event can be consumed. When `srcBlockConfirmations` is set to 0, the protocol automatically applies this minimum value. If the chain does not define one, the protocol defaults to 1.
:::

## Configuration file

Finality data for a specific chain can be found in the [table below](#supported-chains) or in the [chain configuration](https://github.com/concero/v2-networks/blob/master/networks/mainnet.json) file on GitHub.

The configuration file contains either a `finalityTagEnabled` or a `finalityConfirmations` field. If `minBlockConfirmations` is not specified, it defaults to 1.

```json
"arbitrum": {
"name": "arbitrum",
"chainId": 42161,
"chainSelector": 42161,
// ...
"finalityTagEnabled": true,
"minBlockConfirmations": 3,
// ...
"finalityConfirmations": 100 // used if the "finalized" tag is not supported, or when it is safer to rely on block depth
}
```

:::warning
Block depth-based finality does not guarantee irreversibility. On chains without `finalityTagEnabled` support, chain reorgs remain possible and may affect message delivery.
:::

## Supported chains

Finality is only available on chains where `finalityTagEnabled` is true or `finalityConfirmations` is set in the protocol's [chain configuration](https://github.com/concero/v2-networks/blob/master/networks/mainnet.json).

<FinalityTable />
4 changes: 4 additions & 0 deletions vocs.config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ export default defineConfig({
text: 'Send & Receive a message',
link: '/integrate-concero/send-a-message',
},
{
text: 'Finality',
link: '/integrate-concero/finality',
},
{
text: 'Track a message',
link: '/integrate-concero/track-a-message',
Expand Down