Skip to main content
This page covers how to integrate Bungee Auto for samechain and crosschain swaps using Native tokens (ETH, POL, etc.) as input tokens by directly interacting with the Bungee Inbox contract.

Overview

The Inbox Contract integration is required for native tokens and can also be used for ERC20 tokens when:
  • Permit2 is not an option
  • The integration is directly onchain
  • You prefer a fully onchain approach
This method involves:
  1. Getting a quote from the Bungee API
  2. Creating and submitting a request via the inbox contract
  3. Monitoring the status via the API

Integration Steps

Step 1: Get a Quote

For native tokens, request a quote using the address 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE.
const BUNGEE_API_BASE_URL = "https://public-backend.bungee.exchange";

const quoteParamsNative = {
  userAddress: "0x...",
  originChainId: 10, // Optimism
  destinationChainId: 42161, // Arbitrum
  inputToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", // ETH on Optimism
  inputAmount: "100000000000000", // 0.0001 ETH in wei
  receiverAddress: "0x...",
  outputToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", // ETH on Arbitrum
};
When integrating Bungee Auto, you can specify the useInbox parameter to directly create a request onchain and bypass permit2 gasless signatures:
  • useInbox: Enables autoRoute.txData for ERC20 tokens and disables autoRoute.signTypedData

Example

/**
 * Get a quote with ERC20 onchain request information
 */
async function getQuoteWithUseInbox() {
  // Set up the parameters for the quote request
  const quoteParams = {
    userAddress: "0xYourUsersAddress",
    originChainId: "1", // Ethereum
    destinationChainId: "10", // Optimism
    inputToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum
    outputToken: "0x7F5c764cBc14f9669B88837ca1490cCa17c31607", // USDC on Optimism
    inputAmount: "1000000", // 1 USDC (6 decimals)
    receiverAddress: "0xYourUsersAddress",
    useInbox: true, // Enables autoRoute.txData for ERC20 tokens and disables autoRoute.signTypedData
  };

  // Build the URL with query parameters
  const url = `${BUNGEE_API_BASE_URL}/api/v1/bungee/quote`;
  const queryParams = new URLSearchParams(quoteParams);
  const fullUrl = `${url}?${queryParams}`;

  // Make the request
  const response = await fetch(fullUrl, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
  });

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

  return data.result;
}
Before submitting a user request to BungeeInbox, check if the request needs approval.You can verify this by checking if the field autoRoute.approvalData contains any data.
  • If approvalData is present and populated, it means approval is required.
  • If approvalData is empty, no approval is needed, and you can submit the request directly.
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);
    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.autoRoute) {
      throw new Error(`No autoRoute available. server-req-id: ${serverReqId}`);
    }

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

Step 2: Submit the Transaction to the Inbox Contract

For native tokens, submit the transaction directly to the inbox contract using the transaction data from the quote.
async function submitNativeTransaction(txData) {
  const hash = await walletClient.sendTransaction({
    to: txData.to,
    value: BigInt(txData.value),
    data: txData.data,
  });

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

  return { hash, receipt };
}

Step 3: Check Request Status

After submitting the request, check its status to track progress. You can implement a polling mechanism to continuously check until completion:
async function checkStatus(requestHash) {
  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];
}

async function pollForCompletion(
  requestHash,
  interval = 5000,
  maxAttempts = 60
) {
  let attempts = 0;
  while (attempts < maxAttempts) {
    const status = await checkStatus(requestHash);
    const code = status?.bungeeStatusCode;
    if (code === 3 || code === 4) {
      console.log("Transaction complete:", status.destinationData?.txHash);
      return status;
    }
    if (code === 5) {
      throw new Error(`Request expired. Status: ${JSON.stringify(status)}`);
    }
    if (code === 6) {
      throw new Error(`Request cancelled. Status: ${JSON.stringify(status)}`);
    }
    if (code === 7) {
      throw new Error(`Request refunded. Status: ${JSON.stringify(status)}`);
    }
    attempts++;
    await new Promise((resolve) => setTimeout(resolve, interval));
  }
  throw new Error("Polling timed out. Transaction may not have completed.");
}

Complete Integration Example

import { createPublicClient, createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { optimism } from "viem/chains";

// Check if PRIVATE_KEY is set
if (!process.env.PRIVATE_KEY) {
  console.error("Error: PRIVATE_KEY environment variable is not set");
  process.exit(1);
}

// Create account from private key
// Normalize private key to ensure it has exactly one "0x" prefix (required by viem)
const normalizedPrivateKey = `0x${process.env.PRIVATE_KEY.replace(/^0x/i, '')}`;
const account = privateKeyToAccount(normalizedPrivateKey);

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) {
  const url = `${BUNGEE_API_BASE_URL}/api/v1/bungee/quote`;
  const queryParams = new URLSearchParams(params);
  const fullUrl = `${url}?${queryParams}`;

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

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

  return {
    txData: data.result.autoRoute.txData,
    requestHash: data.result.autoRoute.requestHash,
  };
}

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

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

async function checkStatus(requestHash) {
  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];
}

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

  // Get the quote
  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("Transaction sent:", hash);

    // Poll for completion
    console.log("\n3. Checking status...");
    let status;
    let attempts = 0;
    const maxAttempts = 60;
    const interval = 5000;
    
    while (attempts < maxAttempts) {
      await new Promise((resolve) => setTimeout(resolve, interval));
      status = await checkStatus(requestHash);
      const code = status?.bungeeStatusCode;
      console.log("Status:", code);
      
      if (code === 3 || code === 4) {
        console.log("Transaction complete:", status.destinationData?.txHash);
        break;
      }
      
      if (code === 5) {
        throw new Error(`Request expired. Status: ${JSON.stringify(status)}`);
      }
      if (code === 6) {
        throw new Error(`Request cancelled. Status: ${JSON.stringify(status)}`);
      }
      if (code === 7) {
        throw new Error(`Request refunded. Status: ${JSON.stringify(status)}`);
      }
      
      attempts++;
    }
    
    if (attempts >= maxAttempts) {
      throw new Error(`Polling timed out after ${maxAttempts} attempts. Last status: ${JSON.stringify(status)}`);
    }
  }
}

main();