Skip to main content
Bungee also supports swapping assets from any EVM chain to HyperCore and from HyperCore to any EVM chain. This guide walks you through swapping assets between EVM chains and HyperCore using Bungee. It covers transaction options, and current limitations.

Quick Start

Key differences

Follow the “Auto routing” guides when going from any EVM to HyperCore. The main difference when integrating HyperCore Bungee API compared to standard EVM chains: Transaction Flow from HyperCore to EVM
  • User sends funds to a Deposit contract with the quote ID
  • Bungee indexes the transaction and delivers the funds on the destination chain
API Behavior
  • Both userAddress and receiverAddress required for quotes
  • The only assets supported from/to HyperCore are USDC (Spot) and USDC (Perps).
  • There is no Fee collection currently from HyperCore to EVM
  • The quote responses already take into account that HyperCore charges a 1 USDC activation fee for new account activation and withdrawals
  • Only USDC (Perps) is supported when bridging out of Hypercore

Integration Steps

HyperCore to EVM

The “Auto routing” guide from Bungee Docs outlines how to integrate the deposit flow as it is a quite similar process.
  1. Select Chains: Users choose the source and destination chains, which determine the tokens available for bridging.
  2. Fetch Routes: After selecting the tokens, retrieve the deposit route if available using the /quote endpoint. The response will contain depositRoute.signTypedData and depositRoute.quoteId.
  3. Sign and Submit: Sign the signTypedData from the quote response using primaryType: "HyperCoreTransaction:SendAsset", then submit the signed data to HyperLiquid’s API endpoint (https://api.hyperliquid.xyz/exchange).
  4. Track Transaction Status: Monitor the transaction’s progress by polling the /status endpoint using the quoteId from the quote response until the bridging process is complete.
For HyperCore quotes, please ensure both userAddress and receiverAddress are defined.inputAmount Format for HyperCore:The inputAmount parameter must be provided as an integer string in 6-decimal base units (micro-units). This is the same format used for USDC on most EVM chains.
Human-readableinputAmount value
9.99 USDC"9990000"
2.00 USDC"2000000"
10.23 USDC"10230000"
Conversion formula: inputAmount = humanAmount × 10^6 (rounded to integer)For example, to send 9.99 USDC, use inputAmount: "9990000" (9.99 × 1,000,000 = 9,990,000).The HyperCore signature generation uses this micro-unit value directly. Do not pass decimal strings like "9.99".Also, note that from HyperCore to EVM is a separate route from autoRoute since it is under depositRoute.

Examples

Queries

https://public-backend.bungee.exchange/api/v1/bungee/quote?userAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045&receiverAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045&originChainId=10&destinationChainId=1337&inputToken=0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85&outputToken=0x2000000000000000000000000000000000000000&inputAmount=2000000
https://public-backend.bungee.exchange/api/v1/bungee/quote?userAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045&receiverAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045&originChainId=1337&destinationChainId=10&inputToken=0x6d1e7cde53ba9467b783cb7c530ce054&outputToken=0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85&inputAmount=10230000
For HyperCore quotes, inputAmount must be provided as an integer string in 6-decimal base units (micro-units). For example, to send 10.23 USDC, use inputAmount: "10230000" (10.23 × 10^6).

Scripts

import { privateKeyToAccount } from "viem/accounts";
import { createPublicClient, http, createWalletClient } from "viem";
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 account = privateKeyToAccount(
  process.env.PRIVATE_KEY.startsWith('0x') 
    ? process.env.PRIVATE_KEY 
    : `0x${process.env.PRIVATE_KEY}`
);

// Create Viem clients
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
const quoteParamsERC20 = {
  userAddress: account.address,
  receiverAddress: account.address,
  originChainId: 10, // Optimism
  destinationChainId: 1337, // HyperCore
  inputToken: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85", // USDC on Optimism
  outputToken: "0x2000000000000000000000000000000000000000", // USDC (SPOT) on HyperCore
  //outputToken: "0x6d1e7cde53ba9467b783cb7c530ce054", // USDC (Perps) on HyperCore
  inputAmount: "2000000", // 2 USDC (6 decimals)
};

// Function to get a quote
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);
    console.log(fullUrl);
    const data = await response.json();
    const serverReqId = response.headers.get("server-req-id");

    // Check for errors
    if (!data.success) {
      throw new Error(
        `Quote error: ${data.statusCode}: ${data.message}. server-req-id: ${serverReqId}`
      );
    }

    // Check if autoRoute exists
    if (!data.result.autoRoute) {
      throw new Error(`No autoRoute available. server-req-id: ${serverReqId}`);
    }

    const quoteId = data.result.autoRoute.quoteId;
    const requestType = data.result.autoRoute.requestType;
    console.log("- Quote ID:", quoteId);
    console.log("- Request Type:", requestType);

    // Extract data based on the response structure
    let witness = null;
    let signTypedData = null;

    if (data.result.autoRoute.signTypedData) {
      signTypedData = data.result.autoRoute.signTypedData;
      // The witness is located in signTypedData.values.witness
      if (signTypedData?.values?.witness) {
        witness = signTypedData.values.witness;
      }
    }

    // Extract approval data if present
    const approvalData = data.result.autoRoute.approvalData;

    // Log request hash
    if (data.result.autoRoute.requestHash) {
      console.log("- Request Hash:", data.result.autoRoute.requestHash);
    }

    return {
      quoteId,
      requestType,
      witness,
      signTypedData,
      approvalData,
      fullResponse: data,
    };
  } catch (error) {
    console.error("Failed to get quote:", error);
    throw error;
  }
}

// Function to sign typed data using viem
async function viemSignTypedData(signTypedData) {
  try {
    const signature = await account.signTypedData({
      types: signTypedData.types,
      primaryType: "PermitWitnessTransferFrom",
      message: signTypedData.values,
      domain: signTypedData.domain,
    });

    console.log("- Signature:", signature);
    return signature;
  } catch (error) {
    console.error("Failed to sign typed data:", error);
    throw error;
  }
}

// Function to submit the signed request
async function submitSignedRequest(
  requestType,
  request,
  userSignature,
  quoteId
) {
  try {
    // Prepare request body
    const requestBody = {
      requestType,
      request,
      userSignature,
      quoteId,
    };

    const response = await fetch(
      `${BUNGEE_API_BASE_URL}/api/v1/bungee/submit`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(requestBody),
      }
    );

    const data = await response.json();
    const serverReqId = response.headers.get("server-req-id");
    console.log("Server request id", serverReqId);

    // Response data is saved to file but not logged to keep output clean

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

    console.log("- Request Hash:", data.result.requestHash);
    return data.result;
  } catch (error) {
    console.error("Failed to submit signed request:", 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;
  }
}

// Function to check and handle token approvals
async function checkAndApproveToken(approvalData) {
  if (!approvalData || !approvalData.tokenAddress) {
    console.log("No approval data found or required");
    return;
  }

  console.log("\nChecking token approval...");

  // ERC20 ABI for allowance and approve functions
  const erc20Abi = [
    {
      inputs: [
        { name: "owner", type: "address" },
        { name: "spender", type: "address" },
      ],
      name: "allowance",
      outputs: [{ name: "", type: "uint256" }],
      stateMutability: "view",
      type: "function",
    },
    {
      inputs: [
        { name: "spender", type: "address" },
        { name: "amount", type: "uint256" },
      ],
      name: "approve",
      outputs: [{ name: "", type: "bool" }],
      stateMutability: "nonpayable",
      type: "function",
    },
  ];

  try {
    const currentAllowance = await publicClient.readContract({
      address: approvalData.tokenAddress,
      abi: erc20Abi,
      functionName: "allowance",
      args: [
        approvalData.userAddress,
        approvalData.spenderAddress,
      ],
    });

    console.log(`Current allowance: ${currentAllowance}`);
    console.log(`Required approval: ${approvalData.amount}`);

    // Check if approval is needed
    if (BigInt(currentAllowance) >= BigInt(approvalData.amount)) {
      console.log("Sufficient allowance already exists.");
      return;
    }

    console.log("Insufficient allowance. Approving tokens...");

    // Send approval transaction
    const hash = await walletClient.writeContract({
      address: approvalData.tokenAddress,
      abi: erc20Abi,
      functionName: "approve",
      args: [
        approvalData.spenderAddress,
        approvalData.amount,
      ],
    });

    console.log(`Approval transaction sent: ${hash}`);

    // Wait for transaction to be mined
    const receipt = await publicClient.waitForTransactionReceipt({ hash });
    console.log(`Approval confirmed in block ${receipt.blockNumber}`);

    return receipt;
  } catch (error) {
    console.error("Error in approval process:", error);
    throw error;
  }
}

// Main function to handle the flow
async function main() {
  try {
    console.log("Starting Bungee ERC20 Token Test (Auto Route)...");

    // Get the quote and extract data
    console.log("\n1. Getting quote...");
    const quoteResponse = await getQuote(quoteParamsERC20);
    const { quoteId, requestType, witness, signTypedData } = quoteResponse;
    console.log(JSON.stringify(quoteResponse.fullResponse, null, 2));

    // Check if approval is needed and handle it
    //if (quoteResponse.approvalData) {
    //  await checkAndApproveToken(quoteResponse.approvalData);
    //}

    if (signTypedData && witness) {
      // Sign the typed data
      console.log("\n2. Signing typed data...");
      const signature = await viemSignTypedData(signTypedData);

      // Submit the signed request
      console.log("\n3. Submitting signed request...");
      const submitResult = await submitSignedRequest(
        requestType,
        witness,
        signature,
        quoteId
      );

      console.log(
        "\n4. Submission complete:",
        "\n- Hash:",
        submitResult.requestHash,
        "\n- Type:",
        submitResult.requestType
      );

      // Check the status
      // Wait for 5 seconds
      console.log("\n5. Waiting for 5 seconds...");
      let status;
      do {
        await new Promise((resolve) => setTimeout(resolve, 5000));
        console.log("\n6. Checking status...");
        try {
          status = await checkStatus(submitResult.requestHash);
          console.log("- Status details:", status.bungeeStatusCode);
        } catch (error) {
          console.error(
            "Failed to check status:",
            error?.message || "Unknown error"
          );
        }
      } while (status?.bungeeStatusCode !== 3 && status?.bungeeStatusCode !== 4);

      console.log(
        "\n7. Transaction complete:",
        "\n- Hash:",
        status.destinationData?.txHash || "Transaction hash not available"
      );
    } else {
      console.log("No signature data available in the quote response");
    }
  } catch (error) {
    console.error("Error in processing:", error?.shortMessage || error.message);
    throw error;
  }
}

// Execute the main function
main();
import { privateKeyToAccount } from "viem/accounts";
import { http, createWalletClient, parseSignature } from "viem";
import { arbitrum } 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.trim().startsWith('0x') 
  ? process.env.PRIVATE_KEY.trim() 
  : `0x${process.env.PRIVATE_KEY.trim()}`;
const account = privateKeyToAccount(normalizedPrivateKey);

// Create Viem clients
const walletClient = createWalletClient({
  account,
  chain: arbitrum,
  transport: http(),
});

// API and token parameters
const BUNGEE_API_BASE_URL = "https://public-backend.bungee.exchange";
const HYPERLIQUID_API_URL = "https://api.hyperliquid.xyz/exchange";

// Quote parameters
const quoteParamsERC20 = {
  userAddress: account.address,
  receiverAddress: account.address,
  originChainId: 1337, // HyperCore
  destinationChainId: 10, // Optimism
  inputToken: "0x6d1e7cde53ba9467b783cb7c530ce054", // USDC (Perps) on HyperCore
  outputToken: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85", // USDC on Optimism
  inputAmount: "10230000", // USDC (Perps)
};

// Function to get a quote
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);
    console.log(fullUrl);
    const data = await response.json();
    const serverReqId = response.headers.get("server-req-id");

    // Check for errors
    if (!data.success) {
      throw new Error(
        `Quote error: ${data.statusCode}: ${data.message}. server-req-id: ${serverReqId}`
      );
    }

    // Check if autoRoute exists
    if (!data.result.depositRoute) {
      throw new Error(`No depositRoute available. server-req-id: ${serverReqId}`);
    }

    const quoteId = data.result.depositRoute.quoteId;
    console.log("- Quote ID:", quoteId);

    // Extract data based on the response structure
    const signTypedData = data.result.depositRoute.signTypedData;

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

// Function to sign typed data using viem
async function viemSignTypedData(signTypedData) {
  try {
    const signature = await walletClient.signTypedData({
      types: signTypedData.types,
      primaryType: "HyperCoreTransaction:SendAsset",
      message: signTypedData.values,
      domain: signTypedData.domain,
    });

    const signatureParsed = parseSignature(signature);
    const signatureObject = {
      r: signatureParsed.r,
      s: signatureParsed.s,
      v: Number(signatureParsed.v),
    };

    return signatureObject;
  } catch (error) {
    console.error("Failed to sign typed data:", error);
    throw error;
  }
}

// Function to submit the signed request
async function submitSignedRequest(
  signTypedData,
  signature,
) {
  try {
    // Prepare request body
    const requestBody = {
      action: signTypedData.values,
      nonce: signTypedData.values.nonce,
      signature: signature,
    };

    console.log("- HyperCore Submit Request:", JSON.stringify(requestBody, null, 2));

    const response = await fetch(
      HYPERLIQUID_API_URL,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(requestBody),
      }
    );

    const data = await response.json();
    if (data.status === 'err') {
      console.error("- HyperCore API Error Response:", data);
      throw new Error(
        `HyperCore API error: ${data.response || data.message || 'Unknown error'}`
      );
    }

    return data;
  } catch (error) {
    console.error("Failed to submit signed request:", error);
    throw error;
  }
}

// Function to check the status of a request
async function checkStatus(quoteId) {
  try {

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

// Main function to handle the flow
async function main() {
  try {
    console.log("Starting Bungee ERC20 Token Test (Auto Route)...");

    // Get the quote and extract data
    console.log("\n1. Getting quote...");
    const quoteResponse = await getQuote(quoteParamsERC20);
    const { quoteId, signTypedData } = quoteResponse;
    console.log(JSON.stringify(quoteResponse.fullResponse, null, 2));

    if (signTypedData) {
      // Sign the typed data
      console.log("\n2. Signing typed data...");
      const signature = await viemSignTypedData(signTypedData);

      // Submit the signed request
      console.log("\n3. Submitting signed request...");
      const submitResult = await submitSignedRequest(
        signTypedData,
        signature,
      );

      console.log(
        "\n4. Submission complete:",
        "\n- Response:",
        submitResult,
      );

      // Check the status
      // Wait for 5 seconds
      console.log("\n5. Waiting for 5 seconds...");
      let status;
      do {
        await new Promise((resolve) => setTimeout(resolve, 5000));
        console.log("\n6. Checking status...");
        try {
          status = await checkStatus(quoteId);
          console.log("- Status details:", status.bungeeStatusCode);
        } catch (error) {
          console.error(
            "Failed to check status:",
            error?.message || "Unknown error"
          );
        }
      } while (status?.bungeeStatusCode !== 3 && status?.bungeeStatusCode !== 4);

      console.log(
        "\n7. Transaction complete:",
        "\n- Hash:",
        status.destinationData?.txHash || "Transaction hash not available"
      );
    } else {
      console.log("No signature data available in the quote response");
    }
  } catch (error) {
    console.error("Error in processing:", error?.shortMessage || error.message);
    throw error;
  }
}

// Execute the main function
main();