Skip to main content
Version: 0.2.x-amarok



This quickstart will teach you how to use xcall, the cross-chain communication primitive, to send funds and data across chains.

In this guide, we will build a cross-chain Greeter. The HelloTarget contract on the destination domain (Mumbai) stores a greeting variable that we want to update. The HelloSource contract on the origin domain (Goerli) will use xcall to execute the updating function on HelloTarget.

HelloTarget will require a payment of 1 TEST token to update the greeting.


Install Node.js and use Node.js v16. Follow the instructions to install nvm, a node version manager, which will make switching versions easier.

Install the latest beta version of Connext contracts package in your project.

npm install @connext/nxtp-contracts

Target Contract

All target contracts must implement Connext's IXReceiver interface. This interface ensures that Connext can call the contract and pass necessary data.

pragma solidity ^0.8.15;import {IXReceiver} from "@connext/nxtp-contracts/contracts/core/connext/interfaces/IXReceiver.sol";import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";contract HelloTarget is IXReceiver {  string public greeting;  // Hardcoded cost to update the greeting, in wei units  uint256 public cost = 1e18;  // The TEST Token on Mumbai  IERC20 public token = IERC20(0xeDb95D8037f769B72AAab41deeC92903A98C9E16);  /** @notice The receiver function as required by the IXReceiver interface.    * @dev The Connext bridge contract will call this function.    */  function xReceive(    bytes32 _transferId,    uint256 _amount,    address _asset,    address _originSender,    uint32 _origin,    bytes memory _callData  ) external returns (bytes memory) {    // Enforce the cost to update the greeting    require(      _asset == address(token) && _amount >= cost,      "Must pay at least 1 TEST"    );    // Unpack the _callData    string memory newGreeting = abi.decode(_callData, (string));    _updateGreeting(newGreeting);  }  /** @notice Internal function to update the greeting.    * @param newGreeting The new greeting.    */  function _updateGreeting(string memory newGreeting) internal {    greeting = newGreeting;  }}

The goal is to call _updateGreeting from the origin domain.

Source Contract

The source contract initiates the cross-chain operation with xcall and passes the encoded greeting into the call. All xcall params are detailed here.

Notice the token that is passed in is the TEST token on Goerli. You can read more about token flavors and why they matter here.

pragma solidity ^0.8.15;import {IConnext} from "@connext/nxtp-contracts/contracts/core/connext/interfaces/IConnext.sol";import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";/** * @title HelloSource * @notice Example source contract that updates a greeting in HelloTarget. * @dev Must pay at least 1 TEST to update the greeting. */contract HelloSource {  // The connext contract on the origin domain  IConnext public immutable connext;  // Hardcoded cost to update the greeting, in wei units  // Exactly 0.05% above 1 TEST to account for router fees  uint256 public cost = 1.0005003e18;  // The canonical TEST Token on Goerli  IERC20 public token = IERC20(0x7ea6eA49B0b0Ae9c5db7907d139D9Cd3439862a1);  constructor(IConnext _connext) {    connext = _connext;  }  /** @notice Updates a greeting variable on the HelloTarget contract.    * @param target Address of the HelloTarget contract.    * @param destinationDomain The destination domain ID.    * @param newGreeting New greeting to update to.    * @param relayerFee The fee offered to relayers. On testnet, this can be 0.    */  function updateGreeting (    address target,     uint32 destinationDomain,    string memory newGreeting,    uint256 relayerFee  ) external {    require(      token.allowance(msg.sender, address(this)) >= cost,      "User must approve amount"    );    // User sends funds to this contract    token.transferFrom(msg.sender, address(this), cost);    // This contract approves transfer to Connext    token.approve(address(connext), cost);    // Encode the data needed for the target contract call.    bytes memory callData = abi.encode(newGreeting);    connext.xcall{value: relayerFee}(      destinationDomain, // _destination: Domain ID of the destination chain      target,            // _to: address of the target contract      address(token),    // _asset: address of the token contract      msg.sender,        // _delegate: address that can revert or forceLocal on destination      cost,              // _amount: amount of tokens to transfer      30,                // _slippage: the max slippage the user will accept in BPS (0.3%)      callData           // _callData: the encoded calldata to send    );  }}

Executing the Transaction

If you don't already have gas funds on Goerli, try these faucets to get some:

You should try deploying (and verifying) your own contracts, but you can use contracts we already deployed for this example:

TEST Token Minting

Mint some TEST tokens that will be used for this example.

You can use Etherscan to call functions on (verified) contracts. Go to the TEST Token on Etherscan and click on the "Write Contract" button.


A new tab will show up with all write functions of the contract. Connect your wallet, switch to the right network, and enter the parameters for the mint function:

  • account: <YOUR_WALLET_ADDRESS>
  • amount: 10000000000000000000 (10 TEST)

TEST Token Spending Approval

Tokens will move from User's wallet => HelloSource => Connext => HelloTarget.

The user must first approve a spending allowance of the TEST ERC20 to the HelloSource contract. The require clause starting on line 37 checks for this allowance.

Again, on the Etherscan page, enter the parameters for the approve function:

  • spender: 0x9ce3799f033d89d316f373f1db161c84a401a26c
    • This is the address of HelloSource.
  • amount: 10000000000000000000 (10 TEST)
    • Recall that HelloTarget requires a payment of at least 1 TEST (1e18 wei units). In HelloSource, this is hardcoded as 1000500300000000000 to account for a 0.05% fee that routers will take on the bridged asset. But you can approve as much as you want. This way, you can call updateGreeting without having to do an approval every time.
    • If earning fees as a router sounds interesting, check out the Routers documentation.
TestERC20 Etherscan Approve

Then "Write" to the approve function.

Execute updateGreeting

Similarly to the approval function for TEST, navigate to the HelloSource contract on Etherscan. Fill out the updateGreeting function parameters and "Write" to the contract.

TestERC20 Etherscan Approve

Let's talk about the different parameters.

  • target: 0x9094da44ec4335632c28749437f616a8a6cadcb6

    • The address of HelloTarget.
  • destinationDomain: 9991

    • The Domain ID of the destination chain. You can find a mapping of Domain IDs here. Remember, HelloTarget is deployed to Mumbai.
  • newGreeting: hello chain!

    • Whatever string you want to update the greeting to.
  • relayerFee: 0

    • IMPORTANT! This is a fee paid to relayers, which are off-chain agents that help execute the final leg of the cross-chain transfer on the destination. Relayers get paid in the origin chain's native asset. This is why HelloSource passes the fee like so:

      connext.xcall{value: relayerFee}(...)

      However, we're paying 0 fees here! Only because on testnet, our relayers are not taking fees.


      As a xApp developer, you have some tools available to estimate what this relayerFee should be. Check out the guide on Estimating Fees.

Check HelloTarget

After executing updateGreeting, HelloTarget should be updated in just a few minutes.

  • Would it always be this fast? See our guide on Authentication to learn when xcall is fast or slow.

Head over to the HelloTarget contract on Etherscan. This time, we'll go to the Read Contract tab and look at the value of greeting. It has updated!

TestERC20 Etherscan Approve

Send a couple more updates from HelloSource but make it a different string. At some point, your TEST allowance to HelloSource will run out and you'll need to do the approval dance again.

Congrats! You've gone cross-chain!

Next Steps

  • Try tracking the status of an xcall after you send it.
  • Learn about authentication and important security considerations.
  • See how nested xcalls can open up infinite cross-chain possibilities.
  • Fork the xApp Starter Kit below (includes code for this example) and build your own xApp.
»xApp Starter Kit