Skip to main content

Example

Here's an example of a simple cross chain contract, XGreeter. This contract lets you send greetings from one chain to another.

XGreeter Contract

/**
* @title XGreeter
* @notice A cross chain greeter
*/
contract XGreeter is XApp {
/// @dev Emitted when someone greets the ether
event Greetings(address indexed from, uint64 indexed fromChainId, string greeting);

constructor(address portal) XApp(portal) { }

/// @dev Greet on another chain
/// `xcall` is a method inherited from `XApp`
function xgreet(uint64 destChainId, address to, string calldata greeting) external {
xcall(
destChainId,
to,
abi.encodeWithSignature("greet(string)", greeting)
);
}

/// @dev Greet on this chain
/// The `xrecv` modifier reads the current xmsg into `xmsg` storage
function greet(string calldata greeting) external xrecv {
require(isXCall(), "XGreeter: only xcall");
emit Greetings(xmsg.sender, xmsg.sourceChainId, greeting);
}
}

Walkthrough

Let's walk through this step by step.

First, inherit from XApp.

contract XGreeter is XApp {
constructor(address portal) XApp(portal) { }

// ...
}

Perform a Cross Chain Call

To call a contract on another chain, use xcall.

function xgreet(uint64 destChainId, address to, string calldata greeting) external {
xcall(
destChainId
to,
abi.encodeWithSignature("greet(string)", greeting)
);
}

Receive a Cross Chain Call

When receiving an xcall, you can read its context via omni.xmsg().

omni.xmsg().sourceChainId // where this xcall came from
omni.xmsg().sender // who sent it

With this context, we can have our XGreeter emit events detailing the source chain and sender.

function greet(string calldata greeting) external {
emit Greetings(omni.xmsg().sender, omni.xmsg().sourceChainId, 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.

function greet(string calldata greeting) external xrecv {
emit Greetings(xmsg.sender, xmsg.sourceChainId, greeting);
}

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.

function isXCall() internal view returns (bool) {
return omni.isXCall() && msg.sender == address(omni);
}

Note that not only does isXCall check with the portal that the current transaction is an xcall, it also confirms the sender is the portal itself. This helps avoid mistaking calls later in an xcall stacktrace with the original xcall. Using this helper, we can ensure that greet() can only every be called via an xcall.

function greet(string calldata greeting) external xrecv  {
require(isXCall(), "XGreeter: only xcall");
emit Greetings(xmsg.sender, xmsg.sourceChainId, greeting);
}