Send a Cross-Chain Message
pragma solidity ^0.8.28;
import {ConceroClient} from "@concero/messaging-contracts-v2/contracts/ConceroClient/ConceroClient.sol";
import {IConceroRouter} from "@concero/messaging-contracts-v2/contracts/interfaces/IConceroRouter.sol";
import {MessageCodec} from "@concero/messaging-contracts-v2/contracts/common/libraries/MessageCodec.sol";
contract YourDapp is ConceroClient {
constructor(address conceroRouter) ConceroClient(conceroRouter) {}
function sendMessage() external payable returns (bytes32 messageId) {
IConceroRouter router = IConceroRouter(i_conceroRouter);
// Setup verifiers
address[] memory verifiers = new address[](1);
verifiers[0] = 0x1A808aa4F1E874763E114AB4fD170460D84A8D54; // Source-chain verifier library (Ethereum Sepolia)
// Build MessageRequest
IConceroRouter.MessageRequest memory req = IConceroRouter.MessageRequest({
dstChainSelector: 421614, // Arbitrum Sepolia
srcBlockConfirmations: 0, // optional
feeToken: address(0), // The only supported fee token for now is ETH
relayerLib: 0x8338A0B9c83C0d7E67C267432DF4C8b7cA98e11C, // Source-chain relayer library (Ethereum Sepolia)
validatorLibs: verifiers,
validatorConfigs: new bytes[](1), // Empty config for the verifier
relayerConfig: "",
dstChainData: MessageCodec.encodeEvmDstChainData(
0x1111111111111111111111111111111111111111, // receiver address
200_000 // gasLimit (uint32)
),
// Let's send a string, an address and a number to the destination chain receiver.
payload: abi.encode(bytes("Hello from Ethereum Sepolia!"), msg.sender, 123456)
});
// Quote total message fee (including relayer & verifier fee)
uint256 fee = router.getMessageFee(req);
// Send message and obtain message ID
messageId = router.conceroSend{value: fee}(req);
}
}ConceroMessageSent Event
If the message is successfully sent, the ConceroMessageSent event is emitted on the source chain.
Relayers watch this event and pick messages that match their relayer library.
event ConceroMessageSent(
bytes32 indexed messageId,
bytes messageReceipt,
address[] validatorLibs,
address relayerLib
);Receiving a Message
If your destination chain contract is correctly configured, you will receive the message on the destination chain.
pragma solidity ^0.8.28;
import {ConceroClient} from "@concero/messaging-contracts-v2/contracts/ConceroClient/ConceroClient.sol";
import {MessageCodec} from "@concero/messaging-contracts-v2/contracts/common/libraries/MessageCodec.sol";
contract YourDapp is ConceroClient {
using MessageCodec for bytes;
event MessageReceived(bytes data, address sender, uint256 value);
constructor(address conceroRouter) ConceroClient(conceroRouter) {}
// Your app must override this function to handle incoming messages
function _conceroReceive(bytes calldata messageReceipt) internal override {
// Obtain the message from the source chain sender
bytes calldata messagePayload = messageReceipt.calldataPayload();
// Decode the message: bytes, address, uint256
(bytes memory data, address sender, uint256 value) = abi.decode(messagePayload, (bytes, address, uint256));
}
}