Skip to main content
This guide shows how to withdraw USDC from a HyperCore spot or perp balance to HyperEVM using the HyperCore API.
Note: You can only withdraw USDC from HyperCore to the same address on HyperEVM. It’s not possible to specify a different recipient address.

Prerequisites

Before you start, you should have:
  • Installed Node.js and npm on your development machine
  • A wallet with USDC balance on HyperCore (spot or perp)
  • Your wallet’s private key available
  • Created a new Node project and have the following dependencies installed:
    • ethers

Steps

Use the following steps to withdraw USDC from HyperCore to HyperEVM.

Step 1. Construct the sendAsset action

Create a sendAsset action object with the following parameters:
  • type: sendAsset
  • hyperliquidChain: Mainnet (or Testnet for testnet)
  • signatureChainId: The blockchain ID used for signing (for example, "0xa4b1" for Arbitrum)
  • destination: The USDC token system address (0x2000000000000000000000000000000000000000)
  • sourceDex: spot to withdraw from spot balance, or omit for perp balance
  • destinationDex: spot"
  • token: USDC
  • amount: The amount of USDC in HyperCore minor units (8 decimal places)
  • fromSubAccount: Set to null for main account, or the subaccount address
  • nonce: Current timestamp in milliseconds
const action = {
  type: "sendAsset",
  hyperliquidChain: "Mainnet",
  signatureChainId: "0xa4b1",
  destination: "0x2000000000000000000000000000000000000000",
  sourceDex: "spot", // or omit for perp
  destinationDex: "spot",
  token: "USDC",
  amount: "1000000000", // 10 USDC (8 decimals)
  fromSubAccount: null,
  nonce: Date.now(),
};

Step 2. Sign the action using EIP-712

Sign the action using the EIP-712 typed data signing standard. The signature proves that you authorize this withdrawal. The signing domain should include:
  • name: "HyperliquidTransaction:SendAsset"
  • version: "1"
  • chainId: The chain ID from signatureChainId (as a number)
  • verifyingContract: "0x0000000000000000000000000000000000000000"
import { ethers } from "ethers";

async function signSendAssetAction(
  action: any,
  privateKey: string,
): Promise<{ r: string; s: string; v: number }> {
  const wallet = new ethers.Wallet(privateKey);

  // Convert chainId from hex to number
  const chainId = parseInt(action.signatureChainId, 16);

  // EIP-712 domain
  const domain = {
    name: "HyperliquidTransaction:SendAsset",
    version: "1",
    chainId: chainId,
    verifyingContract: "0x0000000000000000000000000000000000000000",
  };

  // EIP-712 types
  const types = {
    SendAsset: [
      { name: "hyperliquidChain", type: "string" },
      { name: "destination", type: "string" },
      { name: "token", type: "string" },
      { name: "amount", type: "string" },
      { name: "time", type: "uint64" },
    ],
  };

  // Message to sign
  const value = {
    hyperliquidChain: action.hyperliquidChain,
    destination: action.destination,
    token: action.token,
    amount: action.amount,
    time: action.nonce,
  };

  // Sign the typed data
  const signature = await wallet.signTypedData(domain, types, value);

  // Split signature into r, s, v components
  const sig = ethers.Signature.from(signature);

  return {
    r: sig.r,
    s: sig.s,
    v: sig.v,
  };
}

Step 3. Submit the signed action to the exchange API

Call the exchange endpoint with the action, nonce, and signature.
async function submitSendAsset(
  action: any,
  signature: { r: string; s: string; v: number },
) {
  const response = await fetch("https://api.hyperliquid.xyz/exchange", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      action: action,
      nonce: action.nonce,
      signature: signature,
    }),
  });

  const data = await response.json();

  if (data.status === "ok") {
    console.log("Withdrawal successful:", data);
    return data;
  } else {
    throw new Error(`Withdrawal failed: ${JSON.stringify(data)}`);
  }
}

Full example code

The following is a complete example of how to withdraw USDC from HyperCore to HyperEVM.
#!/usr/bin/env node

/**
 * Script: Withdraw USDC from HyperCore to HyperEVM
 * - Constructs a SendAsset action
 * - Signs the action using EIP-712
 * - Submits the signed action to the HyperCore API
 *
 * Requires: npm i ethers
 * Ethers v6
 */

const { ethers } = require("ethers");

// -------- Configuration --------
const CONFIG = {
  // Private key for signing
  PRIVATE_KEY: process.env.PRIVATE_KEY || "YOUR_PRIVATE_KEY_HERE",

  // HyperCore API endpoint
  API_ENDPOINT:
    process.env.API_ENDPOINT || "https://api.hyperliquid-testnet.xyz/exchange",

  // Chain configuration
  HYPERLIQUID_CHAIN: process.env.HYPERLIQUID_CHAIN || "Testnet", // or "Mainnet"
  SIGNATURE_CHAIN_ID: process.env.SIGNATURE_CHAIN_ID || "0xa4b1", // Arbitrum chain ID for signing

  // Withdrawal parameters
  AMOUNT_USDC: process.env.AMOUNT_USDC || "10", // Amount in USDC (HyperCore uses 8 decimals)
  SOURCE_DEX: process.env.SOURCE_DEX || "", // "" for perp, "spot" for spot
  DESTINATION_DEX: "spot", // Always "spot" for HyperEVM withdrawals

  // System address for USDC token on HyperCore
  USDC_SYSTEM_ADDRESS: "0x2000000000000000000000000000000000000000",
};

// -------- Helper Functions --------

/**
 * Convert USDC amount to HyperCore minor units (8 decimals)
 */
function toHyperCoreUnits(amountStr) {
  // HyperCore uses 8 decimals
  const amount = parseFloat(amountStr);
  return Math.floor(amount * 100000000).toString();
}

/**
 * Sign SendAsset action using EIP-712
 */
async function signSendAssetAction(action, privateKey) {
  const wallet = new ethers.Wallet(privateKey);

  // Convert chainId from hex to number
  const chainId = parseInt(action.signatureChainId, 16);

  // EIP-712 domain
  const domain = {
    name: "HyperliquidTransaction:SendAsset",
    version: "1",
    chainId: chainId,
    verifyingContract: "0x0000000000000000000000000000000000000000",
  };

  // EIP-712 types
  const types = {
    SendAsset: [
      { name: "hyperliquidChain", type: "string" },
      { name: "destination", type: "string" },
      { name: "token", type: "string" },
      { name: "amount", type: "string" },
      { name: "time", type: "uint64" },
    ],
  };

  // Message to sign
  const value = {
    hyperliquidChain: action.hyperliquidChain,
    destination: action.destination,
    token: action.token,
    amount: action.amount,
    time: action.nonce,
  };

  // Sign the typed data
  const signature = await wallet.signTypedData(domain, types, value);

  // Split signature into r, s, v components
  const sig = ethers.Signature.from(signature);

  return {
    r: sig.r,
    s: sig.s,
    v: sig.v,
  };
}

/**
 * Submit signed action to HyperCore exchange API
 */
async function submitSendAsset(apiEndpoint, action, signature) {
  const response = await fetch(apiEndpoint, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      action: action,
      nonce: action.nonce,
      signature: signature,
    }),
  });

  const data = await response.json();

  if (data.status === "ok") {
    return data;
  } else {
    throw new Error(`Withdrawal failed: ${JSON.stringify(data)}`);
  }
}

// -------- Main --------
async function main() {
  const {
    PRIVATE_KEY,
    API_ENDPOINT,
    HYPERLIQUID_CHAIN,
    SIGNATURE_CHAIN_ID,
    AMOUNT_USDC,
    SOURCE_DEX,
    DESTINATION_DEX,
    USDC_SYSTEM_ADDRESS,
  } = CONFIG;

  if (!PRIVATE_KEY || PRIVATE_KEY === "YOUR_PRIVATE_KEY_HERE") {
    throw new Error("Set PRIVATE_KEY environment variable");
  }

  const wallet = new ethers.Wallet(PRIVATE_KEY);
  const userAddress = wallet.address;

  console.log("Withdrawing USDC from HyperCore to HyperEVM");
  console.log("User Address:", userAddress);
  console.log("Amount:", AMOUNT_USDC, "USDC");
  console.log("Source DEX:", SOURCE_DEX || "perp");
  console.log("API Endpoint:", API_ENDPOINT);

  // Convert amount to HyperCore minor units (8 decimals)
  const amount = toHyperCoreUnits(AMOUNT_USDC);

  // Construct the sendAsset action
  const action = {
    type: "sendAsset",
    hyperliquidChain: HYPERLIQUID_CHAIN,
    signatureChainId: SIGNATURE_CHAIN_ID,
    destination: USDC_SYSTEM_ADDRESS,
    sourceDex: SOURCE_DEX,
    destinationDex: DESTINATION_DEX,
    token: "USDC",
    amount: amount,
    time: Date.now(),
  };

  console.log("\nAction:", JSON.stringify(action, null, 2));

  // Sign the action
  console.log("\nSigning action...");
  const signature = await signSendAssetAction(action, PRIVATE_KEY);
  console.log("Signature:", signature);

  // Submit the action
  console.log("\nSubmitting withdrawal...");
  const result = await submitSendAsset(API_ENDPOINT, action, signature);

  console.log("\n✅ Withdrawal successful!");
  console.log("Result:", JSON.stringify(result, null, 2));
}

// Run
if (require.main === module) {
  main().catch((e) => {
    console.error("\n❌ Error:", e.message);
    process.exit(1);
  });
}