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 {
bytes memory data = encodeDictionaryInput(_key,_val);
IMOSV3.CallData memory cData = IMOSV3.CallData(_target,data,50000,0);
"send request failed"
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 mapo relay chain:
function transferOut(uint256 _toChain, CallData memory _callData) external override
checkBridgeable(Utils.fromBytes(, _toChain)
require(_toChain != selfChainId, "Only other chain");
require(_callData.gasLimit >= gasLimitMin ,"Execution gas too low");
require(_callData.gasLimit <= gasLimitMax ,"Execution gas too high");
require(_callData.value == 0,"Not supported at present value");
(uint256 fee,address receiverFeeAddress) = feeService.getMessageFee(_toChain,;
//require(fee > 0,"Address has no message fee");
uint256 amount = msg.value;
require(amount == fee, "Need message fee");
if (amount > 0) {
TransferHelper.safeTransferETH(receiverFeeAddress, amount);
bytes32 orderId = _getOrderID(msg.sender,, _toChain);
bytes memory fromAddress = Utils.toBytes(msg.sender);
bytes memory callData = abi.encode(_callData);
emit mapMessageOut(selfChainId, _toChain, orderId,fromAddress,callData);
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 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.