> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bungee.exchange/llms.txt
> Use this file to discover all available pages before exploring further.

# Manual routing

> This page covers how to integrate Bungee Manual for crosschain swaps as it is useful when:

* You need more control over the crosschain 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 crosschain transaction
5. Monitoring the status via the API

<Info>
  * In Bungee Manual, the `slippage` you provide applies only to the swap that may occur on the origin chain before bridging. If no origin swap is needed, the slippage setting does not apply. The bridge step itself does not use swap-style slippage because bridges have different mechanics.
  * In Bungee Auto, the `slippage` parameter is applied to the full end-to-end route (origin swap, bridge step, and any destination swap where applicable).
</Info>

## Integration Steps[​](#integration-steps "Direct link to Integration Steps")

### Step 1: Get a Quote[​](#step-1-get-a-quote "Direct link to 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

<Info>
  The `enableManual: true` parameter is **required** to receive manual routes in the response. Without this parameter, only auto routes will be returned.
</Info>

```javascript theme={null}
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[​](#step-2-build-the-transaction-from-selected-route "Direct link to 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

```javascript theme={null}
// After getting the quote response with manualRoutes
const { manualRoutes } = await getQuote(quoteParamsERC20);

// Inspect the manualRoutes array and let the user pick a route
// For example, selecting the first route:
const chosenManualRoute = manualRoutes[0];

// Extract the quoteId from the chosen manual route
const quoteId = chosenManualRoute.quoteId;

// Build the transaction using the quoteId
async function buildTransaction(quoteId) {
  const response = await fetch(
    `${BUNGEE_API_BASE_URL}/api/v1/bungee/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}`
    );
  }

  // The build-tx response contains txData, approvalData, and requestHash
  return data.result;
}

// Call build-tx to get the transaction data
const buildResult = await buildTransaction(quoteId);
const txData = buildResult.txData; // Transaction data to execute
const requestHash = buildResult.requestHash; // Use this for status checking
```

### Step 3: Handle Token Approvals (if needed)[​](#step-3-handle-token-approvals-if-needed "Direct link to 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.

```js theme={null}
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[​](#step-4-execute-the-transactions "Direct link to Step 4: Execute the Transactions")

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

```js theme={null}
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 };
}
```

<Note>
  If routing via Stargate, include a `msg.value` equal to the `protocolFees.amount` and ensure the address has enough native tokens to cover it.
</Note>

### Step 5: Monitor the Transaction Status[​](#step-5-monitor-the-transaction-status "Direct link to Step 5: Monitor the Transaction Status")

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

> **API Reference**: Status Endpoint

```javascript theme={null}
async function checkStatus(requestHash) {
  const response = await fetch(
    `${BUNGEE_API_BASE_URL}/api/v1/bungee/status?requestHash=${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}`
    );
  }

  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;

    // Terminal success states
    if (code === 3) {
      // FULFILLED
      return status;
    }
    if (code === 4) {
      // SETTLED
      return status;
    }

    // Terminal failure states
    if (code === 5) {
      // EXPIRED
      throw new Error(`Request expired. Status: ${JSON.stringify(status)}`);
    }
    if (code === 6) {
      // CANCELLED
      throw new Error(`Request cancelled. Status: ${JSON.stringify(status)}`);
    }
    if (code === 7) {
      // REFUNDED
      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.");
}
```

For a detailed explanation of all status codes, see the [Request Status Codes](/integrate/integration-guides/status-codes) guide.

## Complete Integration Example (ERC20)[​](#complete-integration-example-erc20 "Direct link to Complete Integration Example (ERC20)")

<Accordion title="Complete Integration Example for ERC20 Using Viem">
  ```javascript theme={null}
  import { createPublicClient, createWalletClient, http } from "viem";
  import { privateKeyToAccount } from "viem/accounts";
  import { optimism } from "viem/chains";
  // Check if PRIVATE_KEY is set, set in console, i.e.: export PRIVATE_KEY=<YOUR_PRIVATE_KEY>
  if (!process.env.PRIVATE_KEY) {
    console.error("Error: PRIVATE_KEY environment variable is not set");
    console.error(
      "Example: export PRIVATE_KEY=<YOUR_PRIVATE_KEY>"
    );
    process.exit(1);
  }

  // Create account from private key
  const normalizedPrivateKey = process.env.PRIVATE_KEY.startsWith("0x")
    ? process.env.PRIVATE_KEY
    : `0x${process.env.PRIVATE_KEY}`;
  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 ERC20 token transfer (USDC from Optimism to Arbitrum)
  const quoteParamsERC20 = {
    userAddress: account.address,
    receiverAddress: account.address,
    originChainId: 10, // Optimism
    destinationChainId: 42161, // Arbitrum
    inputToken: "0x0b2c639c533813f4aa9d7837caf62653d097ff85", // USDC on Optimism
    outputToken: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", // USDC on Arbitrum
    inputAmount: "10000000", // 10 USDC (with decimals)
    enableManual: true, // Important: enables manual routes
  };

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

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

      if (!data.result.manualRoutes || data.result.manualRoutes.length === 0) {
        throw new Error("No manual routes available");
      }

      return {
        manualRoutes: data.result.manualRoutes,
        fullResponse: data,
      };
    } catch (error) {
      console.error("Failed to get quote:", error);
      throw error;
    }
  }

  // Function to build transaction from quote ID
  async function buildTransaction(quoteId) {
    try {
      const response = await fetch(
        `${BUNGEE_API_BASE_URL}/api/v1/bungee/build-tx?quoteId=${quoteId}`
      );
      const data = await response.json();

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

      return data.result;
    } catch (error) {
      console.error("Failed to build transaction:", error);
      throw error;
    }
  }

  // Function to submit the ERC20 transaction
  async function submitERC20Transaction(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 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 {
      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;
    }
  }

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

      // Step 1: Get the quote with manual routes
      console.log("\n1. Getting quote with manual routes...");
      const { manualRoutes } = await getQuote(quoteParamsERC20);

      if (!manualRoutes || manualRoutes.length === 0) {
        throw new Error("No manual routes available");
      }

      // Step 2: Select a route (using the first route as an example)
      console.log("\n2. Selecting route...");
      const selectedRoute = manualRoutes[0];
      const quoteId = selectedRoute.quoteId;
      console.log("- Selected Quote ID:", quoteId);
      console.log("- Available routes:", manualRoutes.length);

      // Step 3: Build the transaction from the selected route
      console.log("\n3. Building transaction...");
      const buildResult = await buildTransaction(quoteId);
      const txData = buildResult.txData;
      const approvalData = buildResult.approvalData;
      const requestHash = buildResult.requestHash;

      // Step 4: Handle token approvals if needed
      if (approvalData) {
        console.log("\n4. Handling token approvals...");
        // For ERC20 tokens, approvals are required before transfer
        // Check current allowance and create approval transaction if needed
        console.log("- Approval required:", approvalData);
        // TODO: Implement approval transaction if needed
      } else {
        console.log("\n4. No approvals required");
      }

      // Step 5: Submit the transaction
      console.log("\n5. Submitting transaction...");
      const { hash, receipt } = await submitERC20Transaction(txData);

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

      // Step 6: Poll for completion
      // Terminal states: 3 = FULFILLED, 4 = SETTLED (success), 5 = EXPIRED, 6 = CANCELLED, 7 = REFUNDED (failure)
      console.log("\n7. Polling for completion...");
      const TERMINAL_SUCCESS = [3, 4];
      const TERMINAL_FAILURE = [5, 6, 7];
      const TERMINAL_STATES = [...TERMINAL_SUCCESS, ...TERMINAL_FAILURE];

      let status;
      do {
        await new Promise((resolve) => setTimeout(resolve, 5000));
        console.log("Checking status...");
        try {
          status = await checkStatus(requestHash);
          console.log("- Status code:", status.bungeeStatusCode);
        } catch (error) {
          console.error(
            "Failed to check status:",
            error?.message || "Unknown error"
          );
        }
      } while (!TERMINAL_STATES.includes(status?.bungeeStatusCode));

      // Handle terminal states
      if (TERMINAL_SUCCESS.includes(status?.bungeeStatusCode)) {
        console.log(
          "\n8. Transaction complete:",
          "\n- Hash:",
          status.destinationData?.txHash || "Transaction hash not available"
        );
      } else {
        const statusNames = { 5: "EXPIRED", 6: "CANCELLED", 7: "REFUNDED" };
        console.error(
          "\n8. Transaction failed:",
          `\n- Status: ${statusNames[status?.bungeeStatusCode]} (${status?.bungeeStatusCode})`,
          "\n- Details:", JSON.stringify(status, null, 2)
        );
        throw new Error(`Request ${statusNames[status?.bungeeStatusCode]}`);
      }
    } catch (error) {
      console.error("Error in processing:", error?.shortMessage || error.message);
      throw error;
    }
  }

  // Execute the main function
  main();
  ```
</Accordion>
