Skip to main content

Simple Bridge

The SimpleBridge just transfers tokens from a user to another wallet (could be themselves) on a different chain. Since no calldata is involved, no target contract is needed.

In this example, SimpleBridge has two functions:

  • xTransfer bridges any ERC20 token

    • The user must first approve a spending allowance of the token to the SimpleBridge contract.
    • relayerFee is paid in native ETH so when xTransfer is called, msg.value MUST be passed in equal to the specified relayerFee. Informaation for calculating relayer fees can be found on Estimating Fees page.
  • xTransferEth bridges ETH (for origin/destination chains whose native asset is ETH)

    • To send and receive native ETH, the flow is a bit different. Since Connext doesn't accept native ETH as the bridged asset, ETH should be first wrapped into WETH on the origin domain and then the delivered WETH on destination should be unwrapped back to ETH.

    • An Unwrapper contract that implements IXReceive already exists on all supported networks to be used as the _to target in xcall. The final recipient on destination should be encoded into the callData param for the Unwrapper to send ETH to (demonstrated on line 92 below).

    • When sending ETH, msg.value = relayerFee + amount. See example below (note: in Etherscan, the payable field is in ether while the other fields are specified in wei).

      etherscan xTransferETh
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.15;

import {IConnext} from "@connext/interfaces/core/IConnext.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IWETH {
function deposit() external payable;
function approve(address guy, uint wad) external returns (bool);
}

/**
* @title SimpleBridge
* @notice Example of a cross-domain token transfer.
*/
contract SimpleBridge {
// The connext contract on the origin domain
IConnext public immutable connext;

constructor(address _connext) {
connext = IConnext(_connext);
}

/**
* @notice Transfers non-native assets from one chain to another.
* @dev User should approve a spending allowance before calling this.
* @param token Address of the token on this domain.
* @param amount The amount to transfer.
* @param recipient The destination address (e.g. a wallet).
* @param destinationDomain The destination domain ID.
* @param slippage The maximum amount of slippage the user will accept in BPS.
* @param relayerFee The fee offered to relayers.
*/
function xTransfer(
address token,
uint256 amount,
address recipient,
uint32 destinationDomain,
uint256 slippage,
uint256 relayerFee
) external payable {
IERC20 _token = IERC20(token);

require(
_token.allowance(msg.sender, address(this)) >= amount,
"User must approve amount"
);

// User sends funds to this contract
_token.transferFrom(msg.sender, address(this), amount);

// This contract approves transfer to Connext
_token.approve(address(connext), amount);

connext.xcall{value: relayerFee}(
destinationDomain, // _destination: Domain ID of the destination chain
recipient, // _to: address receiving the funds on the destination
token, // _asset: address of the token contract
msg.sender, // _delegate: address that can revert or forceLocal on destination
amount, // _amount: amount of tokens to transfer
slippage, // _slippage: the maximum amount of slippage the user will accept in BPS (e.g. 30 = 0.3%)
bytes("") // _callData: empty bytes because we're only sending funds
);
}

/**
* @notice Transfers native assets from one chain to another.
* @param destinationUnwrapper Address of the Unwrapper contract on destination.
* @param weth Address of the WETH contract on this domain.
* @param amount The amount to transfer.
* @param recipient The destination address (e.g. a wallet).
* @param destinationDomain The destination domain ID.
* @param slippage The maximum amount of slippage the user will accept in BPS.
* @param relayerFee The fee offered to relayers.
*/
function xTransferEth(
address destinationUnwrapper,
address weth,
uint256 amount,
address recipient,
uint32 destinationDomain,
uint256 slippage,
uint256 relayerFee
) external payable {
// Wrap ETH into WETH to send with the xcall
IWETH(weth).deposit{value: amount}();

// This contract approves transfer to Connext
IWETH(weth).approve(address(connext), amount);

// Encode the recipient address for calldata
bytes memory callData = abi.encode(recipient);

// xcall the Unwrapper contract to unwrap WETH into ETH on destination
connext.xcall{value: relayerFee}(
destinationDomain, // _destination: Domain ID of the destination chain
destinationUnwrapper, // _to: Unwrapper contract
weth, // _asset: address of the WETH contract
msg.sender, // _delegate: address that can revert or forceLocal on destination
amount, // _amount: amount of tokens to transfer
slippage, // _slippage: the maximum amount of slippage the user will accept in BPS (e.g. 30 = 0.3%)
callData // _callData: calldata with encoded recipient address
);
}
}

Information like asset addresses be found in the Deployments page.