Comment on page
Lifecycle of a Message
In this document, we'll walk through an end-to-end example of sending a cross-chain message using Nomad.
Cross-chain message lifecycle
Let's use an example of a user, Alice, who wants to send 1000 USDC from her account on Ethereum to the same one on Moonbeam, using the Nomad Token Bridge application.
While this example will focus on the ERC-20 token bridging use case, the core logic on how messages get sent in the Nomad protocol is identical for all use cases.
The BridgeRouter contract, triggered by Alice's transaction, will begin executing its business logic (as encoded by the app developer). In this case, it will:
- Perform basic validation (eg.
_amount > 0)
- Check whether the token to be sent is local or remote (USDC is local)
- Accordingly escrow or burn the amount to be sent (USDC will be escrowed)
After the business logic is executed, the BridgeRouter contract will call
dispatchon the Home contract, enqueuing the message to be sent.
The Home contract formats and hashes the message, and inserts it into its Merkle tree. The Merkle tree in the Home contract is the core data structure in Nomad, and contains all the messages to be sent from that Home.
The new message is inserted as a leaf, and then merkelized to generate a new Merkle root, which is stored in the Home's queue. Finally, a
Dispatchevent is emitted, which signals to all relevant parties (ie. the Updater) that a new root has been generated.
As new roots are generated, the Updater calls the
updatefunction on the Ethereum Home contract, committing to the current root and the new root with their digital signature. This signature acts as an attestation on the accumulator root, which can now be relayed to a destination chain.
This function emits an
updateevent which signals to Relayers that a new update has occurred.
Note two things:
- 1.While this function has the same name
update, it has completely different logic in a Replica contract.
A Relayer (which is trustless and permissionless), will call
updatepassing in the
_newRoot, and the
_signaturewhich the Updater generated. This will "kick off" the dispute window for the new root, after which messages can be proven and processed.
To prove a message, anyone can call
proveon the Replica contract, passing in a leaf corresponding to the message, merkle path and index of the leaf to prove inclusion in the new root.
If this succeeds, the message will be processed, meaning it will be forwarded to the corresponding application router on the destination chain.
In this case, a Processor (which is also trustless and permissionless) will prove inclusion of Alice's message in the new root, and then call
processon the Replica. The Replica will then forward the message to the BridgeRouter contract on Moonbeam, and invoke its
Astute readers may notice that we assumed that Nomad's optimistic dispute window elapsed without fraud being flagged. To learn more about how fraud detection, flagging and recovery works in Nomad, please check out the Fraud documentation in the Security section.
handleis called on the Moonbeam BridgeRouter, the BridgeRouter will execute application business logic on its side to complete the bridging process for Alice. In this case, it will mint 1000 Nomad USDC (or madUSDC) to Alice's address on the Moonbeam side.
Note that the BridgeRouter contract is isomorphic, meaning has the same code deployed on both chains. As long as
handleare implemented, they will be able to communicate with each other, with messages brokered by Nomad's messaging passing layer.
Voila! We have successfully bridged tokens and sent a message via Nomad.