Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion skills/hyperliquid/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "hyperliquid",
"description": "Hyperliquid on-chain perpetuals DEX — check positions, get market prices, place and cancel perpetual orders on Hyperliquid L1 (chain_id 999).",
"version": "0.3.0",
"version": "0.3.1",
"author": {
"name": "GeoGu360",
"github": "GeoGu360"
Expand Down
2 changes: 1 addition & 1 deletion skills/hyperliquid/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion skills/hyperliquid/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "hyperliquid"
version = "0.3.0"
version = "0.3.1"
edition = "2021"

[[bin]]
Expand Down
41 changes: 32 additions & 9 deletions skills/hyperliquid/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: hyperliquid
description: Hyperliquid DEX — trade perps & spot, deposit from Arbitrum, withdraw to Arbitrum, transfer between perp and spot accounts, manage gas on HyperEVM.
version: 0.3.0
version: 0.3.1
author: GeoGu360
tags:
- perps
Expand Down Expand Up @@ -50,7 +50,7 @@ if ! command -v hyperliquid >/dev/null 2>&1; then
mingw*_aarch64|msys*_aarch64|cygwin*_aarch64) TARGET="aarch64-pc-windows-msvc"; EXT=".exe" ;;
esac
mkdir -p ~/.local/bin
curl -fsSL "https://github.com/okx/plugin-store/releases/download/plugins/hyperliquid@0.3.0/hyperliquid-${TARGET}${EXT}" -o ~/.local/bin/hyperliquid${EXT}
curl -fsSL "https://github.com/okx/plugin-store/releases/download/plugins/hyperliquid@0.3.1/hyperliquid-${TARGET}${EXT}" -o ~/.local/bin/hyperliquid${EXT}
chmod +x ~/.local/bin/hyperliquid${EXT}
fi
```
Expand All @@ -72,7 +72,7 @@ if [ ! -f "$REPORT_FLAG" ]; then
# Report to Vercel stats
curl -s -X POST "https://plugin-store-dun.vercel.app/install" \
-H "Content-Type: application/json" \
-d '{"name":"hyperliquid","version":"0.3.0"}' >/dev/null 2>&1 || true
-d '{"name":"hyperliquid","version":"0.3.1"}' >/dev/null 2>&1 || true
# Report to OKX API (with HMAC-signed device token)
curl -s -X POST "https://www.okx.com/priapi/v1/wallet/plugins/download/report" \
-H "Content-Type: application/json" \
Expand Down Expand Up @@ -237,9 +237,9 @@ Returns current mid prices for all Hyperliquid perpetual markets, or a specific
hyperliquid prices

# Get price for a specific coin
hyperliquid prices --market BTC
hyperliquid prices --market ETH
hyperliquid prices --market SOL
hyperliquid prices --coin BTC
hyperliquid prices --coin ETH
hyperliquid prices --coin SOL
```

**Output (single coin):**
Expand Down Expand Up @@ -283,6 +283,12 @@ hyperliquid order --coin BTC --side buy --size 0.01 --confirm
# Limit short 0.05 ETH at $3500
hyperliquid order --coin ETH --side sell --size 0.05 --type limit --price 3500 --confirm

# Market long BTC with 10x cross leverage (sets leverage first, then places order)
hyperliquid order --coin BTC --side buy --size 0.01 --leverage 10 --confirm

# Limit long BTC with 5x isolated margin
hyperliquid order --coin BTC --side buy --size 0.01 --type limit --price 60000 --leverage 5 --isolated --confirm

# Market long BTC with bracket: SL at $95000, TP at $110000 (normalTpsl OCO)
hyperliquid order \
--coin BTC --side buy --size 0.01 \
Expand All @@ -296,6 +302,11 @@ hyperliquid order \
--confirm
```

**Leverage flags:**
- `--leverage <N>` — set account leverage for this coin to N× (1–100) before placing. Without this flag, the order inherits the current account-level setting.
- `--isolated` — use isolated margin mode (default is cross margin when `--leverage` is set).
- When `--leverage` is provided, a `updateLeverage` action is signed and submitted first, then the order is placed. This changes the account-level setting for that coin permanently.

**Output (executed with bracket):**
```json
{
Expand Down Expand Up @@ -579,8 +590,10 @@ Withdraws USDC from your Hyperliquid perp account to your Arbitrum wallet.

**Minimum withdrawal: $2 USDC.** Funds arrive on Arbitrum in ~2–5 minutes.

> **Fee notice:** Hyperliquid charges a **$1 USDC fixed withdrawal fee** on every withdrawal. The fee is deducted from your Hyperliquid balance — the recipient receives the full requested amount. Example: withdrawing $50 deducts $51 from your balance; Arbitrum receives $50.

```bash
# Preview
# Preview (shows fee breakdown)
hyperliquid withdraw --amount 50

# Execute
Expand All @@ -590,10 +603,10 @@ hyperliquid withdraw --amount 50 --confirm
hyperliquid withdraw --amount 50 --destination 0xRecipient --confirm
```

**Output fields:** `action`, `wallet`, `destination`, `amount_usd`, `result`
**Output fields:** `action`, `wallet`, `destination`, `amountToReceive_usd`, `withdrawalFee_usd`, `totalDeducted_usd`, `result`

**Flow:**
1. Check withdrawable balance — error if insufficient
1. Check withdrawable balance ≥ amount + $1 fee — error if insufficient
2. Build `withdraw3` user-signed EIP-712 action (domain: HyperliquidSignTransaction, chainId 0x66eee)
3. Sign via `onchainos wallet sign-message --type eip712` with main wallet key
4. Submit to exchange endpoint
Expand Down Expand Up @@ -828,3 +841,13 @@ All data returned by `hyperliquid positions`, `hyperliquid prices`, and exchange




---

## Changelog

### v0.3.1 (2026-04-12)

- **feat**: `order` — new `--leverage <N>` flag (1–100) sets account-level leverage for the coin before placing the order via `updateLeverage` action; fixes the UX gap where users specifying 10x leverage would silently get the account default (e.g. 20x)
- **feat**: `order` — new `--isolated` flag to use isolated margin mode when `--leverage` is set (default is cross)
- **fix**: `withdraw` — add $1 USDC fee notice in preview and output; balance check now validates amount + $1 fee; minimum withdrawal error changed from warning to bail
2 changes: 1 addition & 1 deletion skills/hyperliquid/plugin.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
schema_version: 1
name: hyperliquid
version: "0.3.0"
version: "0.3.1"
description: "Trade perpetuals on Hyperliquid — check positions, get prices, place market/limit orders with TP/SL brackets, close positions, deposit USDC"
author:
name: GeoGu360
Expand Down
43 changes: 43 additions & 0 deletions skills/hyperliquid/src/commands/order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::config::{info_url, exchange_url, normalize_coin, now_ms, CHAIN_ID, AR
use crate::onchainos::{onchainos_hl_sign, resolve_wallet};
use crate::signing::{
build_bracketed_order_action, build_limit_order_action, build_market_order_action,
build_update_leverage_action,
format_px, market_slippage_px, submit_exchange_request,
};

Expand Down Expand Up @@ -37,6 +38,15 @@ pub struct OrderArgs {
#[arg(long)]
pub tp_px: Option<f64>,

/// Leverage multiplier before placing (e.g. 10 for 10x cross). Sets account leverage for this
/// coin first, then places the order. Omit to keep the current account setting.
#[arg(long)]
pub leverage: Option<u32>,

/// Use isolated margin mode when --leverage is set (default is cross margin)
#[arg(long)]
pub isolated: bool,

/// Reduce only — only reduce an existing position, never increase it
#[arg(long)]
pub reduce_only: bool,
Expand All @@ -63,6 +73,13 @@ pub async fn run(args: OrderArgs) -> anyhow::Result<()> {
.parse()
.map_err(|_| anyhow::anyhow!("Invalid size '{}' — must be a number (e.g. 0.01)", args.size))?;

// Validate leverage range (Hyperliquid accepts 1–100)
if let Some(lev) = args.leverage {
if !(1..=100).contains(&lev) {
anyhow::bail!("--leverage must be between 1 and 100 (got {})", lev);
}
}

// TP/SL bracket validation
if let Some(sl) = args.sl_px {
if is_buy && args.tp_px.map_or(false, |tp| tp <= sl) {
Expand Down Expand Up @@ -147,6 +164,10 @@ pub async fn run(args: OrderArgs) -> anyhow::Result<()> {
}
};

let leverage_preview = args.leverage.map(|l| {
format!("{}x {}", l, if args.isolated { "isolated" } else { "cross" })
});

println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
Expand All @@ -157,6 +178,7 @@ pub async fn run(args: OrderArgs) -> anyhow::Result<()> {
"size": args.size,
"type": args.r#type,
"price": args.price,
"leverage": leverage_preview,
"stopLoss": args.sl_px.map(format_px),
"takeProfit": args.tp_px.map(format_px),
"reduceOnly": args.reduce_only,
Expand All @@ -181,6 +203,27 @@ pub async fn run(args: OrderArgs) -> anyhow::Result<()> {
}

let wallet = resolve_wallet(CHAIN_ID)?;

// Set leverage before placing the order if --leverage was provided
if let Some(lev) = args.leverage {
let is_cross = !args.isolated;
let lev_action = build_update_leverage_action(asset_idx, is_cross, lev);
let lev_nonce = now_ms();
let lev_signed = onchainos_hl_sign(&lev_action, lev_nonce, &wallet, ARBITRUM_CHAIN_ID, true, false)?;
let lev_result = submit_exchange_request(exchange, lev_signed).await
.map_err(|e| anyhow::anyhow!("Leverage update failed: {}", e))?;
if lev_result["status"].as_str() == Some("err") {
anyhow::bail!(
"Leverage update rejected by Hyperliquid: {}",
lev_result["response"].as_str().unwrap_or("unknown error")
);
}
println!(
"Leverage set to {}x ({}) for {}",
lev, if is_cross { "cross" } else { "isolated" }, coin
);
}

let signed = onchainos_hl_sign(&action, nonce, &wallet, ARBITRUM_CHAIN_ID, true, false)?;
let result = submit_exchange_request(exchange, signed).await?;

Expand Down
35 changes: 26 additions & 9 deletions skills/hyperliquid/src/commands/withdraw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,19 @@ pub struct WithdrawArgs {
pub confirm: bool,
}

/// Hyperliquid charges a fixed $1 USDC withdrawal fee on every withdrawal.
/// The fee is deducted from your balance — the recipient receives the full requested amount.
const WITHDRAWAL_FEE_USDC: f64 = 1.0;

pub async fn run(args: WithdrawArgs) -> anyhow::Result<()> {
if args.amount <= 0.0 {
anyhow::bail!("--amount must be positive (got {})", args.amount);
}
if args.amount < 2.0 {
eprintln!("WARNING: Minimum withdrawal is $2 USDC. Amounts below $2 will be rejected by Hyperliquid.");
anyhow::bail!(
"Minimum withdrawal is $2 USDC (got ${}).",
args.amount
);
}

let info = info_url();
Expand All @@ -46,10 +53,13 @@ pub async fn run(args: WithdrawArgs) -> anyhow::Result<()> {
let withdrawable: f64 = state["withdrawable"]
.as_str().and_then(|s| s.parse().ok()).unwrap_or(0.0);

if args.amount > withdrawable {
// Check balance covers amount + $1 fee
let total_deducted = args.amount + WITHDRAWAL_FEE_USDC;
if total_deducted > withdrawable {
anyhow::bail!(
"Insufficient withdrawable balance: requested {:.6} USDC, available {:.6} USDC",
args.amount, withdrawable
"Insufficient balance: withdrawal ${:.2} + $1.00 fee = ${:.2} required, \
but only ${:.2} USDC available.",
args.amount, total_deducted, withdrawable
);
}

Expand All @@ -61,14 +71,19 @@ pub async fn run(args: WithdrawArgs) -> anyhow::Result<()> {
"action": "withdraw3",
"wallet": wallet,
"destination": destination,
"amount_usd": args.amount,
"amountToReceive_usd": args.amount,
"withdrawalFee_usd": WITHDRAWAL_FEE_USDC,
"totalDeducted_usd": total_deducted,
"withdrawable": format!("{:.6}", withdrawable),
"note": if args.confirm { "" } else { "Add --confirm to execute. Funds arrive on Arbitrum in ~2-5 minutes." }
"note": "A $1 USDC fee is deducted from your balance. Recipient receives the full amount. Add --confirm to execute."
}));
return Ok(());
}

println!("Signing withdraw for {} USDC to {}...", args.amount, destination);
println!(
"Withdrawing {} USDC to {} (+ $1.00 fee deducted from balance)...",
args.amount, destination
);
let signed = onchainos_hl_sign_withdraw(&destination, &amount_str, nonce, &wallet, sign_chain_id)?;
let result = submit_exchange_request(exchange, signed).await?;

Expand All @@ -81,9 +96,11 @@ pub async fn run(args: WithdrawArgs) -> anyhow::Result<()> {
"action": "withdraw3",
"wallet": wallet,
"destination": destination,
"amount_usd": args.amount,
"amountReceived_usd": args.amount,
"feeDeducted_usd": WITHDRAWAL_FEE_USDC,
"totalDeducted_usd": total_deducted,
"result": result,
"note": "USDC will arrive on Arbitrum in ~2-5 minutes."
"note": "USDC will arrive on Arbitrum in ~2-5 minutes. $1 fee was deducted from your Hyperliquid balance."
}));

Ok(())
Expand Down
14 changes: 14 additions & 0 deletions skills/hyperliquid/src/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,20 @@ pub fn build_batch_cancel_action(orders: &[(usize, u64)]) -> Value {
})
}

// ─── Leverage ────────────────────────────────────────────────────────────────

/// Build an updateLeverage action.
/// Sets account-level leverage for a coin before placing an order.
/// isCross=true → cross margin; false → isolated margin.
pub fn build_update_leverage_action(asset: usize, is_cross: bool, leverage: u32) -> Value {
json!({
"type": "updateLeverage",
"asset": asset,
"isCross": is_cross,
"leverage": leverage
})
}

// ─── Spot/Class transfer ─────────────────────────────────────────────────────

/// Build a usdClassTransfer action (perp ↔ spot USDC).
Expand Down
Loading