Swap across EVM & Tron
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 fund to a Deposit contract with the quote ID
- Bungee indexes the transaction and delivers the funds on the destination funds
API Behavior
- Both
userAddressandreceiverAddressrequired 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.
- Select Chains: Users choose the source and destination chains, which determine the tokens available for bridging.
- Fetch Routes: After selecting the tokens, retrieve the deposit route if available using the
/quoteendpoint. - Submitting a deposit via the Deposit contract: Obtain the transaction data from the
/quoteendpoint. Use this data to execute the deposit transaction on the source chain. - Track Transaction Status: Monitor the transaction's progress by polling the
/statusendpoint until the bridging process is complete.
info
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
Scripts
Quote and swap from Tron USDT to Polygon USDC (tronweb)
import { privateKeyToAccount } from "viem/accounts";
import { TronWeb } from "tronweb";
// Check if PRIVATE_KEY is set, set in console, i.e.: export PRIVATE_KEY=abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890
if (!process.env.PRIVATE_KEY) {
console.error("Error: PRIVATE_KEY environment variable is not set");
console.error(
"Example: PRIVATE_KEY=abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
);
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=abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
);
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);
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();
Quote and swap from Polygon USDC to Tron USDT (Viem)
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=abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890
if (!process.env.PRIVATE_KEY) {
console.error("Error: PRIVATE_KEY environment variable is not set");
console.error(
"Example: PRIVATE_KEY=abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
);
process.exit(1);
}
// Create account from private key
const account = privateKeyToAccount(`${process.env.PRIVATE_KEY.replace(/^0x/, "")}`);
// 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=abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
);
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);
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();