Axelar Logo

Secure
cross-chain
communication
for Web3

Secure
cross-chain
communication
for Web3

Axelar is a permissionless network and gateway that enables you to build secure cross-chain applications.

Build a 1-click experience: enable users to interact with any asset, any application, from any chain.

Add interop to your dApp

Universal asset transfer

Send your tokens across-chain via any wallet, CEX.

				
					GetDepositAddress(src chain, dst chain, asset, dst address)
				
			

General Message Passing


Send any payload with a single smart contract call.

				
					function callContract(
string memory destinationChain,
string memory destinationContractAddress,
bytes memory payload
)
				
			

Use cases

NFT + DeFi

A wallet with universal borrow-lend; NFTs on Ethereum can be used as collateral in DeFi apps on any IBC or EVM-compatible chain.

Universal AMM

Pool liquidity from any user, in any asset, on any chain.

Cross-chain synthetics

Call data from dApps on other chains, to index derivatives on the chain that fits your use-case best.

Open gaming

Any asset, anywhere, becomes currency or credential.

TokenLinker

This example showcases how ERC20 tokens native to a chain can be linked to other chains connected through Axelar. 

				
					pragma solidity 0.8.9;

import { IAxelarExecutable } from '@axelar-network/axelar-cgp-solidity/src/interfaces/IAxelarExecutable.sol';
import { AxelarGasReceiver } from '@axelar-network/axelar-cgp-solidity/src/util/AxelarGasReceiver.sol';
import { IERC20 } from '@axelar-network/axelar-cgp-solidity/src/interfaces/IERC20.sol';

/// @dev An abstract contract responsible for sending token to and receiving token from another TokenLinker.
abstract contract TokenLinker is IAxelarExecutable {
    event SendInitiated(string destinationChain, address indexed recipient, uint256 amount);

    event ReceiveCompleted(string sourceChain, address indexed recipient, uint256 amount);

    address public immutable token;
    mapping(string => string) public links;
    AxelarGasReceiver gasReceiver;

    constructor(address gateway_, address gasReceiver_, address token_) IAxelarExecutable(gateway_) {
        token = token_;
        gasReceiver = AxelarGasReceiver(gasReceiver_);
    }

    //Call this function on setup to tell this contract who it's sibling contracts are.
    function addLinker(string calldata chain_, string calldata address_) external {
        links[chain_] = address_;
    }

    function sendTo(
        string memory chain_,
        address recipient_,
        uint256 amount_,
        address gasToken, 
        uint256 gasAmount
    ) external {
        require(bytes(links[chain_]).length != 0, 'IVALID_DESTINATION_CHAIN');
        _collectToken(msg.sender, amount_);
        IERC20(gasToken).transferFrom(msg.sender, address(this), gasAmount);
        IERC20(gasToken).approve(address(gasReceiver), gasAmount);
        bytes memory payload = abi.encode(recipient_, amount_);
        gasReceiver.receiveGas(
            chain_,
            links[chain_],
            payload,
            gasToken,
            gasAmount
        );
        gateway.callContract(
            chain_,
            links[chain_],
            payload
        );
        emit SendInitiated(chain_, recipient_, amount_);
    }

   function _execute(
        string memory sourceChain_,
        string memory sourceAddress_, 
        bytes calldata payload_
    ) internal override {
        require(keccak256(bytes(links[sourceChain_])) == keccak256(bytes(sourceAddress_)), 'IVALID_SOURCE');
        address recipient;
        uint256 amount;
        (recipient, amount) = abi.decode(payload_, (address, uint256));
        _giveToken(recipient, amount);
        emit ReceiveCompleted(sourceChain_, recipient, amount);
    }

    function _collectToken(address from, uint256 amount) internal virtual;
    function _giveToken(address to, uint256 amount) internal virtual;
}
				
			

Cross-chain NFT

This example showcases a contract that can be used to send NFTs to different blockchains.

				
					pragma solidity 0.8.9;

import '@openzeppelin/contracts/token/ERC721/ERC721.sol';
import { AxelarGasReceiver } from '@axelar-network/axelar-cgp-solidity/src/util/AxelarGasReceiver.sol';
import { IAxelarExecutable } from '@axelar-network/axelar-cgp-solidity/src/interfaces/IAxelarExecutable.sol';
import { IERC20 } from '@axelar-network/axelar-cgp-solidity/src/interfaces/IERC20.sol';

contract NftLinker is ERC721, IAxelarExecutable {
    mapping(uint256 => bytes) public original; //abi.encode(originaChain, operator, tokenId);
    mapping(string => string) public linkers; //Who we trust.
    string chainName;   //To check if we are the source chain.
    AxelarGasReceiver gasReceiver;


    //Contructor that initializes the ERC721 portion of our linker as well as knows where the gateway and gasReceiver are.
    constructor(string memory chainName_, address gateway_, address gasReceiver_) 
    ERC721('Axelar NFT Linker', 'ANL') 
    IAxelarExecutable(gateway_) {
        chainName = chainName_;
        gasReceiver = AxelarGasReceiver(gasReceiver_);
    }

    //Used to add linkers. This should be only callably by trusted sources normally.
    function addLinker(string memory chain, string memory linker) external {
        linkers[chain] = linker;
    }

    //The main function users will interract with.
    function sendNFT(
        address operator, 
        uint256 tokenId, 
        string memory destinationChain, 
        address destinationAddress
    ) external payable {
        //If we are the operator then this is a minted token that lives remotely.
        if(operator == address(this)) {
            require(ownerOf(tokenId) == _msgSender(), 'NOT_YOUR_TOKEN');
            _sendMintedToken(tokenId, destinationChain, destinationAddress);
        } else {
            IERC721(operator).transferFrom(_msgSender(), address(this), tokenId);
            _sendNativeToken(operator, tokenId, destinationChain, destinationAddress);
        }
    }

    //Burns and sends a token.
    function _sendMintedToken(
        uint256 tokenId, 
        string memory destinationChain, 
        address destinationAddress
    ) internal {
        _burn(tokenId);
        //Get the original information.
        (
            string memory originalChain,
            address operator,
            uint256 originalTokenId
        ) = abi.decode(original[tokenId], (string, address, uint256));
        //Create the payload.
        bytes memory payload = abi.encode(originalChain, operator, originalTokenId, destinationAddress);
        //Pay for gas. We could also send the contract call here but then the sourceAddress will be that of the gas receiver which is a problem later.
        gasReceiver.receiveGasNative{value:msg.value}(
            destinationChain, 
            linkers[destinationChain], 
            payload
        );
        //Call the remote contract.
        gateway.callContract(
            destinationChain, 
            linkers[destinationChain], 
            payload
        );
    }
    //Locks and sends a token.
    function _sendNativeToken(
        address operator, 
        uint256 tokenId, 
        string memory destinationChain, 
        address destinationAddress
    ) internal {
        //Create the payload.
        bytes memory payload = abi.encode(chainName, operator, tokenId, destinationAddress);
        //Pay for gas. We could also send the contract call here but then the sourceAddress will be that of the gas receiver which is a problem later.
        gasReceiver.receiveGasNative{value: msg.value}(
            destinationChain, 
            linkers[destinationChain], 
            payload
        );
        //Call remote contract.
        gateway.callContract(
            destinationChain, 
            linkers[destinationChain], 
            payload
        );
    }

    //This is automatically executed by Axelar Microservices since gas was payed for.
    function _execute(string memory sourceChain, string memory sourceAddress, bytes calldata payload) internal override {
        //Check that the sender is another token linker.
        require(keccak256(bytes(sourceAddress)) == keccak256(bytes(linkers[sourceChain])), 'NOT_A_LINKER');
        //Decode the payload.
        (
            string memory originalChain,
            address operator,
            uint256 tokenId,
            address destinationAddress
        ) = abi.decode(payload, (string, address ,uint256, address));
        //If this is the original chain then we give the NFT locally.
        if(keccak256(bytes(originalChain)) == keccak256(bytes(chainName))) {
            IERC721(operator).transferFrom(address(this), destinationAddress, tokenId);
        //Otherwise we need to mint a new one.
        } else {
            //We need to save all the relevant information.
            bytes memory originalData = abi.encode(originalChain, operator, tokenId);
            //Avoids tokenId collisions.
            uint256 newTokenId = uint256(keccak256(originalData));
            original[newTokenId] = originalData;
            _safeMint(destinationAddress, newTokenId);
        }
    }
}

				
			

Axelar tech stack diagram

axelar-tech-stack

Ready to get started?