Skip to main content
Version: v1.3

Send Native Token Across Chains

Introduction

Cross-chain token transfers are crucial in the evolving decentralized finance (DeFi) landscape. In this guide, you'll learn how to send L1 native Tokens from L1 to an L2 EVM account on a destination chain using the sendCrossChain function of the NativeTokenController.sol contract.

Create a Native Token

Create your first native token by following our how to Create a Native Token Guide.

Understanding the sendCrossChain Function

First, let’s take a look at the sendCrossChain function in the NativeTokenController.sol file:

function sendCrossChain(
address destinationChain,
bytes memory destinationAddress,
uint256 amount,
ISCChainID chainID,
uint64 storageDeposit
) external {
// Function implementation
}

This function facilitates the transfer of native tokens from a Layer 1 (L1) address to a specified Layer 2 (L2) EVM account. It requires the L1 address of the destination chain (chainAddress), the recipient address on the destination chain (_destination), the destination chain's ID (_chainID), the amount of native tokens to be sent (_amount), and the amount of base tokens to cover the storage deposit (_storageDeposit). The sendCrossChain wrapper function invokes the ISC.sandbox.send function, which manages the actual cross-chain message transmission.

Setting Up the Development Environment

Setup Demo Repo

Visit the Demo Repo GitHub page for instructions on how to properly setup, IOTA Cross Chain Token Demo

Using the sendCrossChain Function

To send native tokens across chains, you need to provide the following transaction details to the sendCrossChain function:

  • ChainAddress - The L1 address of the destination chain.
  • Destination - The address on the destination chain that will receive the tokens.
  • chainID - The ID of the destination chain.
  • amount - The amount of native tokens to sent.
  • storageDeposit - The base tokens to cover storage deposit.

Example Code

Ownership

You might want to look into making the function ownable with, for example, OpenZeppelin so only owners of the contract can call certain functionalities of your contract.

1. Check the Storage Deposit

Check if the amount paid to the contract is the same as the required storage deposit and set the allowance.

require(msg.value == _storageDeposit*(10**12), "Please send exact funds to pay for storage deposit");
ISCAssets memory allowance;
allowance.baseTokens = _storageDeposit;
Payable

Instead of making the function payable, you could let the contract pay for the storage deposit. If so, you will need to change the require statement to check if the contract's balance has enough funds:

require(address(this).balance > _storageDeposit);

2. Send Token to Another Chain

Let's define a function named sendCrossChainMessage in our contract, which will interact with the sendCrossChain function in the NativeTokenController contract.

function sendCrossChainMessage(
address destinationChain,
bytes memory destinationAddress,
uint256 amount,
ISCChainID chainID,
uint64 storageDeposit
) external {
// Ensure the sender has enough tokens (assuming a balanceOf function exists)
uint256 senderBalance = IERC20(tokenAddress).balanceOf(msg.sender);
require(senderBalance >= amount, "Insufficient token balance");

// Call the sendCrossChain function from NativeTokenController
nativeTokenController.sendCrossChain(destinationChain, destinationAddress, amount, chainID, storageDeposit);
}

Full Example Code


// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./NativeTokenController.sol"; // Adjust the path as necessary
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract CrossChainMessenger {

NativeTokenController public nativeTokenController;

constructor(address _nativeTokenControllerAddress) {
nativeTokenController = NativeTokenController(_nativeTokenControllerAddress);
}

function sendCrossChainMessage(
address destinationChain,
bytes memory destinationAddress,
uint256 amount,
ISCChainID chainID,
uint64 storageDeposit,
tokenAddress
) external {
// Ensure the sender has enough tokens (assuming a balanceOf function exists)
uint256 senderBalance = IERC20(tokenAddress).balanceOf(msg.sender);
require(senderBalance >= amount, "Insufficient token balance");

// Call the sendCrossChain function from NativeTokenController
nativeTokenController.sendCrossChain(destinationChain, destinationAddress, amount, chainID, storageDeposit);
}
}

Conclusion

By following this guide, you have learned how to set up your development environment and use the sendCrossChain function in the NativeTokenController.sol contract to send native tokens across chains. You can now interact with the sendCrossChain function within your own smart contracts to facilitate cross-chain token transfers.

By leveraging cross-chain capabilities, you can create more interoperable and versatile decentralized applications, paving the way for a more connected blockchain ecosystem.