#!/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);
});
}