> ## 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.

# Deposit addresses

> Integrate Bungee Deposit by fetching deposit quote data, submitting txData, and polling request status

This page covers how to integrate the Bungee Deposit flow using the `quote` and `status` endpoints.

The flow is:

1. Fetch a quote with deposit mode enabled (`enableDepositAddress=true`).
2. Read `result.deposit` from the quote response.
3. Use either:

* `deposit.txData` for direct programmatic execution, or
* `deposit.depositData` to display transfer details for user-driven submission.

4. Poll `/status` using `deposit.requestHash` until terminal status.

<Info>
  Currently, the deposit flow is supported on **all EVM** chains, **Tempo**, **Solana**, **Tron** and **Stellar**.

  * **Tron** supports direct deposits from USDT0 OFT chains to USDT on Tron.
    No other tokens or cross-chain swaps are supported at this time.

  * **Stellar** supports Base USDC deposits to and from USDC on Stellar.
    No other tokens or cross-chain swaps are supported at this time.

  * **Tempo** [does not use a native gas token](https://docs.tempo.xyz/protocol/fees). The gas token is determined by the account’s [fee token preferences](https://docs.tempo.xyz/protocol/fees/spec-fee#fee-token-preferences), and [Viem has helper functions](https://viem.sh/tempo/actions/fee.getUserToken) to resolve it. If the swap input token is also selected as the fee token, the swap amount must leave enough balance to cover gas.
</Info>

## Integration Steps

### Step 1: Get a Quote in Deposit Mode

Use the quote endpoint with `enableDepositAddress=true` and `refundAddress=USER_ADDRESS`. The `userAddress` is optional for the deposit flow. The `depositDestinationMemo` may be applicable for Stellar transactions.

```javascript theme={null}
const BUNGEE_API_BASE_URL = "https://public-backend.bungee.exchange";

const quoteParams = {
  originChainId: "8453",
  destinationChainId: "42161",
  inputAmount: "2000000",
  inputToken: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
  outputToken: "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
  userAddress: USER_ADDRESS,  // Optional for the deposit flow
  receiverAddress: RECEIVER_ADDRESS,
  refundAddress: USER_ADDRESS, // Required when `enableDepositAddress` is `true`
  enableDepositAddress: "true",
  disableAuto: "true",
};

async function getDepositQuote(params) {
  const url = `${BUNGEE_API_BASE_URL}/api/v1/bungee/quote?${new URLSearchParams(params)}`;
  const response = await fetch(url);

  if (!response.ok) {
    throw new Error(`Quote HTTP error: ${response.status}`);
  }

  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 || "Unknown error"}. server-req-id: ${serverReqId}`
    );
  }

  const deposit = data?.result?.deposit;
  if (!deposit?.txData || !deposit?.requestHash) {
    throw new Error(`Missing deposit txData/requestHash. server-req-id: ${serverReqId}`);
  }

  return {
    requestHash: deposit.requestHash,
    txData: deposit.txData,
    userOp: deposit.userOp,
  };
}
```

<Note>
  `result.deposit.requestHash` is the identifier you should use with the status endpoint.
</Note>

### Step 2: Submit `deposit.txData`

Use `deposit.txData.type` to select the transaction submission method for the origin chain.
Example below shows EVM submission with `viem`.

In addition to executing with `deposit.txData`, you can also use `deposit.depositData` from the quote response to display the direct transfer details (recipient address, token, amount, chainId, memo if present) so users can submit the transfer manually via a QR code UI.

<Warning>
  For Stellar deposits, when transferring USDC for the first time from a wallet, ensure the wallet has a USDC trustline before submitting the transaction.
</Warning>

```javascript theme={null}
async function submitEvmTransaction(txData, originChainId, privateKey) {
  if (!txData?.to) {
    throw new Error("Transaction 'to' is required");
  }

  const account = privateKeyToAccount(privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`);
  const walletClient = createWalletClient({
    chain,
    account,
    transport: http(),
  });
  const publicClient = createPublicClient({
    chain,
    transport: http(),
  });

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

  const receipt = await publicClient.waitForTransactionReceipt({ hash });
  if (!receipt || receipt.status !== "success") {
    throw new Error(`Transaction failed: ${hash}`);
  }

  return { hash, receipt };
}
```

### Step 3: Poll Status with `requestHash`

After transaction submission, poll the status endpoint using the request hash returned in step 1.

```javascript theme={null}
async function checkStatus(requestHash) {
  const response = await fetch(
    `${BUNGEE_API_BASE_URL}/api/v1/bungee/status?requestHash=${requestHash}`
  );

  if (!response.ok) {
    throw new Error(`Status HTTP error: ${response.status}`);
  }

  const data = await response.json();
  if (!data.success) {
    throw new Error(
      `Status error: ${data.error?.message || data.message || "Unknown error"}`
    );
  }
  if (!Array.isArray(data.result) || data.result.length === 0) {
    throw new Error("No status result found");
  }

  return data.result[0];
}

async function pollDepositStatus(requestHash, intervalMs = 10000, maxAttempts = 60) {
  let attempts = 0;

  while (attempts < maxAttempts) {
    attempts += 1;
    await new Promise((resolve) => setTimeout(resolve, intervalMs));

    const status = await checkStatus(requestHash);
    const code = status?.bungeeStatusCode;

    if (code === 3 || code === 4) return status;
    if (code === 5 || code === 6 || code === 7) {
      throw new Error(`Request failed with status code ${code}`);
    }
  }

  throw new Error("Status polling timed out");
}
```

For status code details, see [Request status codes](/integrate/integration-guides/check-status).

## Examples

### Queries

<Accordion title="Quote from Base USDC to Stellar USDC">
  ```text theme={null}
  https://public-backend.bungee.exchange/api/v1/bungee/quote?originChainId=8453&destinationChainId=1110002&inputAmount=20000000&inputToken=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913&outputToken=USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN&refundAddress=0x664b591aB924C6bb2CacA533ed702386934A11d6&receiverAddress=GAEQYFK3GOLJO7OR24DMBEAGVQ7TRPQXXMSRGLI4BQVZNH6SJT7PTBRC&enableDepositAddress=true&depositDestinationMemo=3063557953
  ```
</Accordion>

<Accordion title="Quote from Stellar USDC to Base USDC">
  ```text theme={null}
  https://public-backend.bungee.exchange/api/v1/bungee/quote?originChainId=1110002&destinationChainId=8453&inputAmount=20000000&inputToken=USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN&receiverAddress=0x7B346f63e6F8f663CBEC6d526bA762B42A1Fdd7A&outputToken=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913&enableDepositAddress=true&refundAddress=0x7B346f63e6F8f663CBEC6d526bA762B42A1Fdd7A&depositDestinationMemo=3063557953&disableAuto=true
  ```
</Accordion>

### Scripts

<Accordion title="Complete Integration Example for the Deposit flow">
  ```javascript theme={null}
  import dotenv from "dotenv";
  import { TronWeb } from "tronweb";
  import { createPublicClient, createWalletClient, http } from "viem";
  import { privateKeyToAccount } from "viem/accounts";
  import { base, arbitrum, optimism, tempo } from "viem/chains";
  import * as StellarSdk from "@stellar/stellar-sdk";
  import StellarHDWallet from "stellar-hd-wallet";
  import {
    Keypair,
    Connection,
    Transaction as SolanaTransaction,
    VersionedTransaction,
  } from "@solana/web3.js";
  import bs58 from "bs58";

  dotenv.config();

  const BUNGEE_API_BASE_URL = "https://public-backend.bungee.exchange";
  const TRON_FULL_HOST = "https://api.trongrid.io";
  const SOLANA_RPC_URL = "https://api.mainnet-beta.solana.com";
  const STELLAR_HORIZON_URL = "https://horizon.stellar.org";

  const SOLANA_CHAIN_ID = 89999;

  // Tron only accepts direct deposit from USDT0 OFT chains to USDT on Tron
  // No other tokens or source chain swaps are supported at the moment
  const TRON_CHAIN_ID = 728126428;
  const TRON_USDT_CONTRACT = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t";

  // Stellar only supports Base USDC from/to USDC on Stellar at the moment
  // No other tokens or source chain swaps are supported at the moment
  const STELLAR_CHAIN_ID = 1110002;
  const STELLAR_USDC_ISSUER = "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN";
  const STELLAR_USDC_ASSET = `USDC-${STELLAR_USDC_ISSUER}`;

  // Tempo tokens
  const TEMPO_PATH_USD = '0x20c0000000000000000000000000000000000000';
  const TEMPO_USDCe = '0x20c000000000000000000000b9537d11c60e8b50';

  let cachedEvmAccount = undefined;
  let cachedTronContext = undefined;
  let cachedSolanaContext = undefined;
  let cachedStellarContextPromise = null;

  // Parameters to edit for each deposit
  const originChainId = SOLANA_CHAIN_ID;
  const destinationChainId = 8453;
  const inputAmount = "80000000";
  const inputToken = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
  const outputToken = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";

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

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

  function getApiHeaders() {
    return {
      "x-api-key": process.env.BUNGEE_API_KEY,
      affiliate: process.env.BUNGEE_AFFILIATE_ID,
    };
  }

  function sanitizePrivateKey(value) {
    if (!value) return null;
    return value.startsWith("0x") ? value : `0x${value}`;
  }

  function parseSolanaKeypair(value) {
    if (!value) return null;

    let privateKeyBytes;
    try {
      privateKeyBytes = bs58.decode(value);
    } catch {
      try {
        privateKeyBytes = Uint8Array.from(JSON.parse(value));
      } catch {
        privateKeyBytes = Uint8Array.from(value.split(",").map(Number));
      }
    }

    return Keypair.fromSecretKey(privateKeyBytes);
  }

  function getEvmAccount() {
    if (cachedEvmAccount !== undefined) {
      return cachedEvmAccount;
    }

    const evmPrivateKey = sanitizePrivateKey(process.env.PRIVATE_KEY?.trim());
    cachedEvmAccount = evmPrivateKey ? privateKeyToAccount(evmPrivateKey) : null;
    return cachedEvmAccount;
  }

  function getTronContext() {
    if (cachedTronContext !== undefined) {
      return cachedTronContext;
    }

    const tronPrivateKey = process.env.TRON_PRIVATE_KEY?.replace(/^0x/, "");
    if (!tronPrivateKey) {
      cachedTronContext = null;
      return cachedTronContext;
    }

    const tronWeb = new TronWeb({
      fullHost: TRON_FULL_HOST,
      privateKey: tronPrivateKey,
    });
    const tronAddress = tronWeb.address.fromPrivateKey(tronPrivateKey);

    cachedTronContext = {
      tronWeb,
      tronAddress,
    };

    return cachedTronContext;
  }

  function getSolanaContext() {
    if (cachedSolanaContext !== undefined) {
      return cachedSolanaContext;
    }

    const solanaPrivateKey = process.env.SOLANA_PRIVATE_KEY;
    if (!solanaPrivateKey) {
      cachedSolanaContext = null;
      return cachedSolanaContext;
    }

    try {
      const solanaKeypair = parseSolanaKeypair(solanaPrivateKey);
      cachedSolanaContext = {
        solanaKeypair,
        solanaAddress: solanaKeypair.publicKey.toBase58(),
      };
    } catch (error) {
      throw new Error(
        `Failed to parse SOLANA_PRIVATE_KEY. Use base58 or array/comma format. ${error.message}`
      );
    }

    return cachedSolanaContext;
  }

  async function addStellarUsdcTrustline(account, signer, server, networkPassphrase) {
    console.log("\nNo USDC trustline found, adding trustline...");

    const transaction = new StellarSdk.TransactionBuilder(account, {
      fee: StellarSdk.BASE_FEE,
      networkPassphrase,
    })
      .addOperation(
        StellarSdk.Operation.changeTrust({
          asset: new StellarSdk.Asset("USDC", STELLAR_USDC_ISSUER),
        })
      )
      .setTimeout(30)
      .build();

    transaction.sign(signer);
    const result = await server.submitTransaction(transaction);
    console.log("Trustline added! Hash:", result.hash);
  }

  async function loadStellarWalletContext() {
    if (!process.env.STELLAR_SEED_PHRASE) {
      throw new Error(
        "STELLAR_SEED_PHRASE is required when Stellar chain is used"
      );
    }

    let wallet;
    try {
      wallet = StellarHDWallet.fromMnemonic(process.env.STELLAR_SEED_PHRASE);
    } catch (error) {
      throw new Error(`Failed to parse STELLAR_SEED_PHRASE: ${error.message}`);
    }

    const secretKey = wallet.getSecret(0);
    const walletAddress = wallet.getPublicKey(0);
    const signer = StellarSdk.Keypair.fromSecret(secretKey);
    const server = new StellarSdk.Horizon.Server(STELLAR_HORIZON_URL);
    const networkPassphrase = StellarSdk.Networks.PUBLIC;

    let account;
    try {
      account = await server.loadAccount(walletAddress);
    } catch (error) {
      throw new Error(`Failed to load Stellar account ${walletAddress}: ${error.message}`);
    }

    const xlm = account.balances.find((b) => b.asset_type === "native");
    const xlmBalance = Number(xlm?.balance ?? "0");

    console.log("Stellar Address:    ", walletAddress);
    console.log("Stellar XLM Balance:", xlm ? xlm.balance : "0");

    if (!Number.isFinite(xlmBalance) || xlmBalance <= 0) {
      throw new Error("No XLM balance in Stellar wallet");
    }

    let hasUsdcTrustline = account.balances.find(
      (b) => b.asset_code === "USDC" && b.asset_issuer === STELLAR_USDC_ISSUER
    );

    console.log("Stellar USDC Trustline:", hasUsdcTrustline ? "YES" : "NO");

    if (!hasUsdcTrustline) {
      await addStellarUsdcTrustline(account, signer, server, networkPassphrase);
      const updatedAccount = await server.loadAccount(walletAddress);

      hasUsdcTrustline = updatedAccount.balances.find(
        (b) => b.asset_code === "USDC" && b.asset_issuer === STELLAR_USDC_ISSUER
      );

      if (!hasUsdcTrustline) {
        throw new Error("Failed to create USDC trustline on Stellar wallet");
      }
    }

    return {
      walletAddress,
      signer,
      server,
      networkPassphrase,
    };
  }

  async function getStellarWalletContext() {
    if (!cachedStellarContextPromise) {
      cachedStellarContextPromise = loadStellarWalletContext();
    }

    return cachedStellarContextPromise;
  }

  // REMOVE THIS OR EXPAND IF YOU ADD MORE CHAIN SUPPORT
  // MERELY SETTING THIS FOR EXAMPLE PURPOSES
  function getEvmChain(chainId) {
    const numericChainId = Number(chainId);

    if (numericChainId === base.id) return base;
    if (numericChainId === arbitrum.id) return arbitrum;
    if (numericChainId === optimism.id) return optimism;
    if (numericChainId === tempo.id) return tempo;

    throw new Error(
      `Unsupported EVM chain ${numericChainId}. Add it to getEvmChain before submitting EVM txs.`
    );
  }

  function applyStellarAssetConstraint({
    originChainId,
    destinationChainId,
    inputToken,
    outputToken,
  }) {
    let resolvedInputToken = inputToken;
    let resolvedOutputToken = outputToken;

    if (Number(originChainId) === STELLAR_CHAIN_ID && inputToken !== STELLAR_USDC_ASSET) {
      console.log(
        `Overriding inputToken to ${STELLAR_USDC_ASSET} because originChainId is ${STELLAR_CHAIN_ID}`
      );
      resolvedInputToken = STELLAR_USDC_ASSET;
    }

    if (
      Number(destinationChainId) === STELLAR_CHAIN_ID &&
      outputToken !== STELLAR_USDC_ASSET
    ) {
      console.log(
        `Overriding outputToken to ${STELLAR_USDC_ASSET} because destinationChainId is ${STELLAR_CHAIN_ID}`
      );
      resolvedOutputToken = STELLAR_USDC_ASSET;
    }

    return {
      inputToken: resolvedInputToken,
      outputToken: resolvedOutputToken,
    };
  }

  async function getWalletAddressForChain(chainId) {
    if (Number(chainId) === SOLANA_CHAIN_ID) {
      const solanaContext = getSolanaContext();
      if (!solanaContext?.solanaAddress) {
        throw new Error("SOLANA_PRIVATE_KEY is required when Solana chain is used");
      }
      return solanaContext.solanaAddress;
    }

    if (Number(chainId) === TRON_CHAIN_ID) {
      const tronContext = getTronContext();
      if (!tronContext?.tronAddress) {
        throw new Error("TRON_PRIVATE_KEY is required when Tron chain is used");
      }
      return tronContext.tronAddress;
    }

    if (Number(chainId) === STELLAR_CHAIN_ID) {
      const stellarContext = await getStellarWalletContext();
      return stellarContext.walletAddress;
    }

    const evmAccount = getEvmAccount();
    if (!evmAccount) {
      throw new Error("PRIVATE_KEY is required when an EVM chain is used");
    }
    return evmAccount.address;
  }

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

    const response = await fetch(fullUrl, {
      headers: getApiHeaders(),
    });
    console.log(fullUrl);

    if (!response.ok) {
      throw new Error(`Quote HTTP error: ${response.status}`);
    }

    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 || "Unknown error"}. server-req-id: ${serverReqId}`
      );
    }

    const deposit = data?.result?.deposit;
    if (!deposit) {
      throw new Error(`No deposit available in quote response. server-req-id: ${serverReqId}`);
    }

    if (!deposit.txData) {
      throw new Error(`deposit.txData is missing. server-req-id: ${serverReqId}`);
    }

    if (!deposit.requestHash) {
      throw new Error(`deposit.requestHash is missing. server-req-id: ${serverReqId}`);
    }

    console.log("- Request Hash:", deposit.requestHash);
    console.log("- User Op:", deposit.userOp || "N/A");
    console.log("- Transaction Type:", deposit.txData.type || "unknown");

    return {
      requestHash: deposit.requestHash,
      txData: deposit.txData,
      fullResponse: data,
    };
  }

  async function submitEvmTransaction(txData) {
    const evmAccount = getEvmAccount();
    if (!evmAccount) {
      throw new Error("PRIVATE_KEY is required for evm txData.type");
    }
    if (!txData.to) {
      throw new Error("EVM transaction 'to' is required");
    }
    const chain = getEvmChain(originChainId);

    const walletClient = createWalletClient({
      chain,
      account: evmAccount,
      transport: http(),
    });
    const publicClient = createPublicClient({
      chain,
      transport: http(),
    });

    console.log("  To:", txData.to);
    console.log("  Value:", txData.value);
    console.log("  Data:", txData.data?.slice(0, 66) ? `${txData.data.slice(0, 66)}...` : "N/A");

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

    console.log("- Transaction sent:", hash);

    const receipt = await publicClient.waitForTransactionReceipt({ hash });
    if (!receipt || receipt.status !== "success") {
      throw new Error(`EVM transaction failed: ${hash}`);
    }

    console.log("- Transaction mined in block:", receipt.blockNumber);
    return { hash, receipt };
  }

  async function submitTronTransaction(txData) {
    const tronContext = getTronContext();
    if (!tronContext?.tronWeb || !tronContext?.tronAddress) {
      throw new Error("TRON_PRIVATE_KEY is required for tron txData.type");
    }
    const { tronWeb, tronAddress } = tronContext;
    if (!txData.to) {
      throw new Error("Tron transaction 'to' is required");
    }
    if (!txData.data) {
      throw new Error("Tron transaction 'data' is required");
    }

    console.log("  To:", txData.to);
    console.log("  Value:", txData.value);
    console.log("  Data:", txData.data);

    const tronTo =
      typeof txData.to === "string" && txData.to.startsWith("T")
        ? txData.to
        : tronWeb.address.fromHex(txData.to);

    const transaction = await tronWeb.transactionBuilder.triggerSmartContract(
      tronTo,
      "",
      {
        input: txData.data.replace(/^0x/, ""),
        callValue: Number(txData.value),
        feeLimit: Number(100000000),
      },
      [],
      tronAddress
    );

    const signed = await tronWeb.trx.sign(transaction.transaction);
    const result = await tronWeb.trx.sendRawTransaction(signed);

    if (!result.result) {
      throw new Error(`Tron transaction failed: ${JSON.stringify(result)}`);
    }

    const hash = result.txid;
    console.log("- Transaction sent:", hash);

    let receipt;
    while (!receipt) {
      await new Promise((resolve) => setTimeout(resolve, 3000));
      try {
        const txInfo = await tronWeb.trx.getTransactionInfo(hash);
        if (txInfo?.blockNumber) {
          receipt = {
            blockNumber: txInfo.blockNumber,
            status: txInfo.receipt ? "success" : "failed",
            transactionHash: hash,
          };
        }
      } catch {
        // Keep polling until the receipt is available.
      }
    }

    console.log("- Transaction mined in block:", receipt.blockNumber);
    return { hash, receipt };
  }

  async function submitSolanaTransaction(txData) {
    const solanaContext = getSolanaContext();
    const solanaKeypair = solanaContext?.solanaKeypair;
    if (!solanaKeypair) {
      throw new Error("SOLANA_PRIVATE_KEY is required for solana txData.type");
    }

    const connection = new Connection(SOLANA_RPC_URL);

    const serializedPayload =
      typeof txData?.data === "string"
        ? txData.data
        : typeof txData?.data?.serializedTx === "string"
          ? txData.data.serializedTx
          : typeof txData?.data?.transaction === "string"
            ? txData.data.transaction
            : typeof txData?.data?.tx === "string"
              ? txData.data.tx
              : null;
    if (!serializedPayload) {
      throw new Error("Solana txData.data must be a serialized transaction payload");
    }

    const normalizedPayload = serializedPayload
      .trim()
      .replace(/\s+/g, "")
      .replace(/-/g, "+")
      .replace(/_/g, "/");
    const base64Padded =
      normalizedPayload +
      "=".repeat((4 - (normalizedPayload.length % 4 || 4)) % 4);
    const serializedTxBytes = Buffer.from(base64Padded, "base64");

    if (!serializedTxBytes.length) {
      throw new Error("Failed to decode serialized Solana txData.data payload");
    }

    console.log("  Serialized Solana payload detected");
    console.log(
      "  Data:",
      serializedPayload.length > 66 ? `${serializedPayload.slice(0, 66)}...` : serializedPayload
    );

    let wireTxBuffer;
    try {
      const versionedTx = VersionedTransaction.deserialize(serializedTxBytes);
      versionedTx.sign([solanaKeypair]);
      wireTxBuffer = versionedTx.serialize();
    } catch (versionedError) {
      try {
        const legacyTx = SolanaTransaction.from(serializedTxBytes);
        legacyTx.sign(solanaKeypair);
        wireTxBuffer = legacyTx.serialize();
      } catch (legacyError) {
        throw new Error(
          `Failed to decode serialized Solana tx payload as versioned or legacy transaction. ` +
          `versionedError=${versionedError.message}; legacyError=${legacyError.message}`
        );
      }
    }

    const signature = await connection.sendRawTransaction(wireTxBuffer, {
      skipPreflight: false,
      preflightCommitment: "confirmed",
    });

    console.log("- Transaction sent:", signature);

    const confirmation = await connection.confirmTransaction(signature, "confirmed");
    if (confirmation.value.err) {
      throw new Error(`Solana transaction failed: ${JSON.stringify(confirmation.value.err)}`);
    }

    console.log("- Transaction confirmed");
    return { hash: signature, receipt: confirmation };
  }

  function getStellarNetworkPassphrase(txData) {
    return (
      txData?.networkPassphrase ||
      txData?.data?.networkPassphrase ||
      txData?.meta?.networkPassphrase ||
      StellarSdk.Networks.PUBLIC
    );
  }

  function parseStellarMemo(memoValue) {
    if (memoValue === undefined || memoValue === null || memoValue === "") {
      return null;
    }

    const memoString = String(memoValue).trim();
    if (!memoString) {
      return null;
    }

    if (/^\d+$/.test(memoString)) {
      return StellarSdk.Memo.id(memoString);
    }

    return StellarSdk.Memo.text(memoString);
  }

  function parseStellarAsset(assetPayload) {
    if (!assetPayload) {
      throw new Error("Missing Stellar operation.asset payload");
    }

    const assetCode = assetPayload.code || assetPayload.assetCode;
    const assetIssuer = assetPayload.issuer || assetPayload.assetIssuer;

    if (!assetCode) {
      throw new Error("Missing Stellar asset code");
    }

    if (assetCode.toUpperCase() === "XLM") {
      return StellarSdk.Asset.native();
    }

    if (!assetIssuer) {
      throw new Error(`Missing issuer for Stellar asset ${assetCode}`);
    }

    return new StellarSdk.Asset(assetCode, assetIssuer);
  }

  async function buildStellarTransactionFromOperation({
    txData,
    signer,
    server,
    networkPassphrase,
  }) {
    const operation = txData?.operation;
    if (!operation) {
      throw new Error("Missing txData.operation for stellar transaction");
    }

    if (!operation.destination) {
      throw new Error("Missing txData.operation.destination for stellar transaction");
    }

    if (!operation.amount) {
      throw new Error("Missing txData.operation.amount for stellar transaction");
    }

    const sourceAccount = await server.loadAccount(signer.publicKey());
    const asset = parseStellarAsset(operation.asset);
    const memo = parseStellarMemo(txData?.memo);

    const builder = new StellarSdk.TransactionBuilder(sourceAccount, {
      fee: StellarSdk.BASE_FEE,
      networkPassphrase,
    }).addOperation(
      StellarSdk.Operation.payment({
        destination: operation.destination,
        asset,
        amount: String(operation.amount),
      })
    );

    if (memo) {
      builder.addMemo(memo);
    }

    return builder.setTimeout(30).build();
  }

  function extractStellarXdr(txData) {
    const candidates = [
      txData?.xdr,
      txData?.transactionXdr,
      txData?.envelopeXdr,
      txData?.unsignedXdr,
      txData?.data,
      txData?.data?.xdr,
      txData?.data?.transactionXdr,
      txData?.data?.envelopeXdr,
      txData?.data?.unsignedXdr,
      txData?.data?.transaction?.xdr,
      txData?.rawTransaction,
    ];

    for (const candidate of candidates) {
      if (typeof candidate === "string" && candidate.trim().length > 0) {
        return candidate.trim();
      }
    }

    const queue = [txData];
    while (queue.length > 0) {
      const current = queue.shift();
      if (!current || typeof current !== "object") {
        continue;
      }

      for (const [key, value] of Object.entries(current)) {
        if (
          typeof value === "string" &&
          value.trim().length > 0 &&
          /xdr|envelope/i.test(key)
        ) {
          return value.trim();
        }
        if (value && typeof value === "object") {
          queue.push(value);
        }
      }
    }

    throw new Error(
      "No Stellar XDR found in txData. Expected xdr/transactionXdr/envelopeXdr field."
    );
  }

  async function submitStellarTransaction(txData) {
    const { signer, server } = await getStellarWalletContext();
    const networkPassphrase = getStellarNetworkPassphrase(txData);

    let transaction;
    try {
      const xdr = extractStellarXdr(txData);
      transaction = StellarSdk.TransactionBuilder.fromXDR(xdr, networkPassphrase);
    } catch {
      transaction = await buildStellarTransactionFromOperation({
        txData,
        signer,
        server,
        networkPassphrase,
      });
    }

    transaction.sign(signer);

    const result = await server.submitTransaction(transaction);
    console.log("- Transaction sent:", result.hash);

    return {
      hash: result.hash,
      receipt: result,
    };
  }

  async function checkStatus(requestHash) {
    const response = await fetch(
      `${BUNGEE_API_BASE_URL}/api/v1/bungee/status?requestHash=${requestHash}`,
      {
        headers: getApiHeaders(),
      }
    );

    if (!response.ok) {
      throw new Error(`Status HTTP error: ${response.status}`);
    }

    const data = await response.json();

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

    if (!Array.isArray(data.result) || data.result.length === 0) {
      throw new Error("No status result found");
    }

    return data.result[0];
  }

  async function main() {
    try {
      const constrainedAssets = applyStellarAssetConstraint({
        originChainId,
        destinationChainId,
        inputToken,
        outputToken,
      });
      const originWalletAddress = await getWalletAddressForChain(originChainId);
      const destinationWalletAddress = await getWalletAddressForChain(destinationChainId);
      const quoteParams = {
        originChainId: String(originChainId),
        destinationChainId: String(destinationChainId),
        inputAmount,
        inputToken: constrainedAssets.inputToken,
        outputToken: constrainedAssets.outputToken,
        userAddress: originWalletAddress,
        receiverAddress: destinationWalletAddress,
        refundAddress: originWalletAddress,
        enableDepositAddress: "true",
        disableAuto: "true",
      };

      console.log("Starting Bungee Deposit flow...");
      console.log(`Origin Chain: ${originChainId}`);
      console.log(`Destination Chain: ${destinationChainId}`);
      console.log(`User Address: ${quoteParams.userAddress}`);
      console.log(`Receiver Address: ${quoteParams.receiverAddress}`);

      console.log("\n1. Getting quote...");
      const quoteResponse = await getQuote(quoteParams);

      const txData = quoteResponse.txData;
      if (!txData || !txData.type) {
        throw new Error("No txData.type available in deposit response");
      }

      let submission;

      if (txData.type === "evm") {
        console.log("\n2. Submitting EVM transaction...");
        submission = await submitEvmTransaction(txData);
      } else if (txData.type === "tron") {
        console.log("\n2. Submitting Tron transaction...");
        submission = await submitTronTransaction(txData);
      } else if (txData.type === "solana") {
        console.log("\n2. Submitting Solana transaction...");
        submission = await submitSolanaTransaction(txData);
      } else if (txData.type === "stellar") {
        console.log("\n2. Submitting Stellar transaction...");
        submission = await submitStellarTransaction(txData);
      } else {
        throw new Error(`Unknown transaction type: ${txData.type}`);
      }

      console.log("\n3. Transaction submitted:");
      console.log("- Hash:", submission.hash);

      const statusRequestHash = quoteResponse.requestHash;
      const waitTime = 10000; // 10 seconds
      console.log(`\n4. Waiting ${waitTime / 2}ms before status polling...`);
      await new Promise((resolve) => setTimeout(resolve, waitTime / 2));

      let status;
      let attempts = 0;
      const maxAttempts = 100;

      do {
        attempts += 1;
        if (attempts > maxAttempts) {
          throw new Error(`Status check timeout after ${maxAttempts} attempts`);
        }

        await new Promise((resolve) => setTimeout(resolve, waitTime));
        console.log(`\n5. Checking status (attempt ${attempts})...`);

        try {
          status = await checkStatus(statusRequestHash);
          console.log("- Status code:", status.bungeeStatusCode);
        } catch (error) {
          console.error("- Status check failed:", error?.message || error);
        }
      } while (!status || status.bungeeStatusCode !== 3);

      console.log("\n6. Transaction complete:");
      console.log("- Status Code:", status.bungeeStatusCode);
      console.log(
        "- Destination Hash:",
        status.destinationData?.txHash || "Transaction hash not available"
      );
    } catch (error) {
      console.error("\nError in processing:", error?.shortMessage || error?.message || error);
      process.exit(1);
    }
  }

  main();
  ```
</Accordion>

## Edge Cases and Best Practices

* Always persist `requestHash` immediately after quote generation.
* Provide `refundAddress` explicitly so refunds can be directed deterministically.
* Validate chain, token, destination receiver, and memo before showing the deposit instructions.
* Treat the source transfer tx hash and `requestHash` as separate objects; use `requestHash` for Bungee status checks.
* For Stellar as destination, ensure the receiver wallet has a USDC trustline before first USDC transfer.
* Surface terminal failure states in UX (`EXPIRED`, `CANCELLED`, `REFUNDED`) and show retry guidance.

## Debugging Checklist

1. Store `requestHash` and `server-req-id` from quote/status calls.
2. Confirm the submitted transfer matches `depositData` (address, token, amount, and memo if present).
3. Poll `/status` with `requestHash` until terminal state.
4. If unresolved, share `requestHash` and `server-req-id` with support.

## Failure Cases API Behavior

Status codes: `5 = EXPIRED`, `6 = CANCELLED`, `7 = REFUNDED`

| Failure Mode                           | Category   | Bungee Status Code | API Behavior                                                  |
| -------------------------------------- | ---------- | ------------------ | ------------------------------------------------------------- |
| Wrong token deposited                  | User error | 5 (EXPIRED)        | Quote expires, deposit not recognized                         |
| Deposited on wrong chain               | User error | 5 (EXPIRED)        | Quote expires, funds on wrong chain are not detected          |
| Less amount deposited                  | User error | 5 (EXPIRED)        | Deposit is detected, but no execution happens                 |
| More amount deposited                  | User error | SUCCESS            | Re-quote with actual amount and suggested slippage is applied |
| Balance monitoring expired, no deposit | Timing     | 5 (EXPIRED)        | Address released                                              |
| Slippage exceeds tolerance             | Market     | STAYS PENDING      | Refund is needed                                              |
