Skip to main content

Bungee Manual

This page covers how to integrate Bungee Manual for cross-chain swaps as it is useful when:

  • You need more control over the cross-chain process
  • You want to compare multiple routes before executing

This method involves:

  1. Getting a quote from the Bungee API
  2. Building the transaction from a selected route
  3. Handling any required token approvals
  4. Executing the cross-chain transaction
  5. Monitoring the status via the API

Integration Steps

Step 1: Get a Quote

For ERC20 & Native tokens, request a quote with all required parameters, including the option to enable manual routes.

API Reference: Quote Endpoint

important

The enableManual: true parameter is required to receive manual routes in the response. Without this parameter, only auto routes will be returned.

const BUNGEE_API_BASE_URL = "https://public-backend.bungee.exchange";

const quoteParamsERC20 = {
userAddress: USER_ADDRESS,
originChainId: 10, // Optimism
destinationChainId: 42161, // Arbitrum
inputToken: "0x0b2c639c533813f4aa9d7837caf62653d097ff85", // USDC on Optimism
inputAmount: "10000000", // 10 USDC (with decimals)
receiverAddress: RECEIVER_ADDRESS,
outputToken: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", // USDC on Arbitrum
enableManual: true, // Important: enables manual routes
};

async function getQuote(params) {
const url = `${BUNGEE_API_BASE_URL}/api/v1/bungee/quote`;
const queryParams = new URLSearchParams(params);
const queryUrl = `${url}?${queryParams}`;

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

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

if (!data.result.manualRoutes || data.result.manualRoutes.length === 0) {
throw new Error(
`No manual routes available. server-req-id: ${serverReqId}`
);
}

return {
manualRoutes: data.result.manualRoutes,
completeResponse: data,
serverReqId,
};
}

Step 2: Build the Transaction from Selected Route

The quote response contains multiple manual routes. Select one and build the transaction data by submitting the quote ID of the selected route.

API Reference: Build Transaction Endpoint

async function buildTransaction(quoteId) {
const response = await fetch(
`${BUNGEE_API_BASE_URL}/api/v1/bungee-manual/build-tx?quoteId=${quoteId}`
);
const data = await response.json();
const serverReqId = response.headers.get("server-req-id");

if (!data.success) {
throw new Error(
`Build TX error: ${data.statusCode}: ${data.message}. server-req-id: ${serverReqId}`
);
}

return data.result;
}

Step 3: Handle Token Approvals (if needed)

Check if the token needs to be approved for spending and create an approval transaction if required. Approval data is included in the build transaction response.

const ERC20_ABI = parseAbi([
"function approve(address spender, uint256 amount) returns (bool)",
"function allowance(address owner, address spender) view returns (uint256)",
]);

async function buildApprovalTransaction(chainId, approvalData) {
// Check current allowance
const currentAllowance = await publicClient.readContract({
address: approvalData.tokenAddress,
abi: ERC20_ABI,
functionName: "allowance",
args: [approvalData.userAddress, approvalData.spenderAddress],
});

// If allowance is sufficient, no approval needed
if (currentAllowance >= BigInt(approvalData.amount)) {
return null;
}

// Create approval transaction data
const data = encodeFunctionData({
abi: ERC20_ABI,
functionName: "approve",
args: [approvalData.spenderAddress, approvalData.amount],
});

return {
to: approvalData.tokenAddress,
data,
chainId: chainId,
value: "0x00",
};
}

Step 4: Execute the Transactions

From the build transaction response, you can get the transaction data and send it to the blockchain.

async function sendTransaction(txData) {
const hash = await walletClient.sendTransaction({
to: txData.to,
data: txData.data,
value: txData.value ? BigInt(txData.value) : BigInt(0),
});

const receipt = await publicClient.waitForTransactionReceipt({ hash });
return { hash, receipt };
}

Step 5: Monitor the Transaction Status

After submitting the request, check its status to track progress.

API Reference: Status Endpoint

async function checkStatus(txHash) {
const response = await fetch(
`${BUNGEE_API_BASE_URL}/api/v1/bungee/status?txHash=${txHash}`
);
const data = await response.json();
const serverReqId = response.headers.get("server-req-id");

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

return data.result;
}

async function pollForCompletion(
requestHash,
interval = 5000,
maxAttempts = 60
) {
let attempts = 0;

while (attempts < maxAttempts) {
const status = await checkStatus(requestHash);
const code = status[0]?.bungeeStatusCode;

if (code === 3) {
return status; // Transaction complete
}

attempts++;
await new Promise((resolve) => setTimeout(resolve, interval));
}

throw new Error("Polling timed out. Transaction may not have completed.");
}

For a detailed explanation of all status codes, see the Request Status Codes guide.

Complete Integration Example (ERC20)

Complete Integration Example for ERC20 Using Viem
import { createPublicClient, createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { optimism } from "viem/chains";
import { logToJson } from "../utils/logger.js";
import path from "node:path";

// Define script-specific output path
const OUTPUT_DIR = path.resolve(
process.cwd(),
"scripts/examples/output/auto-native"
);

// 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(`0x${process.env.PRIVATE_KEY}`);

const publicClient = createPublicClient({
chain: optimism,
transport: http(),
});

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

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

// Quote parameters for native token test (ETH from Optimism to Arbitrum)
const quoteParamsNative = {
userAddress: account.address,
receiverAddress: account.address,
originChainId: 10, // Optimism
destinationChainId: 42161, // Arbitrum
inputToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", // ETH on Optimism
outputToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", // ETH on Arbitrum
inputAmount: "100000000000000", // 0.0001 ETH in wei
};

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

// Log the request parameters
logToJson(params, "quote_request_params", OUTPUT_DIR);

const response = await fetch(fullUrl);
const data = await response.json();

// Log the response
logToJson(data, "quote_response", OUTPUT_DIR);

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

if (!data.result.autoRoute) {
throw new Error("No autoRoute available for native token transfer");
}

// Extract transaction data for native token
const txData = data.result.autoRoute.txData;
const requestHash = data.result.autoRoute.requestHash;

const quoteId = data.result.autoRoute.quoteId;
const requestType = data.result.autoRoute.requestType;
console.log("- Quote ID:", quoteId);
console.log("- Request Type:", requestType);

// Log request hash
if (data.result.autoRoute.requestHash) {
console.log("- Request Hash:", data.result.autoRoute.requestHash);
}

return {
quoteId,
requestType,
txData,
requestHash,
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 {
logToJson(txData, "tx_data", OUTPUT_DIR);

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 mined 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 {
logToJson({ requestHash }, "status_request_params", OUTPUT_DIR);

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

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;
}
}

async function main() {
try {
console.log("Starting Bungee Native Token Test (Auto Route)...");

// Get the quote and extract data
console.log("\n1. Getting quote...");
const { txData, requestHash } = await getQuote(quoteParamsNative);

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

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

console.log("- Request Hash:", requestHash);

// 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(requestHash);
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();