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 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
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
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;
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.