Hello World Contracts
Here's an example of a simple cross-chain contract set for setting and getting a string. This contract lets you send greetings from a rollup chain to a global storage contract deployed on Omni. Two main contracts are used in this example:
RollupGreeter
- A contract deployed on a rollup chain that sends greetings to the Omni chain.GlobalGreeter
- A contract deployed on the Omni chain that stores greetings.
RollupGreeter
Contract
Fetching code from GitHub...
Walkthrough
Let's walk through this step by step.
First, inherit from XApp
.
contract XGreeter is XApp {
// ...
}
You may also specify the confirmation level for the cross-chain message. This is the level of finalisation of the transaction containing the message you want to wait for. In this example, we set it to Latest
, meaning the message is relayed on block creation at source. You can see the supported confirmation levels here.
constructor(address portal, address _omniChainGreeter) XApp(portal, ConfLevel.Latest) {
omniChainGreeter = _omniChainGreeter;
}
Perform a Cross Chain Call
To call a contract on another chain, use xcall
.
function greet(string calldata greeting) external payable {
xcall(
// params for xcall
);
}
GlobalGreeter
Contract
Fetching code from GitHub...
Walkthrough
Similar to RollupGreeter
, we inherit from XApp
.
contract GlobalGreeter is XApp {
// ...
}
Similiar to RollupGreeter
, we can specify the confirmation level for the cross-chain message.
constructor(address portal) XApp(portal, ConfLevel.Latest) {
}
Receive a Cross Chain Call
When receiving an xcall
, you can read its context via omni.xmsg()
.
xmsg.sourceChainId // where this xcall came from
xmsg.sender // who sent it
With this context, we can have our XGreeter
extract the source chain and sender of the xcall
to store the greeting in a struct.
function greet(string calldata _greeting) external xrecv {
// Initialize the fee to 0, for local calls
uint256 fee = 0;
if (isXCall() && xmsg.sourceChainId != omni.chainId()) {
// Calculate the fee for the cross-chain call
fee = feeFor(xmsg.sourceChainId, abi.encodeWithSelector(this.greet.selector, _greeting), DEST_TX_GAS_LIMIT);
}
// Create a Greeting struct to store information about the received greeting
Greeting memory greeting =
Greeting(xmsg.sourceChainId, block.timestamp, fee, msg.sender, xmsg.sender, _greeting);
// Update the lastGreet variable with the information about the received greeting
lastGreet = greeting;
}
For convenience, XApp
defines the xrecv
modifier. This modifier reads the current xmsg into storage, and deletes after its function's execution.
modifier xrecv() {
xmsg = omni.xmsg();
_;
delete xmsg;
}
It also visually marks a function as the target of an xcall
. Though, the xrecv
modifier is not required to receive an xcall
. Using this modifier, we can simplify our XGreeter
a bit further.
Checking for Cross Chain Calls
Note that not every call is an xcall
. In these cases, xmsg
will be its zero value.
xmsg.sourceChainId // 0
xmsg.sender // address(0)
You can check if the current call is an xcall
with isXCall
.
Fetching code from GitHub...
Note that not only does isXCall
check with the portal that the current transaction is an xcall
. This helps avoid mistaking calls later in an xcall
stacktrace with the original xcall
. Using this helper, we can ensure that greet()
can only ever be called via an xcall
.