> ## 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 EVM chains

This guide shows how to withdraw USDC from a HyperCore `spot` or `perp` balance
to an external EVM blockchain (such as Arbitrum, Ethereum, or Base) using the
HyperCore API. Withdrawals from HyperCore to EVM chains default to the Fast
Transfer method, due to the fast finality of HyperEVM.

The withdrawal process:

1. Debits your HyperCore balance (`spot` or `perp`)
2. Routes through HyperEVM where USDC is burned via CCTP
3. CCTP attests to the burn and mints on the destination chain
4. If automatic forwarding is enabled, the recipient receives funds directly

<Note>
  Withdrawals include a HyperCore fee and (if using the Forwarding Service) a CCTP
  forwarding fee. Ensure your withdrawal amount exceeds combined fees depending on
  your transfer.
</Note>

## Important considerations

Keep these things in mind when withdrawing USDC from HyperCore to EVM chains:

* **Data field:** If the data field is empty, the `CoreDepositWallet`
  automatically sets a default hook that enables automatic message forwarding on
  the destination blockchain, provided that the blockchain supports CCTP
  forwarding. If the data field is not empty, its contents are passed to the
  CCTP protocol as the value of the `hookData` field.

* **Destination caller:** The CCTP `destinationCaller` is always set to the zero
  address. Passing your own hook data means that anyone can receive the message
  on the destination blockchain.

* **Withdrawal fees:** In addition to the `maxFee` charged by the HyperCore
  blockchain, an additional fixed forwarding fee may be charged by CCTP if
  automatic forwarding is enabled. The forwarding fee amount depends on the
  destination blockchain and can be viewed by querying the `CoreDepositWallet`
  smart contract. Initially, the fee for forwarding to Arbitrum is 0.2 USDC. If
  the withdrawal includes custom hook data, the forwarding fee is not set and
  users have to receive the message on the destination blockchain themselves.

* **Minimum withdrawal amount:** If the withdrawal amount is less than the
  required forwarding fee, the transaction on HyperEVM reverts. Make sure the
  withdrawal amount is larger than the fees.

## 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...
  DESTINATION_RECIPIENT=0x...  # Recipient address on destination chain
  ```

## Steps

Use the following steps to withdraw USDC from HyperCore to an EVM blockchain.

### Step 1. Construct the `sendToEvmWithData` action

Create a `sendToEvmWithData` action object with the following parameters:

* `type`: `sendToEvmWithData`
* `hyperliquidChain`: `Mainnet` (or `Testnet` for testnet)
* `signatureChainId`: The ID of the chain used when signing in hexadecimal
  format (for example, `"0xa4b1"` for Arbitrum).
* `token`: `USDC`
* `amount`: The amount of USDC as a string (for example, `"10"` for 10 USDC,
  `"1.5"` for 1.5 USDC)
* `sourceDex`: `"spot"` to withdraw from spot balance, or `""` for `perp`
  balance
* `destinationRecipient`: The recipient address on the destination blockchain
* `addressEncoding`: `hex` for EVM chains or `base58` for Solana
* `destinationChainId`: The CCTP destination domain ID (for example, `3` for
  Arbitrum, `0` for Ethereum, `6` for Base)
* `gasLimit`: Gas limit for the transaction on the destination chain
* `data`: CCTP hook data (use `"0x"` for automatic forwarding)
* `nonce`: Current timestamp in milliseconds

```ts TypeScript theme={null}
// Example action payload for sendToEvmWithData
const action = {
  type: "sendToEvmWithData",
  hyperliquidChain: "Mainnet",
  signatureChainId: "0xa4b1", // Chain ID used when signing
  token: "USDC",
  amount: "10", // 10 USDC
  sourceDex: "spot", // or "" for perp
  destinationRecipient: "0x1234567890123456789012345678901234567890",
  addressEncoding: "hex",
  destinationChainId: 3, // Arbitrum CCTP domain
  gasLimit: 200000,
  data: "0x", // "0x" enables automatic forwarding on the destination
  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";

// Sign the action using EIP-712
const wallet = new Wallet(privateKey);
const chainId = parseInt(signatureChainId, 16);

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

const types = {
  "HyperliquidTransaction:SendToEvmWithData": [
    { name: "hyperliquidChain", type: "string" },
    { name: "token", type: "string" },
    { name: "amount", type: "string" },
    { name: "sourceDex", type: "string" },
    { name: "destinationRecipient", type: "string" },
    { name: "addressEncoding", type: "string" },
    { name: "destinationChainId", type: "uint32" },
    { name: "gasLimit", type: "uint64" },
    { name: "data", type: "bytes" },
    { name: "nonce", type: "uint64" },
  ],
};

const message = {
  hyperliquidChain: "Mainnet",
  token: "USDC",
  amount: "10",
  sourceDex: "spot",
  destinationRecipient: "0x...",
  addressEncoding: "hex",
  destinationChainId: 3,
  gasLimit: BigInt(200000),
  data: "0x",
  nonce: BigInt(Date.now()),
};

const sigHex = await wallet.signTypedData(domain, types, message);
const sig = Signature.from(sigHex);
const signature = { 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)
endpoint with the action, nonce, and signature.

```ts TypeScript theme={null}
const response = await fetch("https://api.hyperliquid.xyz/exchange", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    action,
    nonce: timestamp,
    signature,
  }),
});

const result = await response.json();

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

## Full example code

The following is a complete example of how to withdraw USDC from HyperCore to an
external EVM blockchain. By default, it withdraws 10 USDC from your perp balance
to Arbitrum testnet with automatic forwarding enabled. For other destination
chains, update `destinationChainId` (CCTP domain ID) and `signatureChainId` (the
ID of the chain used when signing in hexadecimal format) accordingly.

```ts TypeScript expandable theme={null}
/**
 * Script: Withdraw USDC from HyperCore to EVM chain
 * - Signs EIP-712 sendToEvmWithData action
 * - Submits to Hyperliquid /exchange 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
  sourceDex: process.env.SOURCE_DEX || "", // "" for perp, "spot" for spot

  // Destination parameters
  destinationRecipient: process.env.DESTINATION_RECIPIENT as string,
  destinationChainId: Number(process.env.DESTINATION_CHAIN_ID || 3), // 3 = Arbitrum
  addressEncoding: process.env.ADDRESS_ENCODING || "hex",
  gasLimit: Number(process.env.GAS_LIMIT || 200000),
  data: process.env.DATA || "0x", // "0x" enables automatic forwarding

  // Hyperliquid environment
  isMainnet:
    String(process.env.HL_IS_MAINNET || "false").toLowerCase() === "true",
  signatureChainId: "0xa4b1", // Chain ID used when signing
};

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

  const apiUrl = config.isMainnet
    ? "https://api.hyperliquid.xyz"
    : "https://api.hyperliquid-testnet.xyz";
  const hyperliquidChain = config.isMainnet ? "Mainnet" : "Testnet";
  const chainId = parseInt(config.signatureChainId, 16);
  const timestamp = Date.now();

  console.log("Withdrawing from HyperCore:", hyperliquidChain);
  console.log("Source balance:", config.sourceDex || "perp");
  console.log("Amount (USDC):", config.amount);
  console.log("Destination recipient:", config.destinationRecipient);
  console.log("Destination chain ID:", config.destinationChainId);
  console.log("Gas limit:", config.gasLimit);

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

  // EIP-712 Types
  const types = {
    "HyperliquidTransaction:SendToEvmWithData": [
      { name: "hyperliquidChain", type: "string" },
      { name: "token", type: "string" },
      { name: "amount", type: "string" },
      { name: "sourceDex", type: "string" },
      { name: "destinationRecipient", type: "string" },
      { name: "addressEncoding", type: "string" },
      { name: "destinationChainId", type: "uint32" },
      { name: "gasLimit", type: "uint64" },
      { name: "data", type: "bytes" },
      { name: "nonce", type: "uint64" },
    ],
  };

  // Message to sign
  const message = {
    hyperliquidChain,
    token: "USDC",
    amount: config.amount,
    sourceDex: config.sourceDex,
    destinationRecipient: config.destinationRecipient,
    addressEncoding: config.addressEncoding,
    destinationChainId: config.destinationChainId,
    gasLimit: BigInt(config.gasLimit),
    data: config.data,
    nonce: BigInt(timestamp),
  };

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

  // Build action payload
  const action = {
    type: "sendToEvmWithData",
    hyperliquidChain,
    signatureChainId: config.signatureChainId,
    token: "USDC",
    amount: config.amount,
    sourceDex: config.sourceDex,
    destinationRecipient: config.destinationRecipient,
    addressEncoding: config.addressEncoding,
    destinationChainId: config.destinationChainId,
    gasLimit: config.gasLimit,
    data: config.data,
    nonce: timestamp,
  };

  // 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
```
