Ping Pong
Ping is a contract on some domain and Pong is a contract on some other domain. The user sends a ping and a pong will be sent back!
This example demonstrates how to use nested xcalls and a single direction emulating "callback" behavior.
Ping Contract
The Ping contract contains an external startPingPong function that the user will call to initiate the flow. It also implements IXReceiver because it will act as the "target" of an xcall from Pong. The xReceive function here is essentially a callback function - we can verify that something happened in Pong's domain and handle any results passed back.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.15;
import {IConnext} from "@connext/interfaces/core/IConnext.sol";
import {IXReceiver} from "@connext/interfaces/core/IXReceiver.sol";
/**
* @title Ping
* @notice Ping side of a PingPong example.
*/
contract Ping is IXReceiver {
// The Connext contract on this domain
IConnext public immutable connext;
// Number of pings this contract has received
uint256 public pings;
constructor(address _connext) {
connext = IConnext(_connext);
}
/**
* @notice Starts the ping pong s. equence.
* @param destinationDomain The destination domain ID.
* @param target Address of the Pong contract on the destination domain.
* @param relayerFee The fee offered to relayers.
*/
function startPingPong(
address target,
uint32 destinationDomain,
uint256 relayerFee
) external payable {
require(
msg.value == relayerFee,
"Must send gas equal to the specified relayer fee"
);
// Include the relayerFee so Pong will use the same fee
// Include the address of this contract so Pong will know where to send the "callback"
bytes memory callData = abi.encode(pings, address(this), relayerFee);
connext.xcall{value: relayerFee}(
destinationDomain, // _destination: domain ID of the destination chain
target, // _to: address of the target contract (Pong)
address(0), // _asset: use address zero for 0-value transfers
msg.sender, // _delegate: address that can revert or forceLocal on destination
0, // _amount: 0 because no funds are being transferred
0, // _slippage: can be anything between 0-10000 because no funds are being transferred
callData // _callData: the encoded calldata to send
);
}
/** @notice The receiver function as required by the IXReceiver interface.
* @dev The "callback" function for this example. Will be triggered after Pong xcalls back.
*/
function xReceive(
bytes32 _transferId,
uint256 _amount,
address _asset,
address _originSender,
uint32 _origin,
bytes memory _callData
) external returns (bytes memory) {
uint256 _pongs = abi.decode(_callData, (uint256));
pings++;
}
}Pong Contract
Pong will send a nested xcall back to Ping, including some information that can be acted on.
An important note for Pong is that sendPong is not payable and neither is xReceive. So in order for the 2nd xcall to work with relayerFees, the someone has to send native gas on destination to Pong. In practice, this can take the form of a "gas tank" mechanism that can be filled by users or subsidized by protocols.
Connext is working on an upgrade that will soon allow nested relayer fees to be deducted from the transacting asset, eliminating the need to fund receivers in their native gas token.
Last updated