The protocol's cross-chain functionality allows DApps to maintain interoperability and composability across various networks. Developers can now seamlessly access data from any EVM network, including Ethereum and its layer 2 networks like Polygon, Arbitrum, and Optimism. This ability eliminates complicated cross-chain data stitching, offering real-time insights and data flows between DApps on different networks.
Crosschain Data Lookup
The Crosschain Data Lookup XCHAINfeature offers two main functions, XDATA and XBALANCE, designed to allow for data extraction across various blockchains. This ability to perform crosschain lookups opens up a multitude of possibilities for complex applications.
Parameters for XDATA/XBALANCE
RPC - The RPC URL of the destination EVM compatible blockchain.
constfunctionSig='balanceOf(address)';constfnHash=ethers.utils.id(functionSig); // keccak256 hash of function signatureconstfunctionSelector=fnHash.slice(0,10); // first four bytes of hash consttypes= ['address'];constvalues= ['0x9d31e30003f253563ff108bc60b16fdf2c93abb5'];constencodedParams=ethers.utils.defaultAbiCoder.encode(types, values);// Concatenate function selector and parametersconstencodedData= functionSelector +encodedParams.slice(2); // remove '0x' from params//0x70a082310000000000000000000000009d31e30003f253563ff108bc60b16fdf2c93abb5
FLAG - A flag for XDATA for contract presets to make it easier to do common lookups like ERC20 balances using an address in the data rather than the full calldata. 0 for custom with any calldata.
Constructed as
apiEndpoint = 'XCHAIN'
This flags the oracle that you are making a XDATA or XBALANCE request for another chain.
apiEndpointPath = "XDATA?RPC=" + EVMNETWORKRPCHERE + "&ADDRS=" + TARGETCONTRACTADDRSHERE + "&DATA=" + CALLDATAHERE + "&FLAG=0";
Purple values must be replaced with your values
XDATA Function
The XDATAfunction performs a crosschain lookup for any read-only contract function.
Usage
// SPDX-License-Identifier: MITpragmasolidity ^0.8.12;interface Morpheus {functiongetFeed(uint256 feedID )externalviewreturns (uint256 value,uint256 decimals,uint256 timestamp,stringmemory valStr );functionrequestFeeds(string[] calldataAPIendpoint,string[] calldataAPIendpointPath,uint256[] calldata decimals,uint256[] calldata bounties ) externalpayablereturns (uint256[] memory feeds);functionsupportFeeds(uint256[] calldata feedIds,uint256[] calldata values ) externalpayable;}contract CrosschainLookup { Morpheus morpheus =Morpheus(0x0000000000071821e8033345A7Be174647bE0706);mapping(address=>mapping(address=>uint256)) public userBalance;mapping(address=>mapping(address=>uint256)) public userBalanceFeed;stringpublic RPC ="https://eth.llamarpc.com";addresspublic owner;constructor() payable { owner = msg.sender; }functiongetBalance(address target,addressTOKEN) publicpayable {string[] memory apiEndpoint =newstring[](1); apiEndpoint[0] ="XCHAIN";// ABI encode the balanceOf function and the addressbytesmemory data = abi.encodeWithSignature("balanceOf(address)", target );string[] memory apiEndpointPath =newstring[](1); apiEndpointPath[0] =string.concat("XDATA?RPC=", RPC,"&ADDRS=",bytesToHexString(addressToBytes(TOKEN)),"&DATA=",bytesToHexString(data),"&FLAG=0" );uint256[] memory decimals =newuint256[](1); decimals[0] =0;uint256[] memory bounties =newuint256[](1); bounties[0] = .01ether; // Replace with actual bounty valueuint256[] memory feeds = morpheus.requestFeeds{value: .01ether}( apiEndpoint, apiEndpointPath, decimals, bounties ); userBalanceFeed[target][TOKEN] = feeds[0]; // Storing the feed ID here, to be decoded in setMyBalance }functionaddressToBytes(address_address ) publicpurereturns (bytesmemory) {bytes20 addressBytes =bytes20(_address);bytesmemory result =newbytes(20);for (uint i =0; i <20; i++) { result[i] = addressBytes[i]; }return result; }functionbytesToHexString(bytesmemory data ) publicpurereturns (stringmemory) {bytesmemory alphabet ="0123456789abcdef";bytesmemory str =newbytes(2+ data.length *2); str[0] ="0"; str[1] ="x";for (uint i =0; i < data.length; i++) { str[2+ i *2] = alphabet[uint(uint8(data[i] >>4))]; str[3+ i *2] = alphabet[uint(uint8(data[i] &0x0f))]; }returnstring(str); }functionsetBalance(address target,address token) public { (uint256 balance,uint256 timestamp,, ) = morpheus.getFeed( userBalanceFeed[target][token] );require(timestamp >= block.timestamp -10000,"Data is too old"); userBalance[target][token] = balance; }}
In this usage example, FLAG is set to 0 which signifies a call to a function other than 'balanceOf'. The addresses are handled as strings as required by the node, all requests must be as a string, and you can use the tools in this sol to go from bytes to strs as needed.
XBALANCE Function
The XBALANCE function performs a crosschain lookup of an account's balance in the native token.
These examples demonstrate how developers can leverage the Morpheus oracle contract to request crosschain data or balance. The execution time may vary based on the response from the destination chain and the gas price of the network where Morpheus is deployed.
Crosschain Data Lookup - getFeeds Function
The getFeeds function retrieves the latest data from multiple feeds on the Morpheus oracle.
feedIDs (uint256[] memory): An array of feed IDs to retrieve values for.
Returns
value (uint256[] memory): An array of the latest values of the feeds corresponding to the provided feed IDs.
timestamp (uint256[] memory): An array of Unix timestamps (in seconds) of the latest updates for the feeds corresponding to the provided feed IDs.
valueStr (string[] memory): An array of the latest string values of the feeds corresponding to the provided feed IDs. This is where the raw hexadecimal ABI-encoded data from an XDATA request is returned if the FLAG was set to 0. This raw data can be decoded using Solidity's ABI decoding functions. This example provides a helpful guide on how to decode the ABI in Solidity. On the other hand, for XBALANCE requests or ERC20 balance requests with FLAG set to 1, the balance is returned as a decimal string in the value array. To handle first do bytes(str) to then be able to decode.
Custom data
Hex data
If you need hex for your contract, the oracle will submit as a string. You can convert back to hex using this to be able to ABI decode.
functionstringToByte(stringmemory input) publicpurereturns (bytesmemory) {bytesmemory stringBytes =bytes(input);uint offset =0;// Check for '0x' or '0X' prefix and adjust the offsetif (stringBytes.length >=2&& (stringBytes[0] =='0') && (stringBytes[1] =='x'|| stringBytes[1] =='X')) { offset =2; }// The length of the result should be half the length of the input string minus the offsetbytesmemory result =newbytes((stringBytes.length - offset) /2);for (uint i = offset; i < stringBytes.length; i +=2) { result[(i - offset) / 2] = bytes1(_hexCharToByte(stringBytes[i]) << 4 | _hexCharToByte(stringBytes[i + 1]));
}return result; }function_hexCharToByte(bytes1 char) internalpurereturns (bytes1) {if (uint8(char) >=48&&uint8(char) <=57) {returnbytes1(uint8(char) -48); } elseif (uint8(char) >=65&&uint8(char) <=70) {returnbytes1(uint8(char) -55); // A = 65 in ASCII (65-10) } elseif (uint8(char) >=97&&uint8(char) <=102) {returnbytes1(uint8(char) -87); // a = 97 in ASCII (97-10-32) } else {revert("Invalid hexadecimal character."); } }
Sample
To demonstrate a sample usage of custom ABI decoding in the context of the Crosschain Data Lookup using Morpheus, let's consider an example scenario. We'll simulate a situation where a smart contract needs to decode data returned from an XDATA request with a custom ABI.
In this example, let's assume the contract with a function getUserInfo(address). The function returns multiple values: uint256 balance, uint256 timestamp, and bool isActive. We will encode this call, use the Morpheus oracle to get the data from another chain, and then decode the returned data.
Here are the steps:
Encode the Function Call for getUserInfo: First, we need to encode the function call for getUserInfo similar to how it was done for balanceOf in the provided script.
Make the Request Using Morpheus: We'll use the requestFeeds function of Morpheus to request this data.
Decode the Returned Data: Once the data is returned, we will use a custom function to decode it based on the expected return types from getUserInfo.
// SPDX-License-Identifier: MITpragmasolidity ^0.8.12;interface Morpheus {functionrequestFeeds(string[] calldataAPIendpoint,string[] calldataAPIendpointPath,uint256[] calldata decimals,uint256[] calldata bounties ) externalpayablereturns (uint256[] memory feeds);functiongetFeed(uint256 feedID )externalviewreturns (uint256 value,uint256 decimals,uint256 timestamp,stringmemory valStr );}contract CrosschainLookup { Morpheus public morpheus;stringpublic RPC ="https://eth.llamarpc.com";addresspublic owner;mapping(address=>mapping(address=>uint256)) public userBalanceFeed;mapping(address=>mapping(address=> UserInfo)) public userInfo;structUserInfo {uint256 balance;uint256 timestamp;bool isActive; }constructor(address_morpheus) payable { owner = msg.sender; morpheus =Morpheus(_morpheus); }functiongetUserInfo(address target,addressEXTERNAL_CONTRACT ) publicpayable {// Encode the getUserInfo function callbytesmemory data = abi.encodeWithSignature("getUserInfo(address)", target );// Construct the API endpoint pathstring[] memory apiEndpoint =newstring[](1); apiEndpoint[0] ="XCHAIN";string[] memory apiEndpointPath =newstring[](1); apiEndpointPath[0] =string.concat("XDATA?RPC=", RPC,"&ADDRS=",bytesToHexString(addressToBytes(EXTERNAL_CONTRACT)),"&DATA=",bytesToHexString(data),"&FLAG=0" );uint256[] memory decimals =newuint256[](1); decimals[0] =0;uint256[] memory bounties =newuint256[](1); bounties[0] = .001ether; // Replace with actual bounty valueuint256[] memory feeds = morpheus.requestFeeds{value: .001ether}( apiEndpoint, apiEndpointPath, decimals, bounties ); userBalanceFeed[target][EXTERNAL_CONTRACT] = feeds[0]; }functionprocessUserInfo(address target,address token) public { (,,,stringmemory encodedData) = morpheus.getFeed( userBalanceFeed[target][token] ); (uint256 balance,uint256 timestamp,bool isActive) =decodeUserInfo(stringToBytes(encodedData) ); userInfo[target][token] =UserInfo(balance, timestamp, isActive); }functiondecodeUserInfo(bytesmemory data ) publicpurereturns (uint256 balance,uint256 timestamp,bool isActive) {return abi.decode(data, (uint256,uint256,bool)); }functionaddressToBytes(address_address ) publicpurereturns (bytesmemory) {bytes20 addressBytes =bytes20(_address);bytesmemory result =newbytes(20);for (uint i =0; i <20; i++) { result[i] = addressBytes[i]; }return result; }functionbytesToHexString(bytesmemory data ) publicpurereturns (stringmemory) {bytesmemory alphabet ="0123456789abcdef";bytesmemory str =newbytes(2+ data.length *2); str[0] ="0"; str[1] ="x";for (uint i =0; i < data.length; i++) { str[2+ i *2] = alphabet[uint(uint8(data[i] >>4))]; str[3+ i *2] = alphabet[uint(uint8(data[i] &0x0f))]; }returnstring(str); }functionstringToBytes(stringmemory hexString ) publicpurereturns (bytesmemory) {bytesmemory bstr =bytes(hexString);require(bstr.length >=2,"Input string too short");// Skip '0x' prefix if presentuint offset =0;if (bstr[0] =="0"&& (bstr[1] =="x"|| bstr[1] =="X")) { offset =2; }require( (bstr.length - offset) % 2==0,"Hex string must have an even number of characters" );bytesmemory bytesArray =newbytes((bstr.length - offset) /2);for (uint i =0; i < bytesArray.length; i++) {bytes1 tmp1 = bstr[i *2+ offset];bytes1 tmp2 = bstr[i *2+ offset +1]; bytesArray[i] =bytes1(hexCharToUint(tmp1) *16+hexCharToUint(tmp2) ); }return bytesArray; }functionhexCharToUint(bytes1 c) internalpurereturns (uint8) {if (uint8(c) >=uint8(bytes1("0")) &&uint8(c) <=uint8(bytes1("9"))) {returnuint8(c) -uint8(bytes1("0")); }if (uint8(c) >=uint8(bytes1("a")) &&uint8(c) <=uint8(bytes1("f"))) {return10+uint8(c) -uint8(bytes1("a")); }if (uint8(c) >=uint8(bytes1("A")) &&uint8(c) <=uint8(bytes1("F"))) {return10+uint8(c) -uint8(bytes1("A")); }revert("Invalid hexadecimal character"); }}
In this example:
getUserInfo function encodes a call to an external contract's getUserInfo function and sends it via Morpheus oracle.
decodeUserInfo takes the raw hexadecimal ABI-encoded data (as a string) and decodes it into the expected types (uint256, uint256, bool).
processUserInfo demonstrates how to use decodeUserInfo to process the data returned from the oracle.
This is a high-level example. In a real-world scenario, error handling, gas optimization, and other considerations should be taken into account.