Skip to content

Trustless Bridge Architecture (TVM ↔ TVM)

Overview

Trustless bridge is a cross-chain system for token transfers between TVM networks. Unlike relay-based bridge (EVM-TVM), Trustless bridge does not use relay nodes for transfer confirmation. Instead, it uses cryptographic verification via Merkle proofs and LiteClient contracts.

Key Advantage

Users don't need to trust third parties — only cryptography and TVM network validators. This ensures full decentralization of transfers.

Principal Scheme

Trustless Bridge Principal Scheme

Glossary

Native and Alien Tokens

TermDescription
Native tokenA token that was originally created and exists in this network. When transferred to another network — locked in the Proxy contract.
Alien tokenA wrapped representation of a token from another network. Created (mint) in the destination network during incoming transfer. Burned during reverse transfer.

Example:

  • USDT on TON network is a Native token for TON
  • When transferring USDT from TON to Tycho:
    • On TON: USDT is locked in NativeProxy (remains Native)
    • On Tetra L2:: USDT is minted (Alien token)

Verification Components

TermDescription
LiteClientLight client smart contract storing information about key blocks of the other network: validator set, their weights, epoch timestamps. Updated by Sync Service during epoch changes.
TransactionCheckerContract for transaction verification via Merkle proofs. Verifies transaction existence in block and block signature validity via LiteClient.
Proof ChainMerkle proofs chain: transaction proof → block proof → validator signature verification via LiteClient.
Key BlockTVM network key block containing validator set change information. Synchronized between networks by Sync Service.

Configuration and Management

TermDescription
EventConfigurationEvent configuration contract, manages Event contract deployment, verifies event parameters (chainId, timestamps).
NativeProxyProxy contract for native tokens. Lock on outgoing, unlock on incoming transfers.
AlienProxyProxy contract for alien tokens. Mint on incoming, burn on outgoing transfers.
MergePoolContract for consolidating alien tokens from different networks into single representation (canon token).
Predeployed TokenPre-registered token for native-native scenario (token is native in both networks).

Transfer Types

FlowSource Network ActionDestination Network ActionEvent Contract
Lock Native → Mint AlienNativeProxy lockAlienProxy mintMultiVaultTvmTvmEventAlien
Burn Alien → Unlock NativeAlienProxy burnNativeProxy unlockMultiVaultTvmTvmEventNative
Lock Native → Unlock NativeNativeProxy lockAlienProxy → NativeProxyMultiVaultTvmTvmEventAlien (!)

Transfer Structure

Important to Understand

Each cross-chain transfer consists of two parts:

  1. Source network part — locking/burning tokens and emitting an event
  2. Destination network part — verification and minting/unlocking tokens

Event Contract

For each transfer, a separate Event contract is deployed in the destination network. It performs two key functions:

  1. Double-spending protection — a contract with a unique address (derived from source message msgHash) guarantees that one transaction can only be processed once
  2. Participation in Merge logic — the Event contract determines which token the user receives (derive or canon) based on MergePool settings

Verification Mechanism

Overview

Trustless verification is based on three components:

  1. LiteClient — stores validator set of the other network
  2. TransactionChecker — verifies transaction Merkle proofs
  3. Sync Service — synchronizes key blocks between networks

LiteClient and Key Blocks

LiteClient stores information about current validator set of the other TVM network (from which transfers come). This information is updated during validator epoch changes via Sync Service.

Stored data:

  • current_seq_no — current key block sequence number
  • current_epoch_since/until — validator epoch start/end timestamps
  • current_cutoff_weight — 2/3 + 1 of total validator weight
  • current_validators_set — dictionary: validator_index → (pubkey + weight)

How validator set is updated:

  1. Sync Service monitors key blocks in source network
  2. When new key block appears:
    • Sync Service downloads block with Merkle proof and signatures
    • Calls new_key_block() on LiteClient
  3. LiteClient verifies:
    • Block Merkle proof is correct
    • Block is a key block
    • Block signatures are valid for current validator set
    • Total weight of signers ≥ cutoff_weight
  4. Extracts new validator set from block
  5. Updates stored data

Byzantine Fault Tolerance

Cutoff weight = (total_weight * 2 / 3) + 1 guarantees that signing validator set represents honest majority. Even if up to 1/3 of validators are compromised, they cannot sign an incorrect block.

TransactionChecker and Merkle Proofs

TransactionChecker verifies that transaction exists in block and block is signed by validators (via LiteClient).

Verification algorithm:

  1. Event contract calls check_transaction() on TransactionChecker
  2. TransactionChecker decodes proof:
    • tx_proof — transaction Merkle proof
    • block_proof — block Merkle proof
    • signatures — validator signatures
  3. Verifies transaction presence in block via lookup_tx_in_block()
  4. Sends check_block() to LiteClient
  5. LiteClient verifies signatures:
    • For each signature checks check_signature(block_hash, signature, pubkey)
    • Accumulates total_signed_weight
    • Verifies: total_signed_weight >= cutoff_weight
  6. On success sends response::transaction_checked to Event contract

Attack Protection

AttackProtection Mechanism
Double-spendingEvent contract address is deterministically computed from msgHash. Repeat attempt → deployment error
Replay attackEventConfiguration verifies chainId and destinationChainId
Forged proofsMerkle proof is cryptographically verified
Invalid signaturesLiteClient verifies total signature weight ≥ 2/3 + 1

System Components

On-chain Components (Smart Contracts)

LiteClient

Light client contract storing information about key blocks of the other network.

Stored data:

  • current_seq_no — current key block sequence number
  • current_epoch_since/until — validator epoch start/end timestamps
  • current_cutoff_weight — minimum signature weight (2/3 + 1 of total validator weight)
  • current_validators_set — validator set with public keys and weights

Operations:

  • new_key_block — processing new key block, signature verification, validator set update
  • check_block — block signature verification against current validator set

TransactionChecker

Contract for verifying transactions from another network via Merkle proofs.

Operations:

  • check_transaction — transaction verification:
    1. Extracts transaction and block Merkle proofs
    2. Verifies that transaction exists in block
    3. Sends request to LiteClient for block signature verification
    4. On successful verification sends response::transaction_checked

ProxyMultiVaultNative (NativeProxy)

Proxy contract for working with native tokens (jettons).

Main functions:

  • Receives native tokens (lock) during outgoing transfers
  • Sends native tokens (unlock) during incoming transfers
  • Emits events for transfers to other networks
  • Manages fees and daily limits

Key methods:

  • transferNotification — callback when receiving jetton tokens, initiates outgoing transfer
  • onTvmEventConfirmedExtended — processing confirmed event (completing incoming transfer)

ProxyMultiVaultAlien (AlienProxy)

Proxy contract for working with alien tokens.

Main functions:

  • Mints alien tokens during incoming transfers
  • Processes callback after burning alien tokens and initiates outgoing transfer
  • Manages deployment of new alien tokens
  • Works with MergePool/MergeRouter for token consolidation

Key methods:

  • onTvmEventConfirmedExtended — minting alien tokens during incoming transfer
  • onAcceptTokensBurn — callback from jetton contract after token burning
  • deployTvmAlienToken — deploying new alien token

EventConfiguration (TvmTvmEventConfiguration)

Event configuration contract managing event contract deployment.

Stored data:

  • TransactionChecker address
  • Event contract code
  • Proxy contract address
  • Configuration parameters (chainId, startTimestamp, endTimestamp)

Key methods:

  • deployEvent — deploying new event contract for incoming transfer processing
  • deriveEventAddress — computing deterministic Event contract address from msgHash
  • onTvmEventConfirmedExtended — callback from Event, proxies call to Proxy

MultiVaultTvmTvmEvent (Alien / Native)

Event contracts for processing specific transfers.

Key methods:

  • processProof — transaction proof processing, calling TransactionChecker
  • onTrustlessVerify — callback from TransactionChecker on successful verification
  • _onConfirm — calling proxy to complete transfer

MergePool / MergeRouter

Contracts for consolidating alien tokens from different networks into single representation (canon token).

MergeRouter:

  • Routing to corresponding MergePool for token

MergePool:

  • Stores derive-token to canon-token mapping
  • Conversion between decimals of different representations

Off-chain Components (Backend)

Sync Service

Background service for synchronizing key blocks between networks.

Important

Sync Service runs in background mode and synchronizes key blocks periodically, not for each transfer. This is necessary to maintain current validator set in LiteClient.

Functions:

  • Monitors key blocks in source network
  • Uploads current validator set information to destination network's LiteClient contract
  • Calls new_key_block on LiteClient to update validator set

Update frequency:

  • Key blocks appear during validator epoch changes
  • Usually 1-2 times per day (depends on network)

Proof API

Light node synchronizing blocks and providing API for building proof chain.

Functions:

  • Synchronizes all blocks in real-time
  • Builds Merkle tree proof for transaction
  • Builds block proof
  • Endpoint: GET /v1/proof_chain/{address}/{lt} — returns BOC with proof chain

Bridge API

Event indexer for specific TVM network.

Functions:

  • Event indexing from proxy contracts
  • Transfer status tracking
  • Providing data for event contract deployment

Note: One Bridge API instance serves one TVM network.

Bridge Aggregator API

Aggregator over all APIs for working with transfers.

Functions:

  • Preparing payload for transfers
  • Combining transfer history from all networks
  • Transfer status tracking regardless of networks

Key endpoints:

  • POST /payload/build — build payload for transfer transaction
  • POST /transfers/search — search transfers with filtering
  • POST /transfers/status — get specific transfer status

Event Contract Lifecycle

Event Contract Statuses

CodeStatusDescription
0InitializingInitial state after deployment
1PendingAwaiting verification via TransactionChecker
2ConfirmedTransfer confirmed, proxy called, tokens delivered
3RejectedTransfer rejected (verification failed)
4CancelledTransfer cancelled by user
5LimitReachedDaily limit exceeded, awaiting approval
6LiquidityRequestedLiquidity requested from providers
7LiquidityProvidedLiquidity provided
8VerifiedTransaction verified (trustless), but tokens not yet delivered

State Transition Diagram

Main Flow (Successful Transfer)

  1. Initializing — EventConfiguration deploys contract, calls processProof()
  2. Pending — Event sends verifyTx() to TransactionChecker, awaits response
  3. Verified — TransactionChecker confirmed transaction via onTrustlessVerify(true)
  4. Confirmed — Event called _onConfirm(), Proxy executed mint/unlock

Edge Cases

LimitReached (Daily Limit Exceeded)

When transfer amount exceeds daily limit:

  1. Proxy calls Event.dailyLimitReached(limitApprover)
  2. Status → LimitReached
  3. limitApprover can:
    • approveLimit() → continue transfer → Confirmed
    • rejectLimit() → reject → Rejected
  4. User can: cancel()Cancelled (reverse transfer)

LiquidityRequested (Insufficient Liquidity)

When NativeProxy cannot unlock tokens (insufficient balance):

  1. NativeProxy calls Event.notEnoughLiquidity()
  2. Status → LiquidityRequested
  3. Event requests eventTokenWallet for receiving liquidity
  4. Liquidity provider can send tokens with bounty
  5. After receiving → LiquidityProvided → tokens delivered to user
  6. Or user can: cancel()Cancelled (reverse transfer)

Bounty for Provider

Liquidity provider receives bounty — reward from user for providing liquidity. Bounty amount is set by user or sender.

Transfer Flows

Lock Native → Mint Alien

This flow describes transferring a native token from source network to destination network where it becomes an alien token.

Step-by-step Description

Step 1-2: Initiation and Locking

User sends jetton tokens to NativeProxy. The payload specifies recipient address and target network. Proxy locks tokens and emits TvmTvmNative event.

Step 3: Event Contract Deployment

Proof API generates transaction Merkle proof. EventConfiguration deploys event contract with proof data in destination network.

Step 4-5: Verification

Event contract calls TransactionChecker for transaction verification. TransactionChecker verifies Merkle proof and requests LiteClient for block signature verification.

Step 6-7: Confirmation

On successful verification, TransactionChecker calls onTrustlessVerify(true) on Event contract. Status changes to Verified. Then Event contract calls _onConfirm(), which:

  • Changes status to Confirmed
  • Sends onTvmEventConfirmedExtended to AlienProxy

Step 8-9: Minting

AlienProxy receives confirmation, checks limits, charges fee and mints alien tokens to recipient.


Burn Alien → Unlock Native

Reverse flow: returning alien token to original network where native tokens are unlocked.

Step-by-step Description

Step 1-2: User burns alien tokens via AlienProxy. Proxy emits TvmTvmAlien event.

Step 3-5: Event contract deployment and verification similar to previous flow.

Step 6-7: Event contract receives confirmation, status changes Verified → Confirmed.

Step 8-9: NativeProxy receives confirmation and unlocks native tokens to recipient.

Edge Case: Insufficient Liquidity

If NativeProxy wallet balance is insufficient for unlock:

  1. NativeProxy calls Event.notEnoughLiquidity()
  2. Event → status LiquidityRequested
  3. Liquidity provider can send tokens with bounty
  4. Or user can cancel transfer via cancel()

Lock Native → Unlock Native

Flow for transferring native token between networks where the token is native in both networks.

Implementation Detail

The transfer is technically executed via the Alien flow, but AlienProxy performs a check: if native-native scenario is configured for the token (entry exists in predeployedTokens), AlienProxy doesn't mint the token but proxies the call to NativeProxy.

Step-by-step Description

Step 1-2: User sends native tokens to NativeProxy in source network. Tokens are locked, TvmTvmNative event is emitted.

Step 3-5: In destination network, MultiVaultTvmTvmEventAlien (not Native!) is deployed via AlienEventConfiguration. Verification happens via TransactionChecker.

Step 6-7: Event contract calls receivePredeployedToken on AlienProxy. If token is registered in predeployedTokens, PredeployedTokenData with nativeProxyTokenWallet address is returned.

Step 8-9: After verification, Event contract calls onTvmEventConfirmedExtended on AlienProxy, passing nativeProxyTokenWallet in metadata.

Step 10: AlienProxy checks nativeProxyTokenWallet.hasValue(). If true — doesn't mint, but proxies call to NativeProxy via proxyMultiVaultNative.onTvmEventConfirmedExtended().

Step 11: NativeProxy receives call (accepts from AlienProxy or directly from EventConfig), unlocks native tokens and sends to recipient.

When is this flow used?

Lock Native → Unlock Native is used when token is configured as native in both networks via predeployedTokens in AlienProxy. This is possible for:

  • Wrapped native tokens (e.g., wTON)
  • Tokens specifically configured by administrator

Configuration happens via registering PredeployedTokenData in tvmConfiguration.predeployedTokens mapping.

Risks and Protection Mechanisms

RiskCauseProtection Mechanism
Double-spendingReprocessing same transactionEvent contract address is deterministically computed from msgHash. Repeat deployment impossible.
Forged Merkle proofFaking transaction proofMerkle proof is cryptographically verified via extract_merkle_proof() and lookup_tx_in_block()
Invalid block signaturesInsufficient signatures or invalid signaturesLiteClient verifies: total signer weight ≥ cutoff_weight (2/3 + 1)
Outdated validator setLiteClient stores outdated validator setSync Service periodically updates validator set via new_key_block()
Replay attackEvent from one network processed in anotherEventConfiguration verifies chainId in proof == networkConfiguration.chainId
Wrong destination chainEvent intended for different networkEventConfiguration verifies destinationChainId
Unauthorized event emitterEvent emitted not by Proxy contractEventConfiguration verifies accountAddr == eventEmitter
Daily limit exceededTransfer amount exceeds daily limitProxy checks limits; on exceed → LimitReached status, awaits approval
Not enough liquidityNativeProxy cannot unlock tokensNativeProxy calls notEnoughLiquidity(); provider can supply liquidity with bounty
Wrong predeployed tokenInvalid externalNativeProxyWalletEvent verifies match with data from predeployedTokens

What to do with transfer problems?

  1. LimitReached — wait for approval from limitApprover or cancel transfer
  2. LiquidityRequested — wait for liquidity provider or cancel transfer (receive reverse transfer to source network)
  3. Cancelled — tokens will be returned to source network via reverse transfer

Smart Contract Errors

Event Contracts

Event contract errors (MultiVaultTvmTvmEventAlien, MultiVaultTvmTvmEventNative):

CodeCondition
2321Invalid status for processProof(): status != Initializing && status != Pending && status != Verified
2313Call not from EventConfiguration: msg.sender != eventInitData.configuration
2329Callback not from TransactionChecker: msg.sender != transactionChecker
2312Invalid status for onTrustlessVerify(): status != Pending
2901Callback not from Proxy: msg.sender != transitionalData.proxy
2330Invalid externalNativeProxyWallet for predeployed token
2326Callback not from TokenRoot: msg.sender != transitionalData.token
2327Callback not from MergeRouter: msg.sender != transitionalData.router
2328Callback not from MergePool: msg.sender != transitionalData.pool
2335Call not from limitApprover: msg.sender != limitApprover
2332Call not from recipient/sender (for cancel/setBounty)
2324Invalid status for operation (approveLimit, rejectLimit, cancel, setBounty, retry)
2333Bounty exceeds amount: bounty > eventData.amount
2334Callback not from eventTokenWallet: msg.sender != eventTokenWallet
2331Callback not from Proxy (Native Event) or not from proxy/native_proxy (Alien Event)

EventConfiguration

TvmTvmEventConfiguration contract errors:

CodeCondition
2218endTimestamp already set: networkConfiguration.endTimestamp != 0
2216endTimestamp less than startTimestamp
2221Proof from different network: chainId != networkConfiguration.chainId
2222Invalid msgHash: tvm.hash(message) != _eventVoteData.msgHash
2223Event not for target network: destinationChainId != TON_GLOBAL_ID
2220Event not from Proxy: accountAddr != eventEmitter.value
2211Event before startTimestamp: txTimestamp < networkConfiguration.startTimestamp
2215Event after endTimestamp (if endTimestamp != 0)
2212Callback not from Event contract: deriveEventAddress(_eventInitData) != msg.sender

Proxy Contracts

ProxyMultiVaultAlien and ProxyMultiVaultNative contract errors:

CodeCondition
2710Callback not from allowed EventConfiguration: configuration not in tvmConfiguration.incomingConfigurations

TransactionChecker

TransactionChecker contract errors:

CodeCondition
200Transaction not found in block
201Invalid transaction hash
202Block is not masterchain block
203Callback not from LiteClient: sender_address != ctx_lite_client_addr

LiteClient

LiteClient contract errors:

CodeCondition
111Block is not key block
112Key block from same epoch
113Key block from old epoch
114Invalid validator signature
115Insufficient signature weight: total_signed_weight < ctx_current_cutoff_weight
116Invalid validator epoch parameters

Common Merkle Proof Errors

CodeCondition
101Cell is not exotic cell
102Cell is not Merkle proof
103Cell is not Merkle update

ChainConnect Bridge Documentation