Programmatically Create a New Token Using the Interchain Token Service

Interchain Tokens are created and managed by Quantum Portal’s new Interchain Token Service, which uses Quantum Portal’s communication protocols to send tokens cross-chain. With it, you can build your own asset bridges, build asset transfers into your interchain dApp, and take on many other use cases.

The workflow can be summarized in three steps:

  1. The user obtains a token x from chain A.
  2. A message is sent to chain B specifying where the token should be transferred.
  3. Finally, Chain B receives the message is received and transfers token x to the appropriate destination address.

In this tutorial, you will learn how to:

  • Programmatically create a new Interchain Token from scratch using Quantum Portal’s Interchain Token Service
  • Register and deploy the token locally on the Fantom chain
  • Register and deploy the token remotely on the Polygon chain
  • Transfer your token between those chains

Prerequisites

You will need:

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:

mkdir interchain-token-project && cd interchain-token-project
npm init -y

Install Hardhat and the QPJS SDK

Install Hardhat and the QPJS SDK with the following commands:

npm install --save-dev hardhat@2.18.1 dotenv@16.3.1
npm install @QP-network/QPjs-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, Interchain Token Factory, and Interchain Token Contract, as they will be needed during deployment.

Create a folder named utils. Inside the folder, create the following new files and add the respective ABIs:

Set up an RPC for the local chain

Next, set up an RPC for the Fantom testnet, which you will be using as your local (source) chain.

Create an .env file

To make sure you’re not accidentally publishing your private key, create an .env file to store it in:

touch .env

Add your private key to .env and hardhat.config.js

Export your private key and add it to the .env file you just created:

PRIVATE_KEY= // Add your account private key here

Then, create a file with the name hardhat.config.js and add the following code snippet:

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],
    },
  },
};

Register and deploy a new Interchain Token to the local chain

Now that you’ve set up a Fantom testnet RPC, you can register and deploy a new Interchain Token. For the purposes of this tutorial, the token you’ll create will be called “New Interchain Token” and have the symbol “NIT”:

Create a newInterchainToken.js script

Create a new file named newInterchainToken.js and import the required dependencies:

  • Ethers.js
  • The QPJS SDK
  • The contract ABIs
  • The address of the Interchain Token Service contract
  • The address of the Interchain Token Factory contract
const hre = require("hardhat");
const crypto = require("crypto");
const ethers = hre.ethers;
const {
  QPQueryAPI,
  Environment,
  EvmChain,
  GasToken,
} = require("@QP-network/QPjs-sdk");

const interchainTokenServiceContractABI = require("./utils/interchainTokenServiceABI");
const interchainTokenFactoryContractABI = require("./utils/interchainTokenFactoryABI");
const interchainTokenContractABI = require("./utils/interchainTokenABI");

const interchainTokenServiceContractAddress =
  "0xB5FB4BE02232B1bBA4dC8f81dc24C26980dE9e3C";
const interchainTokenFactoryContractAddress =
  "0x83a93500d23Fbc3e82B410aD07A6a9F7A0670D66";

Get the signer

Next, create a getSigner() function in newInterchainToken.js. This will obtain a signer for a secure transaction:

//...

async function getSigner() {
  const [signer] = await ethers.getSigners();
  return signer;
}

Get the contract instance

Then, create a getContractInstance() function in newInterchainToken.js . This will get the contract instance based on the contract’s address, ABI, and signer:

//...

async function getContractInstance(contractAddress, contractABI, signer) {
  return new ethers.Contract(contractAddress, contractABI, signer);
}

Add a deployment function

Now you’re ready to register and deploy your token! Create a registerAndDeploy() function for the Fantom testnet. This will create a new token called “New Interchain Token” with the symbol “NIT”:

// Register and deploy a new interchain token to the Fantom testnet
async function registerAndDeploy() {
  // Generate random salt
  const salt = "0x" + crypto.randomBytes(32).toString("hex");

  // Create a new token
  const name = "New Interchain Token";
  const symbol = "NIT";
  const decimals = 18;

  // Intial token supply
  const initialSupply = ethers.utils.parseEther("1000");

  // Get a signer to sign the transaction
  const signer = await getSigner();

  // Create contract instances
  const interchainTokenFactoryContract = await getContractInstance(
    interchainTokenFactoryContractAddress,
    interchainTokenFactoryContractABI,
    signer
  );
  const interchainTokenServiceContract = await getContractInstance(
    interchainTokenServiceContractAddress,
    interchainTokenServiceContractABI,
    signer
  );

  // Generate a unique token ID using the signer's address and salt
  const tokenId = await interchainTokenFactoryContract.interchainTokenId(
    signer.address,
    salt
  );

  // Retrieve new token address
  const tokenAddress =
    await interchainTokenServiceContract.interchainTokenAddress(tokenId);

  // Retrieve token manager address
  const expectedTokenManagerAddress =
    await interchainTokenServiceContract.tokenManagerAddress(tokenId);

  // Deploy new Interchain Token
  const deployTxData =
    await interchainTokenFactoryContract.deployInterchainToken(
      salt,
      name,
      symbol,
      decimals,
      initialSupply,
      signer.address
    );

  console.log(
    `
  Deployed Token ID: ${tokenId},
  Token Address: ${tokenAddress},
  Transaction Hash: ${deployTxData.hash},
  salt: ${salt},
  Expected Token Manager Address: ${expectedTokenManagerAddress},
     `
  );
}

Add a main() function

Add a main() function for executing the newInterchainToken.js script. It will handle any errors that may arise:

//...

async function main() {
  const functionName = process.env.FUNCTION_NAME;
  switch (functionName) {
    case "registerAndDeploy":
      await registerAndDeploy();
      break;
    default:
      console.error(`Unknown function: ${functionName}`);
      process.exitCode = 1;
      return;
  }
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Run the newInterchainToken.js script to deploy to Fantom

Run the script in your terminal to register and deploy the token, specifying the fantom testnet:

FUNCTION_NAME=registerAndDeploy npx hardhat run newInterchainToken.js --network fantom

You should see something similar to the following on your console:

Deployed Token ID: 0x4427c3aab191db9cfc696291f93c6c33e93b5384ea5b25497e457316b50c45e9,
Token Address: 0xc028ebdF906a890be73C594b6a14858D0552544A,
Transaction Hash: 0x7b1b6321b5027851a15048fb9743d2b894f19d745441c1daf87d0c5054ed5309,
salt: 0x47ca252a2441a0e420c6ca15944fc902712a120e4be7d753733bb26c55f7098f,
Expected Token Manager Address: 0xECB7E82E4caBA520a4B9a10E6Ce831884c3685eF,

This shows that you have created a new interchain token called New Interchain Token (NIT) and deployed it on the Fantom testnet.

Check the transaction on the Fantom testnet scanner

Check the Fantom testnet scanner to see if you have successfully created and locally deployed a new Interchain Token on the Fantom testnet.

Get new token address

On FTMScan, navigate to the ERC-20 Tokens Transferred section and click New Interchain Token (NIT). This should take you to a page for your new token. Copy the token contract address from the Other Info section and store it somewhere safe. You will need it to initiate a remote transfer from the token in a later step.

Deploy an Interchain Token to a remote chain

You’ve just successfully deployed an Interchain Token to Fantom, which you are using as your local chain. Now, deploy it to Polygon, which will be 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 newInterchainToken.js, call estimateGasFee() from the QPJS SDK to estimate the actual cost of deploying your token on a remote chain:

//...

const api = new QPQueryAPI({ 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 deployment

Create a deployToRemoteChain() function that will perform token remote deployment on the Polygon Mumbai testnet. Make sure to replace the salt value with the salt returned to your terminal from when you ran registerAndDeploy():

//...

// Deploy to remote chain: Polygon
async function deployToRemoteChain() {
  // Get a signer for authorizing transactions
  const signer = await getSigner();
  // Get contract for remote deployment
  const interchainTokenFactoryContract = await getContractInstance(
    interchainTokenFactoryContractAddress,
    interchainTokenFactoryContractABI,
    signer
  );

  // Estimate gas fees
  const gasAmount = await gasEstimator();

  // Salt value from registerAndDeploy(). Replace with your own
  const salt =
    "0x47ca252a2441a0e420c6ca15944fc902712a120e4be7d753733bb26c55f7098f";

  // Initiate transaction
  const txn = await interchainTokenFactoryContract.deployRemoteInterchainToken(
    "Fantom",
    salt,
    signer.address,
    "Polygon",
    gasAmount,
    { value: gasAmount }
  );

  console.log(`Transaction Hash: ${txn.hash}`);
}

//...

Update main() to deploy to remote chains

Update main() to execute deployToRemoteChain() :

//...

async function main() {
  const functionName = process.env.FUNCTION_NAME;
  switch (functionName) {
    //...
    case "deployToRemoteChain":
      await deployToRemoteChain();
      break;
    default:
    //...
  }
}

//...

Run the newInterchainToken.js script to deploy to Polygon

Run the script in your terminal to register and deploy the token, once again specifying the fantom testnet (the source chain where all transactions are taking place):

FUNCTION_NAME=deployToRemoteChain npx hardhat run newInterchainToken.js --network fantom

You should see something similar to the following on your console:

Transaction Hash: 0xca901048c74c3757fcea5d309cd53ef6e21816c841e85145ac8586fa71a9cc48

Check the transaction on the Quantum Portal testnet scanner

Check the QPscan testnet scanner to see if you have successfully created and remotely deployed NIT on the Polygon Mumbai testnet. It should look something like this. Make sure that Quantum Portal shows a successful transaction before continuing on to the next step.

Transfer your token between chains

Now that you have deployed your token both locally to Fantom and remotely to Polygon, you can transfer between those two chains using the NIT you just deployed via the interchainTransfer() method.

Initiate a remote token transfer directly from the token

In newInterchainToken.js, create a transferTokens() function that will facilitate remote token transfers between chains. Change the address in interchainToken to the new token address from the FTM scan that you saved from an earlier step, and change the address in transfer to your own wallet address:

//...

async function transferTokens() {
  // Get signer
  const signer = await getSigner();

  const interchainToken = await getContractInstance(
    "0xc028ebdF906a890be73C594b6a14858D0552544A", // Update with new token address
    interchainTokenContractABI, // Interchain Token contract ABI
    signer
  );

  // Calculate gas amount
  const gasAmount = await gasEstimator();

  // Initate transfer via token
  const transfer = await interchainToken.interchainTransfer(
    "Polygon", // Destination chain
    "0x510e5EA32386B7C48C4DEEAC80e86859b5e2416C", // Update with your own wallet address
    ethers.utils.parseEther("25"), // Transfer 25 tokens
    "0x", // Empty data payload
    { value: gasAmount } // Transaction options
  );
  console.log("Transfer Transaction Hash:", transfer.hash);
}

//...

Update main() to execute token transfer

Update the main() to execute transferTokens():

//...

async function main() {
  const functionName = process.env.FUNCTION_NAME;
  switch (functionName) {
    //...
    case "transferTokens":
      await transferTokens();
      break;
    default:
    //...
  }
}

//...

Run the newInterchainToken.js script to transfer tokens

Run the script in your terminal, specifying the fantom testnet:

FUNCTION_NAME=transferTokens npx hardhat run newInterchainToken.js --network fantom

You should see something similar to the following on your console:

Transfer Transaction Hash: 0x205554f5e39e29b21f3110ebf813579a3826465746c37a151ed18c992004c6b5

If you see this, it means that your interchain transfer has been successful! 🎉

Check the transaction on the Quantum Portal testnet scanner

Check the QPscan testnet scanner to see if you have successfully transferred NIT from the Fantom testnet to the Polygon testnet. It should look something like this.

Import the new token into your wallet

You can also import the new token into your own wallet with its contract address that you saved from FTMScan. You should have 975 NIT on Fantom and 25 NIT on Polygon.

Congratulations!

You have now programmatically created a new interchain token called NIT using Quantum Portal’s Interchain Token Service and transferred it between two chains. You should now be able to confidently create and manage your own Interchain Tokens, opening up a wide range of possibilities for token transfers and asset bridges.

Great job making it this far! To show your support to the author of this tutorial, please post about your experience and tag @QPnetwork on Twitter (X).

What’s next

You can also explore other functionalities of the Interchain Token Service, such as:

References

Edit this page