Skip to main content
Bungee also supports swap assets from any EVM chain to Tron and from Tron to any EVM chain. This guide walks you through swapping assets between EVM chains and Tron using Bungee. It covers transaction options, and current limitations.

Quick Start

Key differences

Here’s what’s different when integrating Tron Bungee API compared to standard EVM chains: Transaction Flow
  • User sends funds to a Deposit contract with the quote ID
  • Bungee indexes the transaction and delivers the funds on the destination chain
API Behavior
  • Both userAddress and receiverAddress required for quotes
  • The only asset supported from/to Tron is Tron USDT
  • There is no Fee collection at the moment

Integration Steps

The “Auto routing onchain” guide from Bungee Docs outlines how to integrate the deposit flow as it is a quite similar process.
  1. Select Chains: Users choose the source and destination chains, which determine the tokens available for bridging.
  2. Fetch Routes: After selecting the tokens, retrieve the deposit route if available using the /quote endpoint.
  3. Submitting a deposit via the Deposit contract: Obtain the transaction data from the /quote endpoint. Use this data to execute the deposit transaction on the source chain.
  4. Track Transaction Status: Monitor the transaction’s progress by polling the /status endpoint until the bridging process is complete.
For Tron quotes, please ensure both userAddress and receiverAddress are defined.Also, note that this is a separate route from autoRoute since it is under depositRoute.

Examples

Queries

https://public-backend.bungee.exchange/api/v1/bungee/quote?userAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045&receiverAddress=TMe1CS54poswoTTrPAtqsKhry4RM7nvSF4&originChainId=137&destinationChainId=728126428&inputToken=0x3c499c542cef5e3811e1192ce70d8cc03d5c3359&outputToken=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t&inputAmount=5000000
https://public-backend.bungee.exchange/api/v1/bungee/quote?userAddress=TMe1CS54poswoTTrPAtqsKhry4RM7nvSF4&receiverAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045&originChainId=728126428&destinationChainId=8453&inputToken=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t&outputToken=0x833589fcd6edb6e08f4c7c32d4f71b54bda02913&inputAmount=9681288

Scripts

import { privateKeyToAccount } from "viem/accounts";
import { TronWeb } from "tronweb";

// Check if PRIVATE_KEY is set, set in console, i.e.: export PRIVATE_KEY=<PRIVATE_KEY_HEX_64>
if (!process.env.PRIVATE_KEY) {
  console.error("Error: PRIVATE_KEY environment variable is not set");
  console.error(
    "Example: PRIVATE_KEY=<PRIVATE_KEY_HEX_64>"
  );
  process.exit(1);
}

// Check if TRON_PRIVATE_KEY is set
if (!process.env.TRON_PRIVATE_KEY) {
  console.error("Error: TRON_PRIVATE_KEY environment variable is not set");
  console.error(
    "Example: TRON_PRIVATE_KEY=<PRIVATE_KEY_HEX_64>"
  );
  process.exit(1);
}

// Create account from private key
const account = privateKeyToAccount(`${process.env.PRIVATE_KEY}`);

// Derive TRON address from TRON private key
const tronPrivateKey = process.env.TRON_PRIVATE_KEY.replace(/^0x/, "");
const tronWeb = new TronWeb({
  fullHost: "https://api.trongrid.io",
  privateKey: tronPrivateKey,
});
const tronAddress = tronWeb.address.fromPrivateKey(tronPrivateKey);

// API and token parameters
const BUNGEE_API_BASE_URL = "https://public-backend.bungee.exchange";

// USDT contract address on Tron
const USDT_CONTRACT = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t";

// Function to get USDT balance
async function getUSDTBalance() {
  try {
    const contract = await tronWeb.contract().at(USDT_CONTRACT);
    const balance = await contract.balanceOf(tronAddress).call();
    return balance.toString();
  } catch (error) {
    console.error("Failed to get USDT balance:", error);
    throw error;
  }
}

// Function to get a quote
async function getQuote(params) {
  try {
    const url = `${BUNGEE_API_BASE_URL}/api/v1/bungee/quote`;
    const queryParams = new URLSearchParams(params);
    const fullUrl = `${url}?${queryParams}`;

    const response = await fetch(fullUrl);
    console.log(fullUrl);
    const data = await response.json();
    const serverReqId = response.headers.get("server-req-id");

    // Check for errors
    if (!data.success) {
      throw new Error(
        `Quote error: ${data.statusCode}: ${data.message}. server-req-id: ${serverReqId}`
      );
    }

    // Check if autoRoute exists
    if (!data.result.depositRoute) {
      throw new Error(`No depositRoute available. server-req-id: ${serverReqId}`);
    }

    const quoteId = data.result.depositRoute.quoteId;
    console.log("- Quote ID:", quoteId);

    // Extract data based on the response structure
    const txData = data.result.depositRoute.txData;

    return {
      quoteId,
      txData,
      fullResponse: data,
    };
  } catch (error) {
    console.error("Failed to get quote:", error);
    throw error;
  }
}

// Function to submit the transaction to the Deposit contract
async function submitNativeTransaction(txData) {
  try {
    console.log("- Submitting transaction to Deposit contract...");
    console.log("  To:", txData.to);
    console.log("  Value:", txData.value);
    console.log("  Data:", txData.data);

    const transaction = await tronWeb.transactionBuilder.triggerSmartContract(
      tronWeb.address.fromHex(txData.to),
      "", // Empty functionSelector when using 'input'
      {
        input: txData.data.replace(/^0x/, ""), // Full data including function selector + params + quoteId
        callValue: parseInt(txData.value) || 0,
        feeLimit: 100000000,
      },
      [], // Empty parameters since we're using 'input'
      tronAddress
    );

    // Sign and send
    const signed = await tronWeb.trx.sign(transaction.transaction);
    const result = await tronWeb.trx.sendRawTransaction(signed);

    if (!result.result) {
      throw new Error(`Transaction failed: ${JSON.stringify(result)}`);
    }

    const hash = result.txid;
    console.log("- Transaction sent:", hash);

    // Wait for confirmation
    let receipt;
    while (!receipt) {
      await new Promise((resolve) => setTimeout(resolve, 3000));
      try {
        const txInfo = await tronWeb.trx.getTransactionInfo(hash);
        if (txInfo?.blockNumber) {
          receipt = { blockNumber: txInfo.blockNumber, status: txInfo.receipt ? "success" : "failed", transactionHash: hash };
        }
      } catch { }
    }

    console.log("- Transaction confirmed in block:", receipt.blockNumber);

    return { hash, receipt };
  } catch (error) {
    console.error("Failed to submit transaction:", error);
    throw error;
  }
}

// Function to check the status of a request
async function checkStatus(requestHash) {
  try {

    const response = await fetch(
      `${BUNGEE_API_BASE_URL}/api/v1/bungee/status?requestHash=${requestHash}`
    );
    const data = await response.json();

    if (!data.success) {
      throw new Error(
        `Status error: ${data.error?.message || "Unknown error"}`
      );
    }

    return data.result[0];
  } catch (error) {
    console.error("Failed to check status:", error);
    throw error;
  }
}

// Main function to handle the flow
async function main() {
  try {
    console.log("Starting Bungee Tron to EVM Token Test (Deposit Route)...");

    // Get USDT balance
    console.log("\n1. Getting USDT balance...");
    const usdtBalance = await getUSDTBalance();
    console.log("- USDT Balance:", usdtBalance);

    // Quote parameters for ERC20 token test
    const quoteParamsERC20 = {
      userAddress: tronAddress,
      receiverAddress: account.address,
      originChainId: 728126428, // Tron
      destinationChainId: 137, // Polygon
      inputToken: USDT_CONTRACT, // USDT on Tron
      outputToken: "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359", // USDC on Polygon
      inputAmount: usdtBalance, // Use actual USDT balance
    };

    // Get the quote and extract data
    console.log("\n2. Getting quote...");
    const quoteResponse = await getQuote(quoteParamsERC20);
    //console.log(JSON.stringify(quoteResponse.fullResponse, null, 2));

    if (quoteResponse.txData) {
      // Submit the transaction
      console.log("\n3. Submitting transaction...");
      const { hash, receipt } = await submitNativeTransaction(quoteResponse.txData);

      console.log(
        "\n4. Transaction submitted:",
        "\n- Hash:",
        hash,
        "\n- Status:",
        receipt.status
      );

      // Wait for 5 seconds before checking status
      console.log("\n5. Waiting for 5 seconds...");
      let status;
      do {
        await new Promise((resolve) => setTimeout(resolve, 5000));
        console.log("\n6. Checking status...");
        try {
          status = await checkStatus(quoteResponse.quoteId);
          console.log("- Status details:", status.bungeeStatusCode);
        } catch (error) {
          console.error(
            "Failed to check status:",
            error?.message || "Unknown error"
          );
        }
      } while (status?.bungeeStatusCode !== 3 && status?.bungeeStatusCode !== 4);

      console.log(
        "\n7. Transaction complete:",
        "\n- Hash:",
        status.destinationData?.txHash || "Transaction hash not available"
      );
    } else {
      console.log("No transaction data available in the quote response");
    }
  } catch (error) {
    console.error("Error in processing:", error?.shortMessage || error.message);
    throw error;
  }
}

// Execute the main function
main();
import { privateKeyToAccount } from "viem/accounts";
import { createPublicClient, http, createWalletClient } from "viem";
import { polygon } from "viem/chains";
import { TronWeb } from "tronweb";

// Check if PRIVATE_KEY is set, set in console, i.e.: export PRIVATE_KEY=<PRIVATE_KEY_HEX_64>
if (!process.env.PRIVATE_KEY) {
  console.error("Error: PRIVATE_KEY environment variable is not set");
  console.error(
    "Example: PRIVATE_KEY=<PRIVATE_KEY_HEX_64>"
  );
  process.exit(1);
}

// Create account from private key
// Normalize private key to ensure it has 0x prefix (required by viem)
const rawPrivateKey = process.env.PRIVATE_KEY;
const normalizedPrivateKey = rawPrivateKey.startsWith('0x') 
  ? rawPrivateKey 
  : `0x${rawPrivateKey}`;
const account = privateKeyToAccount(normalizedPrivateKey);

// Create Viem clients
const publicClient = createPublicClient({
  chain: polygon,
  transport: http(),
});

const walletClient = createWalletClient({
  account,
  chain: polygon,
  transport: http(),
});

// Check if TRON_PRIVATE_KEY is set
if (!process.env.TRON_PRIVATE_KEY) {
  console.error("Error: TRON_PRIVATE_KEY environment variable is not set");
  console.error(
    "Example: TRON_PRIVATE_KEY=<PRIVATE_KEY_HEX_64>"
  );
  process.exit(1);
}

// Derive TRON address from TRON private key
const tronPrivateKey = process.env.TRON_PRIVATE_KEY.replace(/^0x/, "");
const tronWeb = new TronWeb({
  fullHost: "https://api.trongrid.io",
  privateKey: tronPrivateKey,
});
const tronAddress = tronWeb.address.fromPrivateKey(tronPrivateKey);

// API and token parameters
const BUNGEE_API_BASE_URL = "https://public-backend.bungee.exchange";

// Quote parameters for ERC20 token test (USDC from Optimism to Arbitrum)
const quoteParamsERC20 = {
  userAddress: account.address,
  receiverAddress: tronAddress,
  originChainId: 137, // Polygon
  destinationChainId: 728126428, // Tron
  inputToken: "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359", // USDC on Polygon
  outputToken: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", // USDT
  inputAmount: "10000000", // 10 USDC (6 decimals)
};

// Function to get a quote
async function getQuote(params) {
  try {
    const url = `${BUNGEE_API_BASE_URL}/api/v1/bungee/quote`;
    const queryParams = new URLSearchParams(params);
    const fullUrl = `${url}?${queryParams}`;

    const response = await fetch(fullUrl);
    console.log(fullUrl);
    const data = await response.json();
    const serverReqId = response.headers.get("server-req-id");

    // Check for errors
    if (!data.success) {
      throw new Error(
        `Quote error: ${data.statusCode}: ${data.message}. server-req-id: ${serverReqId}`
      );
    }

    // Check if autoRoute exists
    if (!data.result.depositRoute) {
      throw new Error(`No depositRoute available. server-req-id: ${serverReqId}`);
    }

    const quoteId = data.result.depositRoute.quoteId;
    console.log("- Quote ID:", quoteId);

    // Extract data based on the response structure
    const txData = data.result.depositRoute.txData;

    return {
      quoteId,
      txData,
      fullResponse: data,
    };
  } catch (error) {
    console.error("Failed to get quote:", error);
    throw error;
  }
}

// Function to submit the transaction to the inbox contract
async function submitNativeTransaction(txData) {
  try {
    console.log("- Submitting transaction to inbox contract...");
    console.log("  To:", txData.to);
    console.log("  Value:", txData.value);
    console.log("  Data:", txData.data);

    // Send the transaction
    const hash = await walletClient.sendTransaction({
      to: txData.to,
      value: BigInt(txData.value),
      data: txData.data,
    });

    console.log("- Transaction sent:", hash);

    // Wait for transaction to be mined
    const receipt = await publicClient.waitForTransactionReceipt({ hash });
    console.log("- Transaction confirmed in block:", receipt.blockNumber);

    return {
      hash,
      receipt,
    };
  } catch (error) {
    console.error("Failed to submit transaction:", error);
    throw error;
  }
}

// Function to check the status of a request
async function checkStatus(requestHash) {
  try {

    const response = await fetch(
      `${BUNGEE_API_BASE_URL}/api/v1/bungee/status?requestHash=${requestHash}`
    );
    const data = await response.json();

    if (!data.success) {
      throw new Error(
        `Status error: ${data.error?.message || "Unknown error"}`
      );
    }

    return data.result[0];
  } catch (error) {
    console.error("Failed to check status:", error);
    throw error;
  }
}

// Main function to handle the flow
async function main() {
  try {
    console.log("Starting Bungee EVM to Tron Test (Deposit Route)...");

    // Get the quote and extract data
    console.log("\n1. Getting quote...");
    const quoteResponse = await getQuote(quoteParamsERC20);
    //console.log(JSON.stringify(quoteResponse.fullResponse, null, 2));

    if (quoteResponse.txData) {
      // Submit the transaction
      console.log("\n2. Submitting transaction...");
      const { hash, receipt } = await submitNativeTransaction(quoteResponse.txData);

      console.log(
        "\n3. Transaction submitted:",
        "\n- Hash:",
        hash,
        "\n- Status:",
        receipt.status
      );

      // Wait for 5 seconds before checking status
      console.log("\n4. Waiting for 5 seconds...");
      let status;
      do {
        await new Promise((resolve) => setTimeout(resolve, 5000));
        console.log("\n5. Checking status...");
        try {
          status = await checkStatus(quoteResponse.quoteId);
          console.log("- Status details:", status.bungeeStatusCode);
        } catch (error) {
          console.error(
            "Failed to check status:",
            error?.message || "Unknown error"
          );
        }
      } while (status?.bungeeStatusCode !== 3 && status?.bungeeStatusCode !== 4);

      console.log(
        "\n6. Transaction complete:",
        "\n- Hash:",
        status.destinationData?.txHash || "Transaction hash not available"
      );
    } else {
      console.log("No transaction data available in the quote response");
    }
  } catch (error) {
    console.error("Error in processing:", error?.shortMessage || error.message);
    throw error;
  }
}

// Execute the main function
main();