Send Tokens Cross-Chain

There are three ways you can use Quantum Portal to transfer ERC-20 tokens across different blockchains:

  1. Call sendToken() to send a token on any Quantum Portal-supported source chain to a recipient on any Quantum Portal-supported destination chain. This can be done from EVM chains, Cosmos-based chains, or the QPJS SDK.
  2. Transfer assets using a deposit address generated with the QPJS SDK.
  3. Build your own Interchain Token if your token is not natively supported.

Call sendToken() on an EVM source chain

To send a token from an EVM source chain:

1. Find the source chain’s Gateway contract address

Locate the source chain’s Gateway contract address on either the mainnet or the testnet.

EVM chains use Quantum Portal Gateway smart contracts to send tokens. These are application-layer smart contracts that send and receive payloads as well as monitor transaction state.

All Gateway contracts implement the IQPGateway interface, which has a public method called sendToken() that transfers tokens between chains:

function sendToken(
    string memory destinationChain,
    string memory destinationAddress,
    string memory symbol,
    uint256 amount
) external;

2. Call the source chain’s approve() method

Transferring tokens through a Gateway is similar to doing a typical ERC-20 token transfer. You’ll need to call the source chain’s approve() method (inherited from the ERC-20 interface) to allow the Gateway to transfer a specific token in a specific amount.

function approve(address spender, uint256 amount) external returns (bool);

spender is the source chain’s Gateway contract address on either the mainnet or the testnet.

3. Call sendToken() on the source chain’s Gateway contract

Call sendToken() on the source chain’s Gateway contract to transfer the tokens. For example:

sendToken(
    "avalanche", // destination chain name
    "0xF16DfB26e1FEc993E085092563ECFAEaDa7eD7fD", // some destination wallet address (should be your own)
    "axlUSDC", // asset symbol, can be differ by chain, see above
    100000000 // amount (in atomic units)
)

Once you call sendToken(), watch for the tokens to appear at the address on the destination chain.

Call sendToken() on a Cosmos-based source chain

For Cosmos-based source chains, sendToken() is a simple IBC transfer of any asset supported on the Quantum Portal network. The message is sent to the address QP1dv4u5k73pzqrxlzujxg3qp8kvc3pje7jtdvu72npnt5zhq05ejcsn5qme5, which is the designated address on the Quantum Portal network for receiving GMP messages, and includes a memo field with the following payload:

    {
      destination_chain,
      destination_address,
      payload: null,
      type: 3, // corresponds to the `sendToken` command on Quantum Portal
    }

Use the QPJS SDK to call sendToken()

The QPJS SDK allows any frontend application to call sendToken() with one line of JS code.

EVM chain example

import { QPAssetTransfer, CHAINS, Environment, SendTokenParams } from "@QP-network/QPjs-sdk";
import { ethers, Wallet } from "ethers";

const api = new QPAssetTransfer({ environment: Environment.TESTNET });

const getSigner = () => {
  const privateKey = PRIVATE_KEY;
  return new Wallet(privateKey);
};

async function test() {
  const provider = new ethers.providers.JsonRpcProvider(
    "https://api.avax-test.network/ext/bc/C/rpc"
  );
  const signer = getSigner().connect(provider);
  const requestOptions: SendTokenParams = {
    fromChain: CHAINS.TESTNET.AVALANCHE,
    toChain: CHAINS.TESTNET.OSMOSIS,
    destinationAddress: "osmo1x3z2vepjd7fhe30epncxjrk0lehq7xdqe8ltsn",
    asset: { symbol: "aUSDC" },
    amountInAtomicUnits: "5000000",
    options: {
      evmOptions: {
        signer,
        provider,
        txOptions: null as any,
        approveSendForMe: true,
      },
    },
  };
  return api.sendToken(requestOptions);
}

Cosmos-based chain example


import { QPAssetTransfer, CHAINS, Environment, SendTokenParams } from "@QP-network/QPjs-sdk";
import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing";

const api = new QPAssetTransfer({ environment: Environment.TESTNET });

const getSigner = async () => {
  const mnemonic = MNEMONIC;
  return DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { prefix: "osmo" });
};

async function test() {
  const offlineSigner = await getSigner();
  const requestOptions: SendTokenParams = {
    fromChain: CHAINS.TESTNET.OSMOSIS,
    toChain: CHAINS.TESTNET.AVALANCHE,
    destinationAddress: "0xB8Cd93C83A974649D76B1c19f311f639e62272BC",
    asset: { denom: "ibc/6F34E1BD664C36CE49ACC28E60D62559A5F96C4F9A6CCE4FC5A67B2852E24CFE" }, //aUSDC
    amountInAtomicUnits: "1000000",
    options: {
      cosmosOptions: {
        cosmosDirectSigner: offlineSigner,
        rpcUrl: "https://rpc.osmotest5.osmosis.zone",
        fee: {
            gas: "250000",
            amount: [{ denom: "uosmo", amount: "30000" }],
        },
      },
    },
  };
  return api.sendToken(requestOptions);
}

Transfer assets using a deposit address

A deposit address is a temporary one-time address created and monitored by Quantum Portal’s Relayer Services. Deposit addresses generally function for up to 24 hours.

Use a deposit address if:

  • You need functionality not offered by the sendToken() method, such as Cosmos-to-X.
  • You want to allow token transfers from wallets that do not interact with Quantum Portal, such as when withdrawing funds from a centralized exchange.

To transfer assets using a deposit address, install the QPJS SDK and initiate an QPAssetTransfer.

Build an Interchain Token

Interchain Tokens are ERC-20 tokens that are available on multiple blockchains. With Quantum Portal’s Interchain Token Service (ITS), you can either create new Interchain Tokens from scratch or update tokens that already exist on an Ethereum blockchain. If your token is not supported by Quantum Portal, you can turn it into an Interchain Token to make cross-chain transfers.

Edit this page