Skip to main content

Inbox Contract (Native Token)

This page covers how to integrate Bungee Auto for same-chain and cross-chain swaps using Native tokens (ETH, MATIC, 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.

API Reference: Quote Endpoint

const quoteParamsNative = {
userAddress: "0x...",
originChainId: 10, // Optimism
destinationChainId: 42161, // Arbitrum
inputToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", // ETH on Arbitrum
inputAmount: "100000000000000", // 0.0001 ETH in wei
receiverAddress: "0x...",
outputToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", // ETH on Optimism
};
async function getQuote(params) {
const url = `https://public-backend.bungee.exchange/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.autoRoute) {
throw new Error(`No autoRoute available. server-req-id: ${serverReqId}`);
}

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

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 walletClient.waitForTransactionReceipt({ hash });

return { hash, receipt };
}

Step 3: Check Request Status​

After submitting the request, you'll need to check its status to track progress.

API Reference: Status Endpoint

async function checkStatus(requestHash) {
const response = await fetch(
`https://public-backend.bungee.exchange/bungee/status?id=${requestHash}`
);
const data = await response.json();

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

return data.result;
}

You can implement a polling mechanism to continuously check the status until completion:

async function pollForCompletion(
requestHash,
interval = 5000,
maxAttempts = 60
) {
let attempts = 0;
console.log("Polling for transaction status...");
while (attempts < maxAttempts) {
const status = await checkStatus(requestHash);
const code = status[0]?.bungeeStatusCode;
console.log(`Attempt ${attempts + 1}: Status code = ${code}`);
if (code === 3) {
console.log("Transaction complete:", status[0].destinationData.txHash);
return status;
}

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​

Complete Integration Using Viem
import { privateKeyToAccount } from "viem/accounts";
import { createWalletClient, http } from "viem";
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
const account = privateKeyToAccount(process.env.PRIVATE_KEY);
console.log("Account address:", account.address);

// Create wallet client for Optimism
const walletClient = createWalletClient({
account,
chain: optimism,
transport: http(),
});

// Constant parameters
const BUNGEE_API_BASE_URL = "https://public-backend.bungee.exchange";
const USER_ADDRESS = account.address;
const RECEIVER_ADDRESS = account.address;
const DESTINATION_CHAIN_ID = 42161; // Arbitrum
const ORIGIN_CHAIN_ID = 10; // Optimism
const OUTPUT_TOKEN = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; // Native ETH on Arbitrum
const INPUT_TOKEN = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; // Native ETH on Optimism
const INPUT_AMOUNT = "100000000000000"; // 0.0001 ETH

const quoteParamsNative = {
userAddress: USER_ADDRESS,
originChainId: ORIGIN_CHAIN_ID,
destinationChainId: DESTINATION_CHAIN_ID,
inputToken: INPUT_TOKEN,
inputAmount: INPUT_AMOUNT,
receiverAddress: RECEIVER_ADDRESS,
outputToken: OUTPUT_TOKEN,
};

// Function to get a quote
async function getQuote(params) {
const url = `${BUNGEE_API_BASE_URL}/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.autoRoute) {
throw new Error(`No autoRoute available. server-req-id: ${serverReqId}`);
}

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

// Function to submit a transaction to the inbox contract
async function submitNativeTransaction(txData) {
const hash = await walletClient.sendTransaction({
to: txData.to,
value: BigInt(txData.value),
data: txData.data,
});

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

return { hash, receipt };
}

// Function to check the status of a request
async function checkStatus(requestHash) {
try {
const response = await fetch(
`${BUNGEE_API_BASE_URL}/bungee/status?id=${requestHash}`
);
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}`
);
}

console.log("- Status:", data.result);
return data.result;
} catch (error) {
console.error("Failed to check status:", error);
throw error;
}
}

// Function to poll for completion
async function pollForCompletion(
requestHash,
interval = 5000,
maxAttempts = 60
) {
let attempts = 0;
console.log("Polling for transaction status...");

while (attempts < maxAttempts) {
const status = await checkStatus(requestHash);
const code = status[0]?.bungeeStatusCode;
console.log(`Attempt ${attempts + 1}: Status code = ${code}`);

if (code === 3) {
console.log("Transaction complete:", status[0].destinationData.txHash);
return status;
}

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

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

// Main function to handle the flow
async function main() {
try {
console.log("Starting Bungee Auto Native Token test...");
console.log("\n1. Getting quote...");
const quote = await getQuote(quoteParamsNative);
console.log(
"- Raw quote Response:",
JSON.stringify(quote.completeResponse, null, 2)
);

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

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

console.log("\n3. Polling for completion...");
const finalStatus = await pollForCompletion(quote.requestHash);

console.log(
"\n4. Transaction complete with destination hash:",
finalStatus[0].destinationData.txHash
);
} catch (error) {
console.error("Error in processing:", error?.shortMessage || error.message);
throw error;
}
}

main();