Example: Cross-Chain Counter

Write a counter that will increment from a source chain to a destination chain.

Below we implement an example of incrementing a counter cross-chain--commonly used as the cross-chain "Hello World".

Contracts and Patterns

The contracts are simple: a SourceCounter dispatches messages to a destination chain, specifying an amount to increment by. Then, TargetCounter increments by amount on the destination chain.

pragma solidity 0.8.16;

import {ITelepathyRouter} from "src/amb/interfaces/ITelepathy.sol";
import {TelepathyHandler} from "src/amb/interfaces/TelepathyHandler.sol";

contract SourceCounter {
    ITelepathyRouter router;
    uint32 destinationChainId;

    constructor(address _router, uint32 _destinationChainId) {
        router = ITelepathyRouter(_router);
        destinationChainId = _destinationChainId;
    }

    // Increment counter on target chain by given amount
    function increment(uint256 _amount, address _destinationCounter) external virtual {
        bytes memory msgData = abi.encode(_amount);
        router.send(destinationChainId, _destinationCounter, msgData);
    }
}

contract TargetCounter is TelepathyHandler {
    uint256 public counter = 0;
    
    event Incremented(address incrementer, uint256 value);

    constructor(address _router) TelepathyHandler(_router) {}

    // Handle messages being sent and decoding
    function handleTelepathyImpl(
        uint32 _sourceChainId, address _sourceAddress, bytes memory _msgData
    ) internal override {
        (uint256 amount) = abi.decode(_msgData, (uint256));
        counter += amount;
        emit Incremented(_sourceAddress, counter);
    }
}

The ITelepathyRouter interface is used to send messages from source chain to destination chain. The TelepathyHandler interface is used to handle messages sent to a destination chain contract from the Telepathy Router.

If we look closely at SourceContract, we see that incrementing is dispatched by a call to router.send(...). We specify information about the destination chain, the sender, and the information we want to send using this call. The information we want to send across chains must be encoded bytes using abi.encode(...).

To handle messages sent to TargetCounter, it inherits from the TelepathyHandler contract and overrides the handleTelepathyImpl(...) method. This function is called by the TelepathyRouter after a message is relayed to it. The paramaters to handleTelepathyImpl contain the following information:

  • _sourceChainId is the chain the message was sent on

  • _sourceAddress is the message sender on the source chain. Usually receiving contracts should restrict who the _sourceAddress should be.

  • _msgData is the bytes of the message sent on the source chain

Unit Testing

To read more about unit testing cross-chain contracts with Telepathy, please go to the Unit Testing section.

Last updated