Abstract
This paper describes a centralized bridge for ERC-20 tokens between Ethereum and Canton.
The protocol implements a lock/mint and burn/release model coordinated by an off‑chain relayer. The relayer is responsible for observing source‑chain events, enforcing finality, preventing replay, and executing the corresponding action on the destination chain.
The design goals are practical operability and correctness in the presence of real‑world failures: node/RPC outages, relayer restarts, chain reorgs, and partial execution. The system explicitly prioritizes deterministic processing, recoverability, and auditability (database + logs), while keeping the on‑chain components minimal and stable.
Design Principles
- Determinism over speed — prefer conservative finality windows over low latency.
- Fail‑safe defaults — unknown states pause processing, not continue.
- Recoverable by construction — every state can be advanced or retried from the database alone.
- Single source of truth — the database is the operational authority; chains are event sources.
- Minimal on‑chain logic — defer complexity to the relayer where iteration is cheap.
1 Introduction
1.1 Problem Statement
Moving assets between different chains is a complex task, which often requires usage of centralized exchanges. This complicates user onboarding to the new chains, leads to fragmented liquidity, limits possible cross-chain farming strategies, leads to less accurate pricing of on-chain assets due to smaller arbitrage options.
1.2 Scope
The protocol bridges ERC-20 between Ethereum and Canton, for example ETH on Ethereum to cETH on Canton. It is centralized by design, with a trusted relayer enforcing limits and correctness, with the future plans of making it decentralized.
In-scope:
- EVM deposit ingestion (event scanning and reorg recovery).
- Canton withdrawal ingestion (ledger streaming to relayer).
- Idempotency, retries, and state tracking via a database.
- Operational controls (timeouts, observability, manual recovery tools).
1.3 Prior Work
Bridges traditionally use lock/mint and burn/release patterns with a trusted attestation layer. The presented design follows this paradigm while focusing on deterministic backend processing, explicit checkpoints, and reorg recovery. It also borrows operational patterns from centralized settlement systems: strict state transitions, idempotency, and audit‑friendly logs.
2 Protocol Overview
2.1 Components
Ethereum (Solidity)
BridgeVault: holds ERC-20 token deposits and releases on withdrawal.BridgeRouter: user entry point; emitsDepositandWithdrawevents, enforces limits and fee policy, and manages roles.
Canton (Daml)
BridgeRouter: mints/burns ERC-20 tokens on Canton when called by the relayer and emits ledger events.cETH (CIP‑56): wrapped asset representation on Canton with canonical token semantics.
Backend (Python Relayer)
- EVM watcher: scans
Depositevents, persists checkpoints, and triggers mint on Canton. - Canton watcher: streams withdraw requests and triggers
finalizeWithdrawon Ethereum. - State store:
processed_messagesandchain_checkpointsensure idempotency and recovery. - CLI tools: status view and manual deposit ingestion for operational recovery.
Database (Postgres)
processed_messages: one row permessageId, representing the bridge pipeline state and all correlation fields needed for auditing.chain_checkpoints: per watcher/stream checkpoint used for resumability and reorg detection.
2.1.1 processed_messages Schema
| Column | Description |
|---|---|
message_id | Unique cross-chain message identifier (0x hex) |
status | DETECTED, PROCESSING, COMPLETED, FAILED |
tx_hash_in | Source chain tx hash (EVM) or contract ID (Canton) |
tx_hash_out | Destination chain tx hash |
block_number | EVM block where deposit was found |
log_index | EVM log index within block |
src_input_token | Source token address |
src_input_amount | Amount in base units |
src_chain_id | Source chain ID |
dst_chain_id | Destination chain ID |
dst_output_token | Destination token identifier |
recipient | Destination address/party |
created_at | Row insertion time |
updated_at | Last status change time |
2.1.2 chain_checkpoints Schema
| Column | Description |
|---|---|
chain | Namespace key (chain:router:dst) |
key | Checkpoint type (e.g., deposit_last_processed_block) |
value | Block number or ledger offset |
block_hash | Hash at checkpoint height (reorg detection) |
updated_at | Last update time |
2.2 Message Model
Each transfer is identified by a unique messageId (bytes32). The relayer stores and checks this ID to prevent replay and to allow safe restarts. The messageId is carried end‑to‑end and is the primary correlation key in logs and the database.
The bridge treats a "message" as an immutable intent with an execution lifecycle. The relayer records:
- source chain identifier and source transaction hash,
- destination chain identifier and destination transaction hash (once executed),
- amounts and addresses/parties,
- timestamps for observability.
2.2.1 EVM Deposit Event (Solidity)
event Deposit(
bytes32 messageId,
address srcInputToken,
uint256 srcInputAmount,
uint256 srcChainID,
uint256 dstChainID,
bytes32 dstOutputToken,
uint256 dstMinOutputAmount,
bytes32 recipient
);2.2.2 EVM Withdraw Event (Solidity)
event Withdraw(
bytes32 indexed messageId,
address indexed token,
address indexed recipient,
uint256 amount
);2.2.3 Canton WithdrawEvent Template (Daml)
template WithdrawEvent
with
messageId : Text -- hex of bytes32
token : Text -- canton address
recipient : Text -- EVM address
amount : Decimal
relayer : Party
auditObservers : [Party]
where
signatory relayer
observer auditObservers2.2.4 Canonical Message Fields (conceptual)
The concrete schemas differ between EVM events and Canton ledger events, but the bridge normalizes them conceptually as:
| Field | Type | Description |
|---|---|---|
messageId | bytes32 | Unique identifier |
srcChainId | uint256 | Source chain ID |
dstChainId | uint256 | Destination chain ID |
token | address / Text | Token address (EVM) or identifier (Canton) |
sender | address / Party | Source address or party |
recipient | Party / address | Destination party or address |
amount | uint256 / Decimal | Amount in base units |
nonce / salt | optional | Uniqueness aid |
metadata | optional | Additional Reference data |
2.3 Trust Model
The relayer is trusted to:
- execute mint/burn and withdrawals correctly,
- enforce limits and validation rules,
- respect finality and reorg safety.
Users trust that the relayer will not mint unbacked assets and will not release funds without a valid Canton withdrawal. Operational controls, audit logs, and DB state transitions provide accountability.
In practice, this implies:
- The relayer's key management and operational procedures are part of the security boundary.
- The bridge is only as reliable as its RPC/data sources unless the relayer cross‑checks providers.
2.4 State Machine
The relayer uses a small set of statuses in processed_messages to drive idempotent processing.
| Status | Meaning | Typical Next Step |
|---|---|---|
DETECTED | Source event observed and validated; not yet executed on destination | Execute destination action |
PROCESSING | Destination action submitted or in-flight | Wait receipt/finality or retry |
COMPLETED | Destination action confirmed and finalized | No further action |
FAILED | Terminal failure requiring operator action | Manual retry / fix / cancel |
The state machine is intentionally simple. It can be extended later with finer granularity (e.g., SUBMITTED, MINED, FINALIZED) if operational requirements demand it.
┌─────────────────────┐
│ DETECTED │
│ (event ingested) │
└──────────┬──────────┘
│
submit destination action
│
▼
┌─────────────────────┐
┌─────────│ PROCESSING │─────────┐
│ │ (tx in‑flight) │ │
│ └──────────┬──────────┘ │
│ │ │
tx reverted / receipt + finality unrecoverable
timeout/nonce error │ error
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ COMPLETED │ │
│ │ (finalized) │ │
│ └─────────────────────┘ │
│ │
└────────────────────┬────────────────────┘
│
▼
┌─────────────────────┐
│ FAILED │
│ (operator action) │
└─────────────────────┘3 Protocol Flows
3.1 EVM → Canton (Lock/Mint)
- User calls
BridgeRouter.depositon Ethereum. Depositevent is emitted.- EVM watcher scans logs up to a safe head (
latest − confirmations). - Message is validated against limits and policy (min amount, daily caps, destination checks).
- Message is stored in DB as
DETECTED. - Relayer calls Canton
BridgeRouter.mint. - DB status transitions:
DETECTED→PROCESSING→COMPLETEDorFAILED.
┌───────────────────────────────────────────────────────────────────────┐
│ EVM → Canton (Lock / Mint) │
├───────────────────────────────────────────────────────────────────────┤
│ │
│ User Ethereum Backend │
│ │ │ │ │
│ │── deposit() ────────►│ │ │
│ │ │── Deposit event ─────────►│ │
│ │ │ │ │
│ │ │ (wait confirmations) │ │
│ │ │ │ │
│ │ │ ┌─────────┴─────────┐ │
│ │ │ │ validate & insert │ │
│ │ │ │ processed_messages│ │
│ │ │ │ status = DETECTED │ │
│ │ │ └─────────┬─────────┘ │
│ │ │ │ │
│ │ │ ▼ │
│ │ │ ┌──────────────────────┐ │
│ │ │ │ Canton JSON API │ │
│ │ │ │ create / exercise │ │
│ │ │ │ mint on BridgeRouter│ │
│ │ │ └─────────┬────────────┘ │
│ │ │ │ │
│ │ │ status = COMPLETED │
│ │ │ │ │
└───────────────────────────────────────────────────────────────────────┘3.1.1 Validation Checklist
- Event authenticity: emitted by the configured
BridgeRouteraddress. - Token correctness: the deposited token is the expected ETH/cETH contract.
- Amount bounds:
amount >= minAmountandamount <= maxPerTx. - Rate limits: daily limit per token and/or per user.
- Destination validity: destination chain and recipient party/address allowed.
3.1.2 Failure Modes
- RPC errors during scan: handled via retries, scan is resumable.
- Canton RPC/ledger submission fails: message remains
DETECTEDor transitions toFAILEDwith error. - Relayer crash after submission: message is recovered via DB state (retries should be safe and idempotent).
3.2 Canton → EVM (Burn/Release)
- Canton emits
DepositEventwith message data. - Canton watcher stores and marks as
DETECTED, then immediatelyPROCESSING. - Relayer calls Ethereum
finalizeWithdrawviaEVMSigner. - Receipt is awaited; finality wait is enforced (
EVM_TX_CONFIRMATIONSblocks). - DB status transitions to
COMPLETEDorFAILED.
┌───────────────────────────────────────────────────────────────────────┐
│ Canton → EVM (Burn / Release) │
├───────────────────────────────────────────────────────────────────────┤
│ │
│ Canton Ledger Backend Ethereum │
│ │ │ │ │
│ │── DepositEvent ─────►│ │ │
│ │ (websocket) │ │ │
│ │ │ │ │
│ │ ┌─────────┴─────────┐ │ │
│ │ │ insert message │ │ │
│ │ │ status = DETECTED │ │ │
│ │ │ → PROCESSING │ │ │
│ │ └─────────┬─────────┘ │ │
│ │ │ │ │
│ │ │── finalizeWithdraw() ───►│ │
│ │ │ │ │
│ │ │ (wait receipt) │ │
│ │ │ (wait finality) │ │
│ │ │ │ │
│ │ │◄─── receipt + conf ──────│ │
│ │ │ │ │
│ │ status = COMPLETED │ │
│ │ │ │ │
└───────────────────────────────────────────────────────────────────────┘3.2.1 Failure Modes
- Canton stream interruption: resumable from checkpoint; backlog catch‑up.
- Ethereum tx underpriced/stuck: processing monitor alerts; operator can replace tx.
- Ethereum reorg after inclusion but before finality: relayer continues waiting until confirmations are re‑achieved or re-submits if needed.
3.3 Exactly-Once vs At-Least-Once
The relayer processes external events at-least-once (events can be re-observed after restart or rollback), but the combined design aims for exactly-once effects by enforcing idempotency at the message level:
- source observation can repeat,
- destination execution is guarded by
messageIduniqueness and DB state.
The system therefore provides "exactly-once per messageId" semantics, assuming correct DB operation and consistent messageId derivation.
4 State, Finality, and Reorgs
4.1 Checkpointing
chain_checkpoints stores the last fully scanned block and its block hash. On restart, the watcher resumes from this checkpoint. Checkpoints are namespaced by chain ID, router address, and destination chain to prevent collisions across networks.
Checkpointing serves two distinct purposes:
- Progress: resume scanning without rescanning from genesis.
- Integrity: detect when a previously "processed" block has changed due to reorg.
4.2 Reorg Detection and Recovery
The watcher compares the stored checkpoint hash with the current chain hash at the same height. A mismatch triggers rollback by a safety buffer, deletes affected rows, and rescans the range. This ensures that deposits from reorged blocks are not treated as finalized.
The rollback strategy is conservative:
- Roll back to a block before the divergence by a fixed buffer.
- Delete any
processed_messagesderived from blocks above the rollback height. - Rescan deterministically from the rollback height to the safe head.
- Execute Bridge transaction on the destination chain with the updated state.
This approach intentionally prefers correctness over speed.
4.3 Idempotency
processed_messages ensures each messageId is processed once, supporting safe retries and relayer restarts. The DB is the source of truth for operational state and auditing.
Idempotency is implemented as a uniqueness constraint at the application level. The relayer always queries/updates by messageId and treats duplicates as no‑ops.
4.4 Finality Policy
The relayer separates inclusion from finality:
- Inclusion: tx is mined and has a receipt.
- Finality: tx has reached a configured confirmation depth.
For EVM deposits, the watcher only scans up to latest − confirmations. For EVM withdrawals, the signer waits for the receipt and then waits until the receipt's block has enough confirmations.
5 Reliability Controls
- Confirmations: avoid processing near‑head blocks.
- Exit checkpointing: resumable processing on shutdown.
- Catch‑up mode: skips waiting time between block creation reach latest block quickly.
5.1 Configuration Parameters
Operators should treat the following as explicit "risk knobs":
| Parameter | Purpose | Risk if Too Low | Risk if Too High |
|---|---|---|---|
confirmations | Reorg safety depth | Process reorged events | Increased latency |
rollback_buffer | How far to roll back on reorg | Miss divergence edges | Slower recovery |
max_chunk_size | eth_getLogs chunking | RPC failures/timeouts | More RPC calls |
| processing timeout | Alert threshold for stuck messages | Alert fatigue | Slow detection |
These should be tuned per chain and provider characteristics.
6 Security Considerations
- The relayer is a single point of control; access and key management are critical.
- RPC integrity is required; using multiple providers is recommended.
- Database integrity is required for auditability and recovery.
- Operational access should be limited, monitored, and rotated.
6.1 Threat Model (Centralized)
This system should be analyzed as a centralized custody/settlement component:
- A compromised relayer key can steal funds.
- A compromised RPC provider can cause incorrect observations or delayed processing.
- A compromised database can hide or distort operational history.
6.1.1 Attack Surface Summary
| Component | Asset at Risk | Attack Vector | Impact |
|---|---|---|---|
| Relayer EOA key | Vault funds | Key theft | Total loss of funds is prevented by the daily limits on the contract |
| RPC provider | Correct observations | Man‑in‑the‑middle / eclipse | Double‑spend - prevented by the contract validation, missed deposits - can be added to the system manually |
| Database | Audit trail | SQL injection / credential leak | Altered history - the history could be rescanned, double processing - prevented by the contract validation |
| Canton JSON API | Mint/burn | Token hijack / unauthorized exercise | Unbacked mint |
| Process/host | All | RCE / container escape | Full compromise |
6.2 Practical Mitigations
- Hardware-backed key storage and strict operational procedures.
- Multiple independent RPC providers; compare results at critical boundaries.
- Strict allow‑lists and rate limits.
- Run relayer in isolated network environment.
- On-chain validation which prohibits transaction replay.