Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

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));
    }
}