Integrate Custom Token

Custom tokens are independently deployed tokens, as opposed to InterchainTokens deployed via the Interchain Token Service (ITS). These tokens are integrated with ITS across different chains by deploying a Token Manager for each.

Once a custom token has a Token Manager deployed on a given chain, it becomes bridgeable between blockchains that also have a token connected to a Token Manager sharing the same interchainTokenId.

Install the Axelar Interchain Token Service (ITS) package using npm or any other node package manager:

Terminal window
npm i @axelar-network/interchain-token-service

Build your ERC-20 token and deploy it on multiple chains. You can use a tool like the Create3 Deployer to ensure your token has the same address across chains.

Example token contract:

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
contract MyCustomToken is ERC20, ERC20Burnable {
constructor(address initialOwner)
ERC20("My Custom Token", "MCT")
{
// Initialization code here
}
function mint(address to, uint256 amount) public onlyMinter {
_mint(to, amount);
}
function burn(address from, uint256 amount) public onlyMinter {
_burn(from, amount);
}
}

This token is a simple implementation of an ERC-20 token that includes the critical functions needed to integrate with ITS: the ability to mint() and burn() tokens. ITS calls these functions when bridging tokens between chains.

You can also inherit from the InterchainTokenStandard so your token has built-in cross-chain functionality, such as the interchainTransfer() function.

After deploying your custom tokens on your preferred chains, the next step is to integrate your token with ITS by following these steps:

  1. Register token metadata with the ITS Contract
  2. Register custom token with the Interchain Token Factory
  3. Link custom token with the Interchain Token Factory
  4. Assign the minter role to your token’s Token Manager

Register your token metadata using the registerTokenMetadata function on the ITS contract. This function registers metadata for a token on the ITS Hub. This metadata is used for scaling linked tokens. For example, if you are integrating your custom token on chains A, B, and C, you should register token metadata on each chain separately. The registerTokenMetadata() function must be called on each chain the token will be linked on. When triggered, a cross-chain message will be sent from your source chain to ITS Hub on Axelar.

function registerTokenMetadata(
address tokenAddress, // your token address
uint256 gasValue // gas value
) external payable {};

This function registers metadata for a token on the ITS Hub. This metadata is used for scaling linked tokens.

Note: Token metadata must be registered before linkToken can be called for the corresponding token. This function links a remote token on destinationChain to a local token corresponding to the tokenId computed from the provided salt.

You can see a real example of this function being used in this transaction on Axelarscan, which demonstrates registering a token metadata on BNB.

The registerCustomToken() function registers an existing ERC-20 token (your custom token) under an interchainTokenId computed from the provided salt. Once executed, your token’s token manager will be deployed for that blockchain. This function only needs to be called once on a single “home” chain that your token is being integrated with, as opposed to the registerTokenMetadata() function that must be triggered on each chain the token will be linked on.

function registerCustomToken(
bytes32 salt, // your unique salt value
address tokenAddress, // your custom token address
TokenManagerType tokenManagerType, // token manager type
address operator // operator address
) external payable returns (bytes32 tokenId) {};

This function is called from the Interchain Token Factory rather than directly from ITS because the Factory provides additional utility functions and standardized interchainTokenId computation logic.

🚨

Security Note: The key used to register custom tokens is critical for security. If that key is compromised, the token can be compromised across multiple chains. - Interchain native tokens can only be deployed to additional chains via the same deployer key, so that key must be securely retained. - Tokens registered on ITS should be cautious about granting mint/burn permissions to other contracts. For example, sharing mint permission with the Polygon native bridge is not supported (the Polygon native bridge only looks for burns, which ITS uses—potentially allowing duplicate sends).

  1. salt: The salt is used to compute the interchainTokenId. It must be unique for your token integration to ensure the interchainTokenId does not intersect with another interchainTokenId. If the combination of the salt and sender is not unique, the transaction will revert.
  2. tokenAddress: The token address being linked.
  3. tokenManagerType: The type of manager being integrated. The value passed in must correspond to the enum value of the manager. (ie 2 for lockUnlock, 4 for mintBurn).
  4. operator: The address that will hold the operator role for this token’s token manager.

You can see a real example of this function being used in this transaction on BSC Scan, which demonstrates registering a custom token on BNB.

To connect your custom tokens between different chains, you can use the linkToken function. This can only be called once your token metadata has been registered on both chains and at least one of your tokens has been registered on a given “home” chain via the registerCustomToken() function.

function linkToken(
bytes32 salt, // your unique salt value previously used
string calldata destinationChain, // destination chain
bytes calldata destinationTokenAddress, // destination custom token address
TokenManagerType tokenManagerType, // token manager type
bytes calldata linkParams, // operator address
uint256 gasValue // gas value
) external payable returns (bytes32 tokenId) {}

This function does the following:

  1. It computes a interchainTokenId using the same salt you used during token registration.
  2. Creates a cross-chain message that instructs the destination chain to deploy a Token Manager for your token.
  3. Establishes the connection between your tokens across chains by associating them with the same interchainTokenId.
  4. Sets up the appropriate Token Manager type that defines how tokens will be bridged (lock/unlock, mint/burn, etc.).

You can see a real example of this function being used in this transaction on Axelarscan, which demonstrates linking a token between BNB chain and Avalanche.

Transfer the minter / burn role to the Token Manager using the transferMintership() function on your token (or any AccessControls contract that may be handling role management for your token). To retrieve your token manager address, use the tokenManagerAddress function on the ITS contract by specifying your tokenId. This is role transfer is necessary because when your token is bridged to a destination chain, the msg.sender of the mint() call will be the Token Manager.

For further examples utilizing the Interchain Token Service, check out the axelar-examples repository on GitHub. There, you can find implementations such as:

  • its-custom-token — Demonstrates how to use ITS with a custom token implementation.
  • its-executable — Demonstrates how to deploy an interchain token and send a cross-chain transfer along with a message.
  • its-mint-burn-from — Demonstrates how to deploy an interchain token that uses burnFrom() instead of burn() when bridging tokens.

For a step-by-step guide on integrating a custom token, check out the Link Custom Tokens Deployed Across Multiple Chains into Interchain Tokens tutorial.

Edit on GitHub