Brief Description

The OmniDictionary is a simple dApp demo that demonstrates how cross-chain contract interaction is processed through mapo omnichain service contract. It basically allows user to send request to adding a new entry in a dictionary(a simple mapping) on a foreign chain with a simple key and its corresponding value.

Tech Details

In order to achieve inter-blockchain contract execution, developers first needs to process their execution logic in a logic contract and then send the generated calldata from the source chain through the 'TransferOut' method through mapo omnichain service contract.

In our example, 'TransferOut' method is called as following and calldata is also generated in the middle process:

    function sendDictionaryInput(uint256 _tochainId,bytes memory _target,string memory _key,string memory _val) external payable {

    bytes memory data = encodeDictionaryInput(_key,_val);

    IMOSV3.MessageData memory mData = IMOSV3.MessageData(false,IMOSV3.MessageType.CALLDATA,_target,data,500000,0);

    bytes memory mDataBytes = abi.encode(mData);

    (uint256 amount,address receiverAddress) = IMOSV3(mos).getMessageFee(_tochainId,address(0),500000);

        "send request failed"

    emit sendEntry(_tochainId, _target, _key, _val);

secondly, the messenger picks up the event generated by the above transaction and constructed a corresponding proof, then it packs them together and send it to the mapo omnichain service contract on MAP Relay Chain:

    function transferOut(uint256 _toChain, bytes memory _messageData, address _feeToken) external  override
    require(_toChain != selfChainId, "Only other chain");

    MessageData memory msgData = abi.decode(_messageData,(MessageData));

    require(msgData.gasLimit >= gasLimitMin ,"Execution gas too low");
    require(msgData.gasLimit <= gasLimitMax ,"Execution gas too high");
    require(msgData.value == 0,"Not supported msg value");
    (uint256 amount,address receiverFeeAddress)= _getMessageFee(_toChain, _feeToken, msgData.gasLimit);
    if(_feeToken == address(0)){
        require(msg.value >= amount , "Need message fee");

        if (msg.value > 0) {
            TransferHelper.safeTransferETH(receiverFeeAddress, msg.value);
    }else {
        TransferHelper.safeTransferFrom(_feeToken, msg.sender, receiverFeeAddress, amount);

    bytes32 orderId = _getOrderID(msg.sender,, _toChain);

    bytes memory fromAddress = Utils.toBytes(msg.sender);

    emit mapMessageOut(selfChainId, _toChain, orderId, fromAddress, _messageData);

    return true;

And finally, the above process will be repeated by messenger and send it all the data to the destination chain by a 'TransferIn' method which will also execute the user input calldata:

    function transferIn(uint256 _chainId, bytes memory _receiptProof) external virtual nonReentrant whenNotPaused {
    require(_chainId == relayChainId, "invalid chain id");
    (bool sucess, string memory message, bytes memory logArray) = lightNode.verifyProofData(_receiptProof);
    require(sucess, message);

    IEvent.txLog[] memory logs = EvmDecoder.decodeTxLogs(logArray);
    for (uint i = 0; i < logs.length; i++) {
        IEvent.txLog memory log = logs[i];
        bytes32 topic = abi.decode(log.topics[0], (bytes32));

        if (topic == EvmDecoder.MAP_MESSAGE_TOPIC && relayContract == log.addr) {
            (, IEvent.dataOutEvent memory outEvent) = EvmDecoder.decodeDataLog(log);

            if(outEvent.toChain == selfChainId){
    emit mapTransferExecute(_chainId, selfChainId, msg.sender);

and the destination dictionary is finally set by

function setDictionaryEntry(string memory _key,string memory _val) external returns(bool) {
        require(whitelist[msg.sender],"access denied");
        dictionary[_key] = _val;
        return true;

All the above code can be found in our github repo.

Last updated