Skip to main content

Overview

You can bridge tokens between any EVM-compatible chains supported by deBridge, including:
  • Ethereum
  • Polygon
  • Arbitrum
  • BNB Chain
  • Optimism
  • Base
  • Avalanche

Complete Example: Polygon to Arbitrum

This example transfers 5 USDC from Polygon to Arbitrum.
poly-arb-usdc-5.ts
import 'dotenv/config';
import {
  ethers,
  Wallet,
  Contract,
  formatUnits,
  TransactionResponse,
  TransactionReceipt,
  TransactionRequest
} from "ethers";
import { createDebridgeBridgeOrder } from '../../utils/deBridge/createDeBridgeOrder';
import { deBridgeOrderInput } from '../../types';
import { erc20Abi } from '../../constants';
import { getEnvConfig, getJsonRpcProviders } from '../../utils';
import { USDC } from '../../utils/tokens';
import { CHAIN_IDS } from '../../utils/chains';

async function main() {
  const { privateKey } = getEnvConfig();
  const { polygonProvider } = await getJsonRpcProviders();

  // Setup wallet and signer
  const wallet = new Wallet(privateKey);
  const signer = wallet.connect(polygonProvider);
  const senderAddress = await signer.getAddress();
  console.log(`Wallet Address: ${senderAddress}`);

  // Prepare order parameters
  const usdcDecimals = 6;
  const amountToSend = "5";
  const amountInAtomicUnit = ethers.parseUnits(amountToSend, usdcDecimals);

  const orderInput: deBridgeOrderInput = {
    srcChainId: CHAIN_IDS.Polygon.toString(),
    srcChainTokenIn: USDC.POLYGON,
    srcChainTokenInAmount: amountInAtomicUnit.toString(),
    dstChainId: CHAIN_IDS.Arbitrum.toString(),
    dstChainTokenOut: USDC.ARBITRUM,
    dstChainTokenOutRecipient: senderAddress,
    account: senderAddress,
    srcChainOrderAuthorityAddress: wallet.address,
    dstChainOrderAuthorityAddress: wallet.address,
  };

  console.log("Creating deBridge order...");
  const order = await createDebridgeBridgeOrder(orderInput);

  if (!order?.tx?.to || !order?.tx?.data) {
    throw new Error("Invalid transaction request from createDebridgeBridgeOrder");
  }

  console.log("Order Estimation:", order.estimation);
  const transactionRequest: TransactionRequest = order.tx;

  // Approve token spending
  const spenderAddress = transactionRequest.to;
  if (!spenderAddress) {
    throw new Error("Missing spender address");
  }

  console.log(`Checking token approval...`);
  const tokenContract = new Contract(orderInput.srcChainTokenIn, erc20Abi, signer);
  const requiredAmount = BigInt(order.estimation.srcChainTokenIn.amount);

  try {
    const currentAllowance: bigint = await tokenContract.allowance(
      senderAddress,
      spenderAddress
    );
    console.log(`Current allowance: ${formatUnits(currentAllowance, usdcDecimals)} USDC`);

    if (currentAllowance < requiredAmount) {
      console.log("Insufficient allowance. Approving...");
      const approveTx: TransactionResponse = await tokenContract.approve(
        spenderAddress,
        requiredAmount
      );

      console.log(`Approve tx: ${approveTx.hash}`);
      console.log(`View on Polygonscan: https://polygonscan.com/tx/${approveTx.hash}`);
      
      const approveTxReceipt: TransactionReceipt | null = await approveTx.wait();

      if (approveTxReceipt?.status === 1) {
        console.log("Approval successful!");
      } else {
        throw new Error(`Approval failed. Status: ${approveTxReceipt?.status}`);
      }
    } else {
      console.log("Sufficient allowance already granted");
    }
  } catch (error) {
    console.error("Error during token approval:", error);
    throw new Error("Token approval failed");
  }

  // Send bridge transaction
  try {
    console.log("Sending bridge transaction...");
    const txResponse: TransactionResponse = await signer.sendTransaction(transactionRequest);

    console.log(`Transaction sent: ${txResponse.hash}`);
    console.log(`View on Polygonscan: https://polygonscan.com/tx/${txResponse.hash}`);

    const txReceipt: TransactionReceipt | null = await txResponse.wait();

    if (txReceipt) {
      console.log(`Status: ${txReceipt.status === 1 ? 'Success' : 'Failed'}`);
      console.log(`Block: ${txReceipt.blockNumber}`);
      console.log(`Gas used: ${txReceipt.gasUsed.toString()}`);
    }
  } catch (error) {
    console.error("Error sending transaction:", error);
    process.exitCode = 1;
  }
}

main().catch((error) => {
  console.error("Fatal error:", error);
  process.exitCode = 1;
});

Example: Arbitrum to Polygon

Reverse the chain IDs to bridge from Arbitrum to Polygon:
arb-poly-usdc-5.ts
const orderInput: deBridgeOrderInput = {
  srcChainId: CHAIN_IDS.Arbitrum.toString(),
  srcChainTokenIn: USDC.ARBITRUM,
  srcChainTokenInAmount: amountInAtomicUnit.toString(),
  dstChainId: CHAIN_IDS.Polygon.toString(),
  dstChainTokenOut: USDC.POLYGON,
  dstChainTokenOutRecipient: senderAddress,
  account: senderAddress,
  srcChainOrderAuthorityAddress: wallet.address,
  dstChainOrderAuthorityAddress: wallet.address,
};

Example: BNB Chain to Arbitrum

BNB Chain USDC uses 18 decimals instead of the typical 6 decimals.
bnb-usdc-to-arb.ts
const { bnbProvider } = await getJsonRpcProviders();
const signer = wallet.connect(bnbProvider);

const usdcDecimals = 18; // BNB USDC has 18 decimals
const amountToSend = "0.01";
const amountInAtomicUnit = ethers.parseUnits(amountToSend, usdcDecimals);

const orderInput: deBridgeOrderInput = {
  srcChainId: CHAIN_IDS.BNB.toString(),
  srcChainTokenIn: USDC.BNB,
  srcChainTokenInAmount: amountInAtomicUnit.toString(),
  dstChainTokenOutAmount: "auto",
  dstChainId: CHAIN_IDS.Arbitrum.toString(),
  dstChainTokenOut: USDC.ARBITRUM,
  dstChainTokenOutRecipient: wallet.address,
  account: wallet.address,
  srcChainOrderAuthorityAddress: wallet.address,
  dstChainOrderAuthorityAddress: wallet.address,
  referralCode: 31805,
};

Key Concepts

Token Decimals

Always use the correct decimal precision for each token:
// Most USDC tokens use 6 decimals
const polygonUSDC = ethers.parseUnits("5", 6); // 5000000

// BNB Chain USDC uses 18 decimals
const bnbUSDC = ethers.parseUnits("5", 18); // 5000000000000000000

Approval Flow

The approval process follows this pattern:
  1. Check current allowance
  2. Compare with required amount
  3. Approve if insufficient
  4. Wait for approval confirmation
  5. Proceed with bridge transaction
const currentAllowance = await tokenContract.allowance(
  senderAddress,
  spenderAddress
);

if (currentAllowance < requiredAmount) {
  const approveTx = await tokenContract.approve(
    spenderAddress,
    requiredAmount
  );
  await approveTx.wait();
}

Transaction Monitoring

Monitor both approval and bridge transactions:
const txReceipt = await txResponse.wait();

if (txReceipt?.status === 1) {
  console.log("Transaction successful!");
  console.log(`Block: ${txReceipt.blockNumber}`);
  console.log(`Gas used: ${txReceipt.gasUsed}`);
} else {
  console.error("Transaction failed");
}

Supported Chains

ChainChain IDUSDC DecimalsExplorer
Ethereum16etherscan.io
Polygon1376polygonscan.com
Arbitrum421616arbiscan.io
BNB Chain5618bscscan.com
Optimism106optimistic.etherscan.io
Base84536basescan.org

Best Practices

Check the current allowance to avoid unnecessary approval transactions:
const currentAllowance = await tokenContract.allowance(
  senderAddress,
  spenderAddress
);

if (currentAllowance >= requiredAmount) {
  console.log("Sufficient allowance already granted");
  // Skip approval
}
Always verify the token’s decimal precision:
const decimals = await tokenContract.decimals();
const amount = ethers.parseUnits(amountString, decimals);
Implement comprehensive error handling:
try {
  const txReceipt = await txResponse.wait();
  if (txReceipt?.status !== 1) {
    throw new Error("Transaction failed");
  }
} catch (error) {
  console.error("Transaction error:", error);
  process.exitCode = 1;
}
Always wait for transaction confirmation before proceeding:
// Wait for approval
await approveTx.wait();

// Then submit bridge transaction
const txResponse = await signer.sendTransaction(order.tx);
await txResponse.wait();

Troubleshooting

If the approval transaction fails, do not proceed with the bridge transaction.

Common Issues

Insufficient balance: Ensure you have enough tokens and native gas tokens:
const balance = await tokenContract.balanceOf(senderAddress);
const nativeBalance = await provider.getBalance(senderAddress);

console.log(`Token balance: ${formatUnits(balance, decimals)}`);
console.log(`Native balance: ${formatUnits(nativeBalance, 18)}`);
Wrong decimal precision: Verify token decimals:
const decimals = await tokenContract.decimals();
console.log(`Token decimals: ${decimals}`);
Gas estimation failure: Increase gas limit manually:
const txResponse = await signer.sendTransaction({
  ...transactionRequest,
  gasLimit: 500000,
});

Next Steps

Build docs developers (and LLMs) love