diff --git a/src/layouts/navigation.ts b/src/layouts/navigation.ts index 67284864b..60fa1709d 100644 --- a/src/layouts/navigation.ts +++ b/src/layouts/navigation.ts @@ -42,6 +42,10 @@ export const getNavigation = (section) => { title: "Programmatically Create a Canonical Token", href: "/dev/send-tokens/interchain-tokens/developer-guides/programmatically-create-a-canonical-token", }, + { + title: "Link Custom Tokens Into Interchain Tokens", + href: "/dev/send-tokens/interchain-tokens/developer-guides/link-custom-tokens-deployed-across-multiple-chains-into-interchain-tokens", + }, ], }, ], @@ -198,7 +202,7 @@ export const getNavigation = (section) => { }, { title: "Glossary", - href: "/dev/glossary" + href: "/dev/glossary", }, ], }); @@ -283,7 +287,10 @@ export const getNavigation = (section) => { title: "Rotate tofnd mnemonics", href: "/validator/operations/mnemonic-rotation", }, - { title: "Monitor a validator or node", href: "/validator/operations/monitoring" }, + { + title: "Monitor a validator or node", + href: "/validator/operations/monitoring", + }, ], }, { @@ -306,8 +313,14 @@ export const getNavigation = (section) => { { title: "Validator status", children: [ - { title: "Register external chains", href: "/validator/status/register-external-chains" }, - { title: "Check validator status", href: "/validator/status/health-check" }, + { + title: "Register external chains", + href: "/validator/status/register-external-chains", + }, + { + title: "Check validator status", + href: "/validator/status/health-check", + }, { title: "Leave the network", href: "/validator/status/leave" }, ], }, @@ -336,13 +349,19 @@ export const getNavigation = (section) => { title: "Tokens", children: [ { title: "Stake AXL tokens", href: "/resources/tokens/stake-axl" }, - { title: "Wrap / unwrap tokens", href: "/resources/tokens/wrapped-tokens" }, + { + title: "Wrap / unwrap tokens", + href: "/resources/tokens/wrapped-tokens", + }, ], }, { title: "Axelarscan", children: [ - { title: "Add Account to Axelarscan", href: "/resources/axelarscan/axelarscan-add" }, + { + title: "Add Account to Axelarscan", + href: "/resources/axelarscan/axelarscan-add", + }, ], }, { @@ -356,7 +375,10 @@ export const getNavigation = (section) => { { title: "Community", children: [ - { title: "Community Pool Proposals", href: " /resources/community/community-pool-proposals" }, + { + title: "Community Pool Proposals", + href: " /resources/community/community-pool-proposals", + }, { title: "Bug Bounty", href: "/resources/community/bug-bounty" }, ], }, diff --git a/src/pages/dev/send-tokens/interchain-tokens/developer-guides/link-custom-tokens-deployed-across-multiple-chains-into-interchain-tokens.mdx b/src/pages/dev/send-tokens/interchain-tokens/developer-guides/link-custom-tokens-deployed-across-multiple-chains-into-interchain-tokens.mdx new file mode 100644 index 000000000..66cc5795a --- /dev/null +++ b/src/pages/dev/send-tokens/interchain-tokens/developer-guides/link-custom-tokens-deployed-across-multiple-chains-into-interchain-tokens.mdx @@ -0,0 +1,707 @@ +import { Callout } from "/src/components/callout"; + +# Link Custom Tokens Deployed Across Multiple Chains into Interchain Tokens + +Custom ERC-20 tokens deployed on multiple chains with specific minting policies, ownership structures, rate limits, and other bespoke functionalities can be turned into Interchain Tokens through the [Interchain Token Service](https://docs.axelar.dev/dev/send-tokens/interchain-tokens/intro). + +In this tutorial, you will: + +- Link custom tokens deployed across multiple chains into Interchain Tokens +- Deploy a simple ERC-20 token on the Fantom chain +- Deploy a simple ERC-20 token on the Polygon chain +- Deploy a [Token Manager](https://github.com/axelarnetwork/interchain-token-service/blob/main/contracts/token-manager/TokenManager.sol) on both Fantom and Polygon +- Transfer mint access to the [Interchain Token Service address](https://etherscan.io/address/0xB5FB4BE02232B1bBA4dC8f81dc24C26980dE9e3C) on both Fantom and Polygon +- Transfer your token between Fantom and Polygon + +## Prerequisites + +You will need: + +- A basic understanding of [Solidity](https://www.tutorialspoint.com/solidity/index.htm) and [JavaScript](https://www.w3schools.com/js/) +- A [wallet](https://metamask.io/) with FTM and MATIC funds for testing. If you don’t have these funds, you can get FTM from the [Fantom faucet](https://faucet.fantom.network/) and MATIC from the Mumbai faucets ([1](https://faucet.polygon.technology/), [2](https://mumbaifaucet.com/)). + +## Deploy an ERC-20 token on the Fantom and Polygon testnets + +[Create a SimpleERC20 token](https://bakemytoken.com/) and provide it with a name and symbol. You can skip this step if you already have an ERC-20 token deployed on the Fantom and Polygon testnets. + +The following is an example of a token deployed on the Fantom and Polygon testnets, `SimpleCustomToken`. It utilizes OpenZeppelin's libraries to create a custom ERC20 token with functionalities for minting, burning, and access control. The token includes a minter role, which enables designated addresses to mint or burn tokens. Additionally, it incorporates ERC20Permit for gasless transactions. The contract starts with a predefined supply of tokens minted to the deployer's address and establishes roles for a default administrator and minter: + +```Solidity + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +// Importing OpenZeppelin contracts for ERC20 standard token implementations, +// burnable tokens, access control mechanisms, and permit functionality. +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; +import "@openzeppelin/contracts/access/AccessControl.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; + +contract SimpleCustomToken is ERC20, ERC20Burnable, AccessControl, ERC20Permit { + +// Define a constant for the minter role using keccak256 to generate a unique hash. + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + + constructor(address defaultAdmin, address minter) + ERC20("SimpleCustomToken", "SCT") // Setting token name and symbol. + ERC20Permit("SimpleCustomToken") // Initialize ERC20Permit with the token name. + { + // Minting an initial supply of tokens to the message sender. + _mint(msg.sender, 10000 * 10 ** decimals()); + + + // Grant the DEFAULT_ADMIN_ROLE to the specified defaultAdmin address. + _grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin); + + + // Grant the MINTER_ROLE to the specified minter address. + // Addresses with the minter role are allowed to mint new tokens. + _grantRole(MINTER_ROLE, minter); + } + + // Mint new tokens. Only addresses with the MINTER_ROLE can call this function. + function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) { + _mint(to, amount); + } + + // Burn tokens from a specified account. + function burn(address account, uint256 amount) public onlyRole(MINTER_ROLE) { + _burn(account, amount); + } +} +``` + +## Set up your development environment + +### Create and initialize a project + +Open up your terminal and navigate to any directory of your choice. Run the following commands to create and initiate a project: + +```bash +mkdir custom-interchain-token-project && cd custom-interchain-token-project +npm init -y +``` + +# Install Hardhat and the AxelarJS SDK + +Install Hardhat and the AxelarJS SDK with the following commands: + +```bash +npm install --save-dev hardhat@2.18.1 dotenv@16.3.1 +npm install @axelar-network/axelarjs-sdk@0.13.9 crypto@1.0.1 @nomicfoundation/hardhat-toolbox@2.0.2 +``` + +### Set up project ABIs + +Next, set up the ABIs for the [Interchain Token Service](https://github.com/axelarnetwork/interchain-token-service/blob/main/contracts/InterchainTokenService.sol) and the contract from the token you deployed. + +Create a folder named `utils`. Inside the folder, create the following new files and add the respective ABIs: + +- Add the [Interchain Token Service ABI](https://gist.github.com/Olanetsoft/2a632784e6753d34ca7ffc4f73bf58ed) to `interchainTokenServiceABI.json`. +- Add your custom token ABI to `customTokenABI**.**json`. You can get this from [FTMScan](https://testnet.ftmscan.com/) or [PolygonScan](https://mumbai.polygonscan.com/) with the address of your deployed token. + +## Set up an RPC for the local chain + +Back in the root directory, set up an RPC for the Fantom and Polygon testnet. + +### Create an `.env` file + +To make sure you’re not accidentally publishing your private key, create an **`[.env`** file](https://blog.bitsrc.io/a-gentle-introduction-to-env-files-9ad424cc5ff4) to store it in: + +```bash +touch .env +``` + +### Add your private key to `.env` and `hardhat.config.js` + +[Export your private key](https://support.metamask.io/hc/en-us/articles/360015289632-How-to-Export-an-Account-Private-Key) and add it to the  `.env` file you just created: + +```bash +PRIVATE_KEY= // Add your account private key here +``` + + + If you will push this project on GitHub, create a `.gitignore` file and + include `.env`. + + +Then, create a file with the name `hardhat.config.js` and add the following code snippet: + +```JavaScript +require("@nomicfoundation/hardhat-toolbox"); +require("dotenv").config({ path: ".env" }); + +const PRIVATE_KEY = process.env.PRIVATE_KEY; + +/** @type import('hardhat/config').HardhatUserConfig */ +module.exports = { + solidity: "0.8.18", + networks: { + fantom: { + url: "https://rpc.ankr.com/fantom_testnet", + chainId: 4002, + accounts: [PRIVATE_KEY], + }, + polygon: { + url: "https://rpc.ankr.com/polygon_mumbai", + chainId: 80001, + accounts: [PRIVATE_KEY], + }, + }, +}; +``` + +## Deploy Token Manager on the Fantom Testnet + +Now that you have set up an RPC for the Fantom and Polygon testnet, you can deploy a Token Manager on the Fantom testnet. + +### Create a `customInterchainToken.js` script + +Create a new file named `customInterchainToken.js` and import the required dependencies: + +- `Ethers.js` +- The [AxelarJS SDK](https://github.com/axelarnetwork/axelarjs-sdk) +- The custom token contract ABI +- The address of the [`InterchainTokenService`](https://github.com/axelarnetwork/interchain-token-service/blob/main/contracts/InterchainTokenService.sol) contract +- The address of your token deployed on Fantom and Polygon testnet + +```JavaScript +const hre = require("hardhat"); +const crypto = require("crypto"); +const ethers = hre.ethers; +const { + AxelarQueryAPI, + Environment, + EvmChain, + GasToken, +} = require("@axelar-network/axelarjs-sdk"); + +const interchainTokenServiceContractABI = require("./utils/interchainTokenServiceABI"); +const customTokenABI = require("./utils/customTokenABI"); + +const MINT_BURN = 0; +const LOCK_UNLOCK = 2; + +const interchainTokenServiceContractAddress = + "0xB5FB4BE02232B1bBA4dC8f81dc24C26980dE9e3C"; + +const fantomCustomTokenAddress = "0x6B9ee4Ae432ffE426491FeA0032aE350FD9fFE85"; // Replace with your token address on fantom +const polygonCustomTokenAddress = "0xe5f05c39BFf8efb465b573fD53D5556667743046"; // Replace with your token address on Polygon +``` + +### Get the signer + +Next, create a `getSigner()` function in `customInterchainToken.js`. This will obtain a signer for a secure transaction: + +```JavaScript +//... + +async function getSigner() { + const [signer] = await ethers.getSigners(); + return signer; +} +``` + +### Get the contract instance + +Then, create a `getContractInstance()` function in `customInterchainToken.js` . This will get the contract instance based on the contract’s address, ABI, and signer: + +```JavaScript +//... + +async function getContractInstance(contractAddress, contractABI, signer) { + return new ethers.Contract(contractAddress, contractABI, signer); +} +``` + +## Deploy Token Manager on Fantom + +Create a `deployTokenManager()` function for the Fantom testnet. This will deploy a token manager with your custom token address: + +```JavaScript +//... + +// deploy Token Manager : Fantom +async function deployTokenManager() { + // Get a signer to sign the transaction + const signer = await getSigner(); + + // Get the InterchainTokenService contract instance + const interchainTokenServiceContract = await getContractInstance( + interchainTokenServiceContractAddress, + interchainTokenServiceContractABI, + signer + ); + + // Generate a random salt + const salt = "0x" + crypto.randomBytes(32).toString("hex"); + + // Create the params + const params = ethers.utils.defaultAbiCoder.encode( + ["bytes", "address"], + [signer.address, fantomCustomTokenAddress] + ); + + // Deploy the token manager + const deployTxData = await interchainTokenServiceContract.deployTokenManager( + salt, + "", + MINT_BURN, + params, + ethers.utils.parseEther("0.01") + ); + + // Get the tokenId + const tokenId = await interchainTokenServiceContract.interchainTokenId( + signer.address, + salt + ); + + // Get the token manager address + const expectedTokenManagerAddress = + await interchainTokenServiceContract.tokenManagerAddress(tokenId); + + console.log( + ` + Salt: ${salt}, + Transaction Hash: ${deployTxData.hash}, + Token ID: ${tokenId}, + Expected Token Manager Address: ${expectedTokenManagerAddress}, + ` + ); +} +``` + +### Add a `main()` function + +Add a `main()` function to execute the `customInterchainToken.js` script and handle any errors that may arise: + +```JavaScript +//... + +async function main() { + const functionName = process.env.FUNCTION_NAME; + switch (functionName) { + case "deployTokenManager": + await deployTokenManager(); + break; + default: + console.error(`Unknown function: ${functionName}`); + process.exitCode = 1; + return; + } +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); +``` + +### Run the `customInterchainToken.js` script to deploy to Fantom + +Run the script in your terminal to register and deploy the token, specifying the `fantom` testnet: + +```bash +FUNCTION_NAME=deployTokenManager npx hardhat run customInterchainToken.js --network fantom +``` + +If you see something similar to the following on your console, you have successfully registered your token as a canonical Interchain Token. + +```bash +Salt: 0x368f6c0d56af7cb3d90aac4b12186cbb0639edf7923f1405adf752bace5ed795, +Transaction Hash: 0x1854ed395fd1a6542433e277c607f4817ef896ef9c841e2ceaca108d08ff997c, +Token ID: 0xf60cd19fca372e0351a2619fe63435203a8699105c84850681b4aabbd3d9f6e0, +Expected Token Manager Address: 0x71D6374761Dcb3e331Ee08051998A0A958438571, +``` + +### Store the token ID and salt value + +Copy the token ID and salt value and store them somewhere safe. You will need them to initiate a remote token manager deployment and token transfer in a later step. + +### Check the transaction on the Fantom testnet scanner + +Check the [Fantom testnet scanner](https://testnet.ftmscan.com/) to see if you have successfully [deployed a token manager](https://testnet.ftmscan.com/tx/0x1854ed395fd1a6542433e277c607f4817ef896ef9c841e2ceaca108d08ff997c). + +## Deploy a Token Manager remotely on the Polygon Testnet + +You’ve just successfully deployed a token manager to Fantom, which you are using as your local chain. Now, deploy a token manager remotely to Polygon, which will act as the remote chain in this tutorial. Remember that you can specify any two chains to be your local and remote chains. + +### Estimate gas fees + +In `customInterchainToken.js`, call `estimateGasFee()` from the [AxelarJS SDK](https://www.notion.so/bd238d757a144a069501f0b481488ef3?pvs=21) to estimate the actual cost of deploying your remote Canonical Interchain Token on a remote chain: + +```JavaScript +//... + +const api = new AxelarQueryAPI({ environment: Environment.TESTNET }); + +// Estimate gas costs. +async function gasEstimator() { + const gas = await api.estimateGasFee( + EvmChain.FANTOM, + EvmChain.POLYGON, + GasToken.FTM, + 700000, + 1.1 + ); + + return gas; +} + +//... +``` + +### Perform remote token manager deployment + +Create a `deployRemoteTokenManager()` function. This will deploy the remote Canonical Interchain Token on the Polygon Mumbai testnet. + +```JavaScript +//... + +// deploy Remote Token Manager : Polygon +async function deployRemoteTokenManager() { + // Get a signer to sign the transaction + const signer = await getSigner(); + + // Get the InterchainTokenService contract instance + const interchainTokenServiceContract = await getContractInstance( + interchainTokenServiceContractAddress, + interchainTokenServiceContractABI, + signer + ); + + // Create the params + const param = ethers.utils.defaultAbiCoder.encode( + ["bytes", "address"], + [signer.address, polygonCustomTokenAddress] + ); + + const gasAmount = await gasEstimator(); + + // Deploy the token manager + const deployTxData = await interchainTokenServiceContract.deployTokenManager( + "0x368f6c0d56af7cb3d90aac4b12186cbb0639edf7923f1405adf752bace5ed795", // salt used in the previous step + "Polygon", + MINT_BURN, + param, + ethers.utils.parseEther("0.01"), + { value: gasAmount } + ); + + // Get the tokenId + const tokenId = await interchainTokenServiceContract.interchainTokenId( + signer.address, + "0x368f6c0d56af7cb3d90aac4b12186cbb0639edf7923f1405adf752bace5ed795" // salt used in the previous step + ); + + // Get the token manager address + const expectedTokenManagerAddress = + await interchainTokenServiceContract.tokenManagerAddress(tokenId); + + console.log( + ` + Transaction Hash: ${deployTxData.hash}, + Token ID: ${tokenId}, + Expected Token Manager Address: ${expectedTokenManagerAddress}, + ` + ); +} +``` + +### Update `main()` to deploy to remote chains + +Update `main()` to execute `deployRemoteTokenManager()` : + +```JavaScript +//... + +async function main() { + const functionName = process.env.FUNCTION_NAME; + switch (functionName) { + //... + case "deployRemoteTokenManager": + await deployRemoteTokenManager(); + break; + default: + //... + } +} + +//... +``` + +### Run the `customInterchainToken.js` script to deploy to Polygon + +Run the script in your terminal to deploy a token manager remotely, once again specifying the `fantom` testnet (the source chain where all transactions are taking place): + +```JavaScript +FUNCTION_NAME=deployRemoteTokenManager npx hardhat run customInterchainToken.js --network fantom +``` + +You should see something similar to the following on your console: + +```bash +Transaction Hash: 0xbfe92d3fb5bf3e9e08f65a3e11f231ff9df9c65985d2d5b952acf82e1375d5a5, +Token ID: 0xf60cd19fca372e0351a2619fe63435203a8699105c84850681b4aabbd3d9f6e0, +Expected Token Manager Address: 0x71D6374761Dcb3e331Ee08051998A0A958438571, +``` + +Take a look at the token ID and the expected token manager address. These must match the ones obtained in the previous step, as they are linked with the same salt value when deploying a token manager remotely on the Polygon testnet. + + +When deploying the token manager to a preferred chain other than the local chain (in our example, the Fantom testnet) while the remote chain is Polygon, make sure to deploy the remote token manager from the local chain. If you deploy to the Polygon testnet, you may encounter a `TokenManagerDoesNotExist` error. This error occurs because there is no token manager on Polygon with the `tokenId` you are using. You don’t need to deploy a remote token manager for Polygon because there is already one available on Fantom. Therefore, we deploy that token manager from the Fantom testnet. + + + +### Check the transaction on the Axelar testnet scanner + +Check the [Axelarscan testnet scanner](https://testnet.axelarscan.io/) to see if you have successfully deployed the remote token manager on the Polygon Mumbai testnet. It should look something like [this](https://testnet.axelarscan.io/gmp/0xbfe92d3fb5bf3e9e08f65a3e11f231ff9df9c65985d2d5b952acf82e1375d5a5). Ensure that Axelar shows a successful transaction before continuing to the next step. + +## Transfer mint access to the Interchain Token Service address on the Fantom and Polygon Testnets + +You must transfer mint access to the Interchain Token Service address on both chains before you can mint and burn tokens while moving assets between chains. + +### Transfer mint access to the Interchain Token Service on the Fantom testnet + +Create a `transferMintAccessToITSOnFantom()` function that will perform the mint access transfer on the Fantom testnet. + +```JavaScript +//... + +// Transfer Mint Access on all chains to ITS : Fantom +async function transferMintAccessToITSOnFantom() { + // Get a signer to sign the transaction + const signer = await getSigner(); + + const tokenContract = await getContractInstance( + fantomCustomTokenAddress, + customTokenABI, + signer + ); + + // Get the minter role + const getMinterRole = await tokenContract.MINTER_ROLE(); + + const grantRoleTxn = await tokenContract.grantRole( + getMinterRole, + interchainTokenServiceContractAddress + ); + + console.log("grantRoleTxn: ", grantRoleTxn.hash); +} +``` + +### Update `main()` to transfer mint access on Fantom testnet + +Update `main()` to execute `transferMintAccessToITSOnFantom()` : + +```JavaScript +//... + +async function main() { + const functionName = process.env.FUNCTION_NAME; + switch (functionName) { + //... + case "transferMintAccessToITSOnFantom": + await transferMintAccessToITSOnFantom(); + break; + default: + //... + } +} + +//... +``` + +### Run the `customInterchainToken.js` script to deploy to Fantom testnet + +Run the script in your terminal to transfer mint access to ITS specifying the `fantom` testnet: + +```bash +FUNCTION_NAME=transferMintAccessToITSOnFantom npx hardhat run customInterchainToken.js --network fantom +``` + +You should see something similar to the following on your console: + +```bash +grantRoleTxn: 0x12ff55a84aa3bb7bf6a1dada8f14017a0a610bccd9199ca108f22aafdd16a7f2 +``` + +### Check the transaction on the Fantom testnet scanner + +Check the [Fantom testnet scanner](https://testnet.ftmscan.com/) to see if you have successfully [transferred mint access to the Interchain Token Service address](https://testnet.ftmscan.com/tx/0x12ff55a84aa3bb7bf6a1dada8f14017a0a610bccd9199ca108f22aafdd16a7f2). + +### Transfer mint access to the Interchain Token Service on the Polygon testnet + +Create a `transferMintAccessToITSOnPolygon()` function that will perform the mint access transfer on the Fantom testnet. + +```JavaScript +//... + +// Transfer Mint Access on all chains to ITS : Polygon +async function transferMintAccessToITSOnPolygon() { + // Get a signer to sign the transaction + const signer = await getSigner(); + + const tokenContract = await getContractInstance( + polygonCustomTokenAddress, + customTokenABI, + signer + ); + + // Get the minter role + const getMinterRole = await tokenContract.MINTER_ROLE(); + + const grantRoleTxn = await tokenContract.grantRole( + getMinterRole, + interchainTokenServiceContractAddress + ); + + console.log("grantRoleTxn: ", grantRoleTxn.hash); +} + +//... +``` + +### Update `main()` to transfer mint access on Fantom testnet + +Update `main()` to execute `transferMintAccessToITSOnPolygon()` : + +```JavaScript +//... + +async function main() { + const functionName = process.env.FUNCTION_NAME; + switch (functionName) { + //... + case "transferMintAccessToITSOnPolygon": + await transferMintAccessToITSOnPolygon(); + break; + default: + //... + } +} + +//... +``` + +### Run the `customInterchainToken.js` script to deploy to the Polygon testnet + +Run the script in your terminal to transfer mint access to ITS, specifying the `polygon` testnet: + +```bash +FUNCTION_NAME=transferMintAccessToITSOnPolygon npx hardhat run customInterchainToken.js --network polygon +``` + +You should see something similar to the following on your console: + +```bash +grantRoleTxn: 0x2e581188fc57a97d2ab040de9440c112eb9a5553b43646a64a4fa43d8e79991d +``` + +### Check the transaction on the Polygon testnet scanner + +Check the [Polygon testnet scanner](https://mumbai.polygonscan.com/) to see if you have successfully [transferred Mint access to the Interchain Token Service address](https://mumbai.polygonscan.com/tx/0x2e581188fc57a97d2ab040de9440c112eb9a5553b43646a64a4fa43d8e79991d). + +## Transfer your token between chains + +Now that you have deployed a `TokenManager` on both Fantom and Polygon testnet, you can transfer your token between those two chains using the `[interchainTransfer()](https://github.com/axelarnetwork/interchain-token-service/blob/9edc4318ac1c17231e65886eea72c0f55469d7e5/contracts/interfaces/IInterchainTokenStandard.sol#L19)` method. + +### Initiate a remote token transfer + +In `customInterchainToken.js`, create a `transferTokens()` function that will facilitate remote token transfers between chains. Change the token ID to the `tokenId` that you [saved from an earlier step](https://www.notion.so/Edited-Programmatically-Create-a-Canonical-Interchain-Token-Using-the-Interchain-Token-Service-2e65ba3e4e8c4a0d9470effacec2c2be?pvs=21), and change the address in `transfer` to your own wallet address: + +```JavaScript +//... + +// Transfer tokens : Fantom -> Polygon +async function transferTokens() { + // Get a signer to sign the transaction + const signer = await getSigner(); + + const interchainTokenServiceContract = await getContractInstance( + interchainTokenServiceContractAddress, + interchainTokenServiceContractABI, + signer + ); + const gasAmount = await gasEstimator(); + const transfer = await interchainTokenServiceContract.interchainTransfer( + "0xf60cd19fca372e0351a2619fe63435203a8699105c84850681b4aabbd3d9f6e0", // tokenId, the one you store in the earlier step + "Polygon", + "0x510e5EA32386B7C48C4DEEAC80e86859b5e2416C", // receiver address + ethers.utils.parseEther("50"), // amount of token to transfer + "0x", + ethers.utils.parseEther("0.01"), // gasValue + { + // Transaction options should be passed here as an object + value: gasAmount, + } + ); + + console.log("Transfer Transaction Hash:", transfer.hash); + // 0xc2bb491cb1987e3ae9c164694b0c84a649c79d615c8c51c5c5df2bb339ee1f07 +} +``` + +### Update `main()` to execute token transfer + +Update the `main()` to execute `transferTokens()`: + +```JavaScript +//... + +async function main() { + const functionName = process.env.FUNCTION_NAME; + switch (functionName) { + //... + case "transferTokens": + await transferTokens(); + break; + default: + //... + } +} + +//... +``` + +### Run the `customInterchainToken.js` script to transfer tokens + +Run the script in your terminal, specifying the `fantom` testnet: + +```bash +FUNCTION_NAME=transferTokens npx hardhat run customInterchainToken.js --network fantom +``` + +You should see something similar to the following on your console: + +```bash +Transfer Transaction Hash: 0x146589f59683efd6d79cd1404afab6b6df9d052d55cbf69d29fc73c170b73e8b +``` + +If you see this, it means that your interchain transfer has been successful! 🎉 + +### Check the transaction on the Axelar testnet scanner + +Check the [Axelarscan testnet scanner](https://testnet.axelarscan.io/) to see if you have successfully transferred SCT from the [Fantom testnet](https://testnet.ftmscan.com/token/0x6B9ee4Ae432ffE426491FeA0032aE350FD9fFE85) to the [Polygon testnet](https://mumbai.polygonscan.com/token/0xe5f05c39BFf8efb465b573fD53D5556667743046). It should look something like [this](https://testnet.axelarscan.io/gmp/0x146589f59683efd6d79cd1404afab6b6df9d052d55cbf69d29fc73c170b73e8b). + +## Congratulations! + +You have now programmatically linked custom tokens deployed on multiple chains as Interchain Toke using Axelar’s Interchain Token Service and transferred it between two chains. + +Great job making it this far! To show your support to the author of this tutorial, please post about your experience and tag [@axelarnetwork](https://twitter.com/axelarnetwork) on Twitter (X). + +## What’s next + +You can also explore other functionalities of the Interchain Token Service, such as: + +- [Transforming an existing token into an Interchain token](https://docs.axelar.dev/dev/send-tokens/interchain-tokens/upgrade-tokens#canonical-tokens-simple-wrappers) +- [Create a new Interchain Token using the portal](https://docs.axelar.dev/dev/send-tokens/interchain-tokens/create-token#create-a-new-interchain-token-using-the-interchain-token-portal) + +## References + +- [Interchain Token Service Documentation](https://docs.axelar.dev/dev/send-tokens/interchain-tokens/intro) +- [Interchain Tokens GitHub Repository](https://github.com/axelarnetwork/interchain-token-service/tree/main) +- [AxelarJS SDK](https://github.com/axelar-network/axelarjs-sdk)