The Hop Messenger is a modular, trustless messaging protocol that can be used to build powerful cross-chain applications.
The Hop Messenger includes the following contracts:
Dispatchers - dispatch and bundle messagesExecutors - validate and execute messagesTransporters - connect cross-chain Dispatchers and ExecutorsConnectors - Easy one-to-one cross-chain connectionsAliases - Control a cross-chain Alias contract with any contract
There is a single Dispatcher on each chain. Dispatchers are responsible for aggregating messages into bundles sending the bundle to the destination through the transport layer. A small fee is required for each message that is dispatched which goes toward the cost of transporting the bundle.
Executors are responsible for validating and executing messages. There is one Executor deployed on every supported chain. Before a message can be executed, the Executor must prove the bundle by verifying it with the Transporter.
See Transporter package.
A Transporter is used by the messenger for tranporting data such as bundles cross-chain. The data being transported is always represented by a single hash called a commitment. In this case, the commitment is the hash of the bundle data -- the bundleHash.
The Transporter uses the cannonical bridges to transport the bundleHash using Ethereum as a hub. This is a simple transport method optimized for trustlessness and security but it can be quite slow in some cases such as sending a message from an optimistic rollup.
You can send a message by calling the EIP-5164 method, dispatchMessage.
IMessageDispatcher(dispatcher).dispatchMessage(toChainId, to, data);When receiving a message, inherit from MessageReceiver to access the EIP-5164 validation data -- messageId, from address, and fromChainId.
contract MyContract is MessageReceiver {
event MyMessageHandleEvent {
bytes32 indexed messageId /*,
... your event params*/
}
function myFunction() external {
// Parse the cross-chain context from the call
(bytes32 messageId, address from, uint256 fromChainId) = _crossChainContext();
// Validate the cross-chain caller
require(msg.sender == hopMessageBridge, "Invalid sender");
require(from == expectedCrossChainSender, "Invalid cross-chain sender");
require(fromChainId == expectedCrossChainId, "Invalid cross-chain chainId");
// Execute your business logic
emit MyMessageHandleEvent(messageId, /* your event params */);
}
}npm run compilenpm run testDeploy to testnet chains (Goerli, Optimism Goerli, etc.)
npm run testnet:deployDeploy to mainnet chains (Ethereum, Arbitrum, etc.)
npm run mainnetnpm run lintOptionally, you can spin up local networks using anvil instead of using external RPCs in your .env file.
anvil -p 8545 --chain-id 11155111
anvil -p 8546 --chain-id 11155420
anvil -p 8547 --chain-id 84532
anvil -p 8548 --chain-id 42069
.env
RPC_ENDPOINT_SEPOLIA="http://127.0.0.1:8545"
RPC_ENDPOINT_OPTIMISM_SEPOLIA="http://127.0.0.1:8546"
RPC_ENDPOINT_BASE_SEPOLIA="http://127.0.0.1:8547"
RPC_ENDPOINT_HOP_SEPOLIA="http://127.0.0.1:8548"
npm run test
Fees are collected based on an oracle-supplied gas price that includes a premium above the actual estimated gas price. This allows fees to accumulate in the fee reserve. If the oracle-supplied gas price is lower than the actual gas price, fees from the fee reserve are used to cover the difference. If the fee oracle is not properly managed, fees may run dry, requiring DAO intervention.