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

> ## Agent Instructions
> Before reading these docs, install the relevant Circle Skill to get pre-loaded context — skills are LLM-optimized and cover architecture decisions, correct flows, and common pitfalls. In Claude Code: run /plugin marketplace add circlefin/skills then /plugin install circle-skills@circle. With Vercel Skills CLI: run npx skills add circlefin/skills. Available skills: use-usdc https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-usdc/SKILL.md (balances, transfers, approvals on EVM and Solana); EURC has no skill — see https://developers.circle.com/stablecoins/what-is-eurc.md; bridge-stablecoin https://github.com/circlefin/skills/blob/master/plugins/circle/skills/bridge-stablecoin/SKILL.md (crosschain transfers via CCTP, USDC, EURC, and third-party assets); use-arc https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-arc/SKILL.md (Arc chain config, contract deployment, USDC bridging — Arc docs: https://docs.arc.io/llms.txt); use-circle-wallets https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-circle-wallets/SKILL.md (choose wallet type); use-developer-controlled-wallets https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-developer-controlled-wallets/SKILL.md (custodial wallets for payouts and treasury); use-user-controlled-wallets https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-user-controlled-wallets/SKILL.md (embedded wallets with Web2 login); use-modular-wallets https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-modular-wallets/SKILL.md (smart contract wallets with passkeys and ERC-4337); use-gateway https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-gateway/SKILL.md (unified USDC balance, nanopayments); use-smart-contract-platform https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-smart-contract-platform/SKILL.md (deploy, interact, monitor contracts). Pair any skill with the Circle MCP server for live SDK signatures, contract addresses, and chain IDs: {"mcpServers":{"circle":{"url":"https://api.circle.com/v1/codegen/mcp"}}}.
> Read the product overview first. Each product has an overview explaining when and why to use it. Read it before quickstarts.
> Pick the right wallet type. Developer-controlled (you hold keys), user-controlled (end user holds keys via PIN/social login), or modular (smart contract wallets with passkeys). See https://developers.circle.com/wallets.md.
> Use CCTP V2 for crosschain transfers. CCTP natively transfers USDC and EURC via burn-and-mint, and supports permissionless wrapping of third-party assets via lock-and-mint. V1 is legacy; always use V2 unless V1 is specified or the chain requires it (Noble, Sui, and Aptos still require V1). Migration guide: https://developers.circle.com/cctp/migration-from-v1-to-v2.md.
> Use Bridge Kit for frontend bridging. Wraps CCTP with a simpler SDK for user-facing apps. Use CCTP directly for backend transfers.
> Gas Station vs Paymaster. Gas Station sponsors gas for Circle Wallet transactions. Paymaster lets users pay gas in USDC. Different use cases — read both overviews.
> Gateway Nanopayments for sub-cent payments. Gasless USDC micropayments down to $0.000001 via x402 and batched settlement. For pay-per-request APIs, AI agent payments, streaming.
> Look up USDC addresses per chain. Never hardcode — use https://developers.circle.com/stablecoins/usdc-contract-addresses.md.
> Prefer SDKs over raw API calls. Node.js and Python SDKs handle auth, retries, and errors.
> API key required. Bearer token in Authorization header. Testnet and mainnet use separate keys and may use different base URLs depending on the product.
> Set up webhooks when available. Most operations are async. Webhooks deliver transaction confirmations and state changes.
> When calling list endpoints, paginate using pageSize and pageAfter until no nextPageAfter cursor is returned—stopping at the first page silently misses records.
> Building an AI agent? Start with the Agent Stack—Circle CLI, agent wallets, and nanopayments built for autonomous use cases: https://developers.circle.com/agent-stack.md.

# Withdraw USDC from HyperCore to HyperEVM

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.
</Note>

## Prerequisites

Before you begin, ensure that you've:

* Installed [Node.js v22.6+](https://nodejs.org/)

* Prepared an EVM wallet with the private key available

* Funded your HyperCore account with USDC in either `spot` or `perp` balance

* Created a new Node project and installed dependencies:

  ```bash theme={null}
  npm install ethers
  npm install -D typescript @types/node
  ```

* Created a `.env` file with required environment variables:

  ```text theme={null}
  PRIVATE_KEY=0x...
  ```

## 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`: An EVM chain ID used for EIP-712 replay protection. Must
  match between signing and the action payload, but can be any valid chain ID
  (for example, `"0xa4b1"` for Arbitrum)
* `destination`: The USDC token system address
  (`0x2000000000000000000000000000000000000000`)
* `sourceDex`: `"spot"` to withdraw from spot balance, or `""` for perp balance
* `destinationDex`: `"spot"`
* `token`: `USDC`
* `amount`: The amount of USDC as a human-readable string (for example, `"10"`
  for 10 USDC)
* `fromSubAccount`: Set to `""` for main account, or the subaccount address
* `nonce`: Current timestamp in milliseconds

```ts TypeScript theme={null}
const action = {
  type: "sendAsset",
  hyperliquidChain: "Testnet",
  signatureChainId: "0xa4b1", // EVM chain ID for EIP-712 replay protection
  destination: "0x2000000000000000000000000000000000000000",
  sourceDex: "", // "" for perp, "spot" for spot
  destinationDex: "spot",
  token: "USDC",
  amount: "10", // 10 USDC (human-readable)
  fromSubAccount: "",
  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`: `"HyperliquidSignTransaction"`
* `version`: `"1"`
* `chainId`: The chain ID from `signatureChainId` (as a number)
* `verifyingContract`: `"0x0000000000000000000000000000000000000000"`

```ts TypeScript theme={null}
import { Wallet, Signature } from "ethers";

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

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

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

  // EIP-712 types (must match Hyperliquid SDK's SEND_ASSET_SIGN_TYPES)
  const types = {
    "HyperliquidTransaction:SendAsset": [
      { name: "hyperliquidChain", type: "string" },
      { name: "destination", type: "string" },
      { name: "sourceDex", type: "string" },
      { name: "destinationDex", type: "string" },
      { name: "token", type: "string" },
      { name: "amount", type: "string" },
      { name: "fromSubAccount", type: "string" },
      { name: "nonce", type: "uint64" },
    ],
  };

  // Message to sign (only fields defined in EIP-712 types, not signatureChainId)
  const value = {
    hyperliquidChain: action.hyperliquidChain,
    destination: action.destination,
    sourceDex: action.sourceDex,
    destinationDex: action.destinationDex,
    token: action.token,
    amount: action.amount,
    fromSubAccount: action.fromSubAccount,
    nonce: BigInt(action.nonce),
  };

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

  // Split signature into r, s, v components
  const sig = 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](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#send-asset)
endpoint with the action, nonce, and signature.

```ts TypeScript theme={null}
async function submitSendAsset(
  action: any,
  signature: { r: string; s: string; v: number },
) {
  // Use https://api.hyperliquid.xyz for mainnet
  const response = await fetch("https://api.hyperliquid-testnet.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. By default, it withdraws 10 USDC from your perp balance to HyperEVM
testnet.

```ts TypeScript expandable theme={null}
/**
 * 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
 */

import { Wallet, Signature } from "ethers";

// -------- Configuration --------
const config = {
  privateKey: process.env.PRIVATE_KEY as string,

  // Transfer parameters
  amount: process.env.AMOUNT || "10", // 10 USDC (human-readable)
  sourceDex: process.env.SOURCE_DEX || "", // "" for perp, "spot" for spot

  // Hyperliquid environment
  isMainnet:
    String(process.env.HL_IS_MAINNET || "false").toLowerCase() === "true",
};

// System address for USDC token on HyperCore
const USDC_SYSTEM_ADDRESS = "0x2000000000000000000000000000000000000000";

// -------- Main Function --------
async function main() {
  if (!config.privateKey) {
    throw new Error("Set PRIVATE_KEY");
  }

  const apiUrl = config.isMainnet
    ? "https://api.hyperliquid.xyz"
    : "https://api.hyperliquid-testnet.xyz";
  const hyperliquidChain = config.isMainnet ? "Mainnet" : "Testnet";
  const signingChainId = "0xa4b1"; // EVM chain ID for EIP-712 signing (any valid chain ID works)
  const chainId = parseInt(signingChainId, 16);
  const timestamp = Date.now();

  const wallet = new Wallet(config.privateKey);
  console.log("Withdrawing from HyperCore to HyperEVM:", hyperliquidChain);
  console.log("User Address:", wallet.address);
  console.log("Source balance:", config.sourceDex || "perp");
  console.log("Amount (USDC):", config.amount);

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

  // Build action for signing
  const actionForSigning = {
    hyperliquidChain,
    signatureChainId: signingChainId,
    destination: USDC_SYSTEM_ADDRESS,
    sourceDex: config.sourceDex,
    destinationDex: "spot",
    token: "USDC",
    amount: config.amount,
    fromSubAccount: "",
    nonce: timestamp,
  };

  // EIP-712 Types (must match Hyperliquid SDK's SEND_ASSET_SIGN_TYPES)
  const types = {
    "HyperliquidTransaction:SendAsset": [
      { name: "hyperliquidChain", type: "string" },
      { name: "destination", type: "string" },
      { name: "sourceDex", type: "string" },
      { name: "destinationDex", type: "string" },
      { name: "token", type: "string" },
      { name: "amount", type: "string" },
      { name: "fromSubAccount", type: "string" },
      { name: "nonce", type: "uint64" },
    ],
  };

  // Message to sign (only fields defined in EIP-712 types, not signatureChainId)
  const message = {
    hyperliquidChain: actionForSigning.hyperliquidChain,
    destination: actionForSigning.destination,
    sourceDex: actionForSigning.sourceDex,
    destinationDex: actionForSigning.destinationDex,
    token: actionForSigning.token,
    amount: actionForSigning.amount,
    fromSubAccount: actionForSigning.fromSubAccount,
    nonce: BigInt(actionForSigning.nonce),
  };

  // Sign the message using EIP-712
  const sigHex = await wallet.signTypedData(domain, types, message);
  const sig = Signature.from(sigHex);

  // Build action payload for API (includes type and signatureChainId)
  const action: any = {
    type: "sendAsset",
    ...actionForSigning,
  };

  // Submit to Hyperliquid exchange API
  const response = await fetch(`${apiUrl}/exchange`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      action,
      nonce: timestamp,
      signature: { r: sig.r, s: sig.s, v: sig.v },
    }),
  });

  const result = await response.json();

  console.log("\nStatus:", response.status);
  console.log("Response:", JSON.stringify(result, null, 2));

  if (response.status === 200 && result.status === "ok") {
    console.log("\nWithdrawal initiated successfully");
  } else {
    throw new Error(`Withdrawal failed: ${JSON.stringify(result)}`);
  }
}

// Run
main().catch((error) => {
  console.error("Error:", error.message);
  process.exit(1);
});
```

Run the script:

```bash theme={null}
node --env-file=.env script.ts
```
