Sample Read Request Contract
This section explains how to create a simple cross-chain read request contract by integrating the Router Gateway contracts. The assumption made here is that a random contract already exists on the destination chain that retrieves a certain value from the source chain.
Step 1) Installing the dependencies
Install the evm-gateway contracts with either of the following commands:
yarn add @routerprotocol/evm-gateway-contracts
npm install @routerprotocol/evm-gateway-contracts
Make sure you're using the latest version of the Gateway contracts.
Step 2) Instantiating the contract
Import IGateway.sol from @routerprotocol/evm-gateway-contracts/contracts:
//SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.0 <0.9.0;
import "@routerprotocol/evm-gateway-contracts/contracts/IGateway.sol";
contract ReadCall {
}
Step 3) Defining the state variables and the constructor
IGateway public gatewayContract;
address public owner;
uint256 public value;
event ReceivedData(uint256 value);
constructor(
address payable gatewayAddress,
string memory feePayerAddress
) {
owner = msg.sender;
gatewayContract = IGateway(gatewayAddress);
gatewayContract.setDappMetadata(feePayerAddress);
}
- Create an instance of the
gatewayContractof typeIGateway. This will be the contract that will route the request to the destination chain. - Create a variable
ownerof typeaddresswhich will be used for access control. - Create a variable
valueof typeuint256which will be used for storing the value that we read from the destination chain. Note that one can read any kind of data from the destination chain contract. - Create an event
ReceivedDatawith a parametervalueof typeuint256. It will be emitted when the data is received from the destination chain. - Create a constructor with the
gatewayAddressand thefeePayeraddress in string format and set these variables inside the constructor.
Step 4) Setting the fee payer address through the setDappMetadata function
function setDappMetadata(
string memory FeePayer
) public {
require(msg.sender == owner, "Only owner can set the metadata");
gatewayContract.setDappMetadata(FeePayer);
}
To allow your dApp to send cross-chain transactions, you must specify the fee payer address on the Router Chain. This can be done by calling the setDappMetadata function on the Gateway contract, and passing the fee payer address as a parameter. Once the fee payer address is set, the fee payer must provide approval on the Router Chain to confirm their willingness to pay fees for your dApp.
It's important to note that any fee refunds will be credited to the fee payer address specified in the setDappMetadata function.
Step 5) Setting the Gateway address through the setGateway function
function setGateway(address gateway) external {
require(msg.sender == owner, "only owner");
gatewayContract = IGateway(gateway);
}
This is an administrative function which sets the address of the Gateway contract. This function should be invoked whenever the Router's Gateway contract gets updated.
Step 6) Sending a read request to the destination chain
Lets say there is a Multiplication contract on the destination chain with a function getResult() that takes a uint256 number as parameter and multiplies it with 2 and returns the result.
contract Multiplication {
function getResult(uint256 num) external view returns (uint256) {
return num * 2;
}
}
To send a read request to the destination chain to get the multiplication of any number, create a getResult function along with an interface.
Next, encode a function call using that interface in the following way:
interface IMultiplication {
function getResult(uint256 num) external view returns (uint256)
}
function sendReadRequest(
string calldata destChainId,
string calldata destinationContractAddress,
bytes calldata requestMetadata,
uint256 _value
) public payable {
bytes memory packet = abi.encodeCall(IMultiplication.getResult, (_value));
bytes memory requestPacket = abi.encode(destinationContractAddress, packet);
gatewayContract.iSend{ value: msg.value }(
1,
0,
string(""),
destChainId,
requestMetadata,
requestPacket
);
}
The parameters:
1) destChainId - Chain ID of the destination chain in string format.
2) destinationContractAddress - Address of the contract on the destination chain in string format.
3) requestMetadata - The detailed documentation for the requestMetadata function can be found here.
To create a read request payload packet, one can encode the function call using the abi.encodeCall function in the way provided in the above snippet. Now, to create the final request packet, just abi.encode the destination contract address along with the payload packet. Once this is done, we can generate a cross-chain read request by calling the iSend function of the Gateway contract with the required parameters.
Step 7) Handling the Acknowledgment
Once the read request is executed on the destination chain, the requested data is sent along with an acknowledgment to the source chain. To handle the acknowledgment, the applications need to include an iAck() function in the contract.
function iAck(
uint256 ,//requestIdentifier,
bool ,//execFlag,
bytes memory execData
) external {
value = abi.decode(execData, (uint256));
emit ReceivedData( value);
}
The function named
iAckshould be created with the same function signature as specified in the documentation. This function is called by the Gateway contract on the source chain and the function name and parameters should not be changed to avoid any failed requests. Further information about this function can be found here.The
requestIdentifierparameter received in theiAckfunction contains the nonce that was generated by the Gateway contract when the request was initiated on the source chain.The
execFlagtells the execution status of the cross-chain request on the destination chain andexecDataconsists of the abi-encoded value returned from theiReceivefunction.If the execution is successful on the destination chain:
execFlag-[true]execData-(abi.encode(<return_value>))
Since the return value is
uint256, thisexecDatacan be decoded using abi decoding in the following way:uint256 val = abi.decode(execData, (uint256));If the execution fails on the destination chain:
execFlag-[false]execData-[abi.encode(<error>)]
In this way, one can create a simple cross-chain read request contract using Router CrossTalk.
Full read request contract example (to be deployed on the intended source chain)
//SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;
import "@routerprotocol/evm-gateway-contracts/contracts/IGateway.sol";
interface IMultiplication {
function getResult(uint256 num) external view returns (uint256);
}
contract ReadCall {
IGateway public gatewayContract;
address public owner;
uint256 public value;
event ReceivedData( uint256 value);
constructor(
address payable gatewayAddress,
string memory feePayerAddress
) {
owner = msg.sender;
gatewayContract = IGateway(gatewayAddress);
gatewayContract.setDappMetadata(feePayerAddress);
}
/// @notice function to set the fee payer address on Router Chain.
/// @param feePayerAddress address of the fee payer on Router Chain.
function setDappMetadata(string memory feePayerAddress) external {
require(msg.sender == owner, "only owner");
gatewayContract.setDappMetadata(feePayerAddress);
}
/// @notice function to set the Router Gateway Contract.
/// @param gateway address of the Gateway contract.
function setGateway(address gateway) external {
require(msg.sender == owner, "only owner");
gatewayContract = IGateway(gateway);
}
/// @notice function to get the request metadata to be used while initiating cross-chain request
/// @return requestMetadata abi-encoded metadata according to source and destination chains
function getRequestMetadata(
uint64 destGasLimit,
uint64 destGasPrice,
uint64 ackGasLimit,
uint64 ackGasPrice,
uint128 relayerFees,
uint8 ackType,
bool isReadCall,
bytes memory asmAddress
) public pure returns (bytes memory) {
bytes memory requestMetadata = abi.encodePacked(
destGasLimit,
destGasPrice,
ackGasLimit,
ackGasPrice,
relayerFees,
ackType,
isReadCall,
asmAddress
);
return requestMetadata;
}
function sendReadRequest(
string calldata destChainId,
string calldata destinationContractAddress,
bytes calldata requestMetadata,
uint256 _value
) public payable {
bytes memory packet = abi.encodeCall(IMultiplication.getResult, (_value));
bytes memory requestPacket = abi.encode(destinationContractAddress, packet);
gatewayContract.iSend{ value: msg.value }(
1,
0,
string(""),
destChainId,
requestMetadata,
requestPacket
);
}
function iAck(
uint256 ,//requestIdentifier,
bool ,//execFlag,
bytes memory execData
) external {
value = abi.decode(execData, (uint256));
emit ReceivedData( value);
}
}
Multiplication contract example for reading contract state (to be deployed on the intended destination chain)
//SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;
import "./IMultiplication.sol";
contract Multiplication {
function getResult(uint256 num) external view returns (uint256) {
return num * 2;
}
}