Bridge Asset

Complete guide to bridging ERC20 tokens and ETH across chains using Lxly.js

Overview

Asset bridging is the most fundamental cross-chain operation, enabling users to transfer tokens and native assets between different blockchain networks. This guide covers token transfers with comprehensive examples and best practices.

Key Features:

  • Token Transfers: Bridge ERC20 tokens between chains
  • ETH Bridging: Transfer native ETH across networks
  • Automatic Wrapping: Tokens are automatically wrapped on destination chains
  • 1:1 Backing: Perfect value preservation across all networks

Token Instance Creation

Initialize Tokens

const { LxLyClient, use } = require('@maticnetwork/lxlyjs');
const { Web3ClientPlugin } = require('@maticnetwork/maticjs-web3');

// Initialize client (see Client Initialization guide)
const client = await initializeClient();

// Create ERC20 token instances
const erc20Ethereum = client.erc20('0x3fd0A53F4Bf853985a95F4Eb3F9C9FDE1F8e2b53', 0);  // Network 0
const erc20Polygon = client.erc20('0x3cc6055f4e88638c46daa9cf5f5fc54a801e5f03', 1);   // Network 1

// ETH token (use zero address)
const ethToken = client.erc20('0x0000000000000000000000000000000000000000', 0);

Token Types

Token TypeAddressDescription
Native ETH0x0000000000000000000000000000000000000000Native Ether on Ethereum
ERC20 TokenContract addressAny ERC20-compliant token
Wrapped TokenGenerated addressToken representation on destination chain

Basic Asset Bridging

Bridge ERC20 Tokens

async function bridgeERC20() {
  const amount = '1000000000000000000';  // 1 token (18 decimals)
  const destinationAddress = '0xRecipientAddress';
  const destinationNetwork = 1;  // Polygon zkEVM
  
  try {
    // Check and handle approval if needed
    const isApprovalNeeded = await erc20Ethereum.isApprovalNeeded();
    if (isApprovalNeeded) {
      const allowance = await erc20Ethereum.getAllowance(userAddress);
      if (parseInt(allowance) < parseInt(amount)) {
        console.log('🔐 Approving tokens...');
        const approveResult = await erc20Ethereum.approve(amount);
        await approveResult.getReceipt();
        console.log('✅ Approval completed');
      }
    }
    
    // Bridge tokens
    const bridgeResult = await erc20Ethereum.bridgeAsset(
      amount,
      destinationAddress,
      destinationNetwork,
      {
        gasLimit: 300000,
        forceUpdateGlobalExitRoot: true
      }
    );
    
    const bridgeTxHash = await bridgeResult.getTransactionHash();
    console.log('Bridge transaction:', bridgeTxHash);
    
    return bridgeTxHash;
  } catch (error) {
    console.error('Bridge failed:', error);
    throw error;
  }
}

Bridge ETH

async function bridgeETH() {
  const ethAmount = '100000000000000000';  // 0.1 ETH
  const destinationAddress = '0xRecipientAddress';
  
  try {
    const bridgeResult = await ethToken.bridgeAsset(
      ethAmount,
      destinationAddress,
      1,  // Destination network (Polygon)
      {
        value: ethAmount,  // ETH value to send
        gasLimit: 250000
      }
    );
    
    const txHash = await bridgeResult.getTransactionHash();
    console.log('ETH bridge transaction:', txHash);
    
    return txHash;
  } catch (error) {
    console.error('ETH bridge failed:', error);
    throw error;
  }
}

Bridge with Permit

For tokens that support permit (gasless approvals):

async function bridgeWithPermit() {
  const amount = '2000000000000000000';  // 2 tokens
  const destinationAddress = '0xRecipientAddress';
  const destinationNetwork = 1;
  
  try {
    // Bridge with permit (single transaction, no separate approval)
    const bridgeResult = await erc20Ethereum.bridgeAssetWithPermit(
      amount,
      destinationAddress,
      destinationNetwork,
      {
        gasLimit: 350000,
        forceUpdateGlobalExitRoot: true
      }
    );
    
    const txHash = await bridgeResult.getTransactionHash();
    console.log('Bridge with permit transaction:', txHash);
    
    return txHash;
  } catch (error) {
    console.error('Bridge with permit failed:', error);
    throw error;
  }
}

Claiming Assets

Check Claim Status

async function checkClaimStatus(bridgeTransactionHash, sourceNetwork) {
  try {
    // Check if bridge is claimable
    const isClaimable = await client.isBridgeClaimable(bridgeTransactionHash, sourceNetwork);
    console.log('Is claimable:', isClaimable);
    
    // Check if already claimed
    const isClaimed = await client.isBridged(bridgeTransactionHash, sourceNetwork, 1);
    console.log('Is claimed:', isClaimed);
    
    return { isClaimable, isClaimed };
  } catch (error) {
    console.error('Error checking claim status:', error);
    throw error;
  }
}

Claim Bridged Assets

async function claimAssets(bridgeTransactionHash, sourceNetwork) {
  const destinationNetwork = 1;  // Claiming on Polygon
  
  try {
    // Get destination token instance
    const destinationToken = client.erc20('0x3cc6055f4e88638c46daa9cf5f5fc54a801e5f03', destinationNetwork);
    
    // Claim the bridged assets
    const claimResult = await destinationToken.claimAsset(
      bridgeTransactionHash,
      sourceNetwork,
      {
        gasLimit: 400000
      }
    );
    
    const claimTxHash = await claimResult.getTransactionHash();
    console.log('Claim transaction:', claimTxHash);
    
    return claimTxHash;
  } catch (error) {
    console.error('Claim failed:', error);
    throw error;
  }
}

Check Claimed Token Balances

async function verifyClaimedAssets(userAddress, destinationNetwork) {
  try {
    // Get destination token instance
    const destinationToken = client.erc20('0x3cc6055f4e88638c46daa9cf5f5fc54a801e5f03', destinationNetwork);
    
    // Check current balance
    const currentBalance = await destinationToken.getBalance(userAddress);
    console.log('Current balance on destination:', currentBalance);
    
    // Convert to readable format (assuming 18 decimals)
    const readableBalance = (parseInt(currentBalance) / 10**18).toString();
    console.log('Readable balance:', readableBalance, 'tokens');
    
    return currentBalance;
  } catch (error) {
    console.error('Error checking claimed assets:', error);
    throw error;
  }
}

Token Information

Get Token Metadata

// Get wrapped token address on destination
const wrappedTokenAddress = await sourceToken.bridge.getMappedTokenInfo(
  0,  // Origin network
  '0x3fd0A53F4Bf853985a95F4Eb3F9C9FDE1F8e2b53'  // Origin token address
);
console.log('Wrapped token address:', wrappedTokenAddress);

// Pre-calculate wrapped token address (even if not deployed yet)
const precalculatedAddress = await sourceToken.bridge.precalculatedMappedTokenInfo(
  0,  // Origin network
  '0x3fd0A53F4Bf853985a95F4Eb3F9C9FDE1F8e2b53'  // Origin token address
);
console.log('Precalculated address:', precalculatedAddress);

// Get origin token info from wrapped token
const originInfo = await destinationToken.bridge.getOriginTokenInfo(
  '0x3cc6055f4e88638c46daa9cf5f5fc54a801e5f03'  // Wrapped token address
);
console.log('Origin network:', originInfo[0]);
console.log('Origin token address:', originInfo[1]);
Edit on GitHub

Last updated on