Technology

How a blockchain checks another blockchain

Start here for the idea in plain terms. Keep reading for the mechanism, in full detail, the way an engineer would want it.

Part one · the plain version

The problem with the middle

Two blockchains cannot talk to each other. They are separate systems that only trust their own record. So the industry built bridges: a contract holding deposits on one side, another on the other, and some group of signers deciding when to move value between them.

That group is the weak point. It always has been. Compromise the signers or their code and the money in the middle is gone.

What we did instead

L1X validators, the machines that produce blocks, also watch other blockchains directly.

When something happens on another chain, each validator looks at it themselves and waits until it is deep enough in that chain's history to be permanent. When a block is proposed containing that event, every other validator compares it against what they saw with their own eyes. If it does not match exactly, they refuse to sign. Without their signatures the block does not exist.

So a false event cannot get in, because getting it in would require the honest majority of the network to lie about something they can each independently check.

That is the whole idea. The rest of this page is how it is built.

The inbound pipeline, playing continuously
01 watch
A watcher per supported chain holds each event until it is at or beyond that chain’s finality depth. Never to the head, because a reorg would rewrite what it saw.
02 propose
The proposer packages newly verified events as attestation transactions, capped per block and per source.
03 verify
Every other validator re checks the attestation against its own cache: exists, matches byte for byte, at depth. Any mismatch and the block cannot reach quorum.
04 deliver
The core calls recordAndExecute on the Portal, which stores the event and invokes the handler under a gas cap. A failed handler stays claimable.
Figure 1. Watch, propose, verify, deliver. The verify stage is where a dishonest proposal dies, because the block cannot reach quorum without the validators that would have to lie about it.
Part two · the mechanism

Three planes

L1X separates the work into three planes, each using the right interface for the job.

EVMPortal contract
Controlcontract state ⇄

Applications register which foreign events they care about. The registry stores it; the core reads it back through the contract.

Inboundattestation transaction →

Verified foreign events are delivered to applications as a consensus gated transaction, written by the core into the Portal.

Outbound← events

Applications send messages to other chains. The Portal emits, the core reads them from the block.

Coreconsensus
Figure 2. Durable configuration lives in state, one shot messages travel as events, and anything the core writes into contract readable state arrives as a transaction, because only a transaction can be voted on.

If a node could write a foreign event straight into state, one dishonest node could invent one. Making it a transaction forces the whole validator set to agree first.

Control plane: registering interest

An application calls registerListener(chainId, contract, eventSignature, handler) on the Portal. Both are Solidity, so this is an ordinary contract call.

The registration lands in normal L1X state. During block execution the core notices the Portal's state changed, reads the current watch list back through the contract, and refreshes an in memory cache. Watchers start or stop according to the new list.

Rule

The core reads the registry through the contract, never from raw storage slots, so the registry's internal layout stays free to change.

Rule

The refresh is conditional, happening only in blocks where the registry actually changed.

Inbound plane: attested delivery

watch

Each validator runs a watcher per supported chain, holding each event until it is at or beyond that chain's configured finality depth. Watchers never scan to the head of a chain, because a reorg would rewrite what they saw.

propose

The block proposer packages newly verified events as attestation transactions. Caps apply per block and per source so one busy contract cannot crowd out the chain.

verify

Every other validator re checks each attestation against its own watcher cache. The event must exist, match byte for byte, and be at depth. On any mismatch the validator errors out, never signs, and the block cannot reach quorum.

deliver

On execution the core calls recordAndExecute on the Portal, which stores the event and invokes the registered handler under a gas cap. If the handler reverts or runs out of gas, the event is recorded as claimable and anyone can retry it with the original payload.

The attestation transaction is signed by the protocol itself, not by any user key. There is no valid signing payload for it, so it is unforgeable by construction rather than by policy.

Determinism

Every honest validator must reach the same answer, or blocks fail to finalise for innocent reasons. So:

R1

Never scan to chain head. Only at or beyond the configured finality depth.

R2

Match event bytes exactly. No normalising, no re encoding.

R3

No dependence on wall clock time or local RPC latency.

R4

Rotate across multiple providers, and treat disagreement between them as “not verified” rather than picking a winner.

Finality depths are set per chain and identical across all validators.

Outbound plane: sending messages

An application calls sendMessage(destChainId, destAddress, payload). The Portal emits a canonical event, the core reads block events and hands the message to the signer, validators sign, and a verifier contract on the destination chain checks those signatures against the published L1X validator set before delivering.

Outbound is the direction that requires the destination chain to authenticate L1X, which is why the epoch validator set is published on chain and committed by the protocol rather than configured by an operator.

The Portal contract

A single Solidity contract at a fixed, well known address.

FunctionCallerPurpose
registerListener(chainId, contract, eventSig, handler)Any applicationAdd a source to the watch list
readEvent(messageId)Any contract, viewRead a verified foreign event
sendMessage(destChainId, destAddress, payload)Any applicationEmit an outbound message
claim(messageId, payload, gasCap)AnyoneRetry a delivery whose handler failed
recordAndExecute(...)Protocol onlyStore an attested event and invoke the handler

If any externally controlled key could call recordAndExecute, the trust model would collapse back to a relayer with a private key, which is precisely what this design removes. Events are stored once, keyed by an identity derived from the source chain, contract, transaction hash and log index, and are replay protected.

What this replaces

Traditional bridgeL1X
A contract holding pooled deposits on each chainNo pooled custody in the middle
A relayer or committee with signing keysThe validator set that already secures the chain
Trust in an external multisigTrust in L1X consensus
Integration code deployed on every chainNothing deployed on the source chain
Wrapped assets issued by a bridge operatorAssets issued by application contracts on verified events

Security posture

Independent verification

Every validator re derives its own answer. Agreement is the product of independent observation, not of trusting the proposer.

Finality aware

Events are only usable once they are deep enough on the source chain to be irreversible under normal conditions.

Replay protected

Each event has one identity and is recorded once.

Bounded execution

Handlers run under a gas cap; a failing handler cannot stall the chain and its event stays claimable.

Audited

The core and cross chain code have been audited by Hashlock, with reports published.

Open

The node, the Portal and the tooling are public. Everything on this page can be read in the source.

Read the papers

Two documents go deeper than any web page should.