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:
- Getting a quote from the Bungee API
- Creating and submitting a request via the inbox contract
- 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();