Skip to content

Commit 5af80a0

Browse files
authored
Merge pull request #154 from GeoGu360/fix/hyperliquid-v0.3.1
fix(hyperliquid): add --leverage flag to order, $1 fee notice on withdraw (v0.3.1)
2 parents c24d74d + 3f1bd4e commit 5af80a0

8 files changed

Lines changed: 119 additions & 22 deletions

File tree

skills/hyperliquid/.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "hyperliquid",
33
"description": "Hyperliquid on-chain perpetuals DEX — check positions, get market prices, place and cancel perpetual orders on Hyperliquid L1 (chain_id 999).",
4-
"version": "0.3.0",
4+
"version": "0.3.1",
55
"author": {
66
"name": "GeoGu360",
77
"github": "GeoGu360"

skills/hyperliquid/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

skills/hyperliquid/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "hyperliquid"
3-
version = "0.3.0"
3+
version = "0.3.1"
44
edition = "2021"
55

66
[[bin]]

skills/hyperliquid/SKILL.md

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

239239
# Get price for a specific coin
240-
hyperliquid prices --market BTC
241-
hyperliquid prices --market ETH
242-
hyperliquid prices --market SOL
240+
hyperliquid prices --coin BTC
241+
hyperliquid prices --coin ETH
242+
hyperliquid prices --coin SOL
243243
```
244244

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

286+
# Market long BTC with 10x cross leverage (sets leverage first, then places order)
287+
hyperliquid order --coin BTC --side buy --size 0.01 --leverage 10 --confirm
288+
289+
# Limit long BTC with 5x isolated margin
290+
hyperliquid order --coin BTC --side buy --size 0.01 --type limit --price 60000 --leverage 5 --isolated --confirm
291+
286292
# Market long BTC with bracket: SL at $95000, TP at $110000 (normalTpsl OCO)
287293
hyperliquid order \
288294
--coin BTC --side buy --size 0.01 \
@@ -296,6 +302,11 @@ hyperliquid order \
296302
--confirm
297303
```
298304

305+
**Leverage flags:**
306+
- `--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.
307+
- `--isolated` — use isolated margin mode (default is cross margin when `--leverage` is set).
308+
- 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.
309+
299310
**Output (executed with bracket):**
300311
```json
301312
{
@@ -579,8 +590,10 @@ Withdraws USDC from your Hyperliquid perp account to your Arbitrum wallet.
579590

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

593+
> **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.
594+
582595
```bash
583-
# Preview
596+
# Preview (shows fee breakdown)
584597
hyperliquid withdraw --amount 50
585598

586599
# Execute
@@ -590,10 +603,10 @@ hyperliquid withdraw --amount 50 --confirm
590603
hyperliquid withdraw --amount 50 --destination 0xRecipient --confirm
591604
```
592605

593-
**Output fields:** `action`, `wallet`, `destination`, `amount_usd`, `result`
606+
**Output fields:** `action`, `wallet`, `destination`, `amountToReceive_usd`, `withdrawalFee_usd`, `totalDeducted_usd`, `result`
594607

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

829842

830843

844+
845+
---
846+
847+
## Changelog
848+
849+
### v0.3.1 (2026-04-12)
850+
851+
- **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)
852+
- **feat**: `order` — new `--isolated` flag to use isolated margin mode when `--leverage` is set (default is cross)
853+
- **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

skills/hyperliquid/plugin.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
schema_version: 1
22
name: hyperliquid
3-
version: "0.3.0"
3+
version: "0.3.1"
44
description: "Trade perpetuals on Hyperliquid — check positions, get prices, place market/limit orders with TP/SL brackets, close positions, deposit USDC"
55
author:
66
name: GeoGu360

skills/hyperliquid/src/commands/order.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::config::{info_url, exchange_url, normalize_coin, now_ms, CHAIN_ID, AR
44
use crate::onchainos::{onchainos_hl_sign, resolve_wallet};
55
use crate::signing::{
66
build_bracketed_order_action, build_limit_order_action, build_market_order_action,
7+
build_update_leverage_action,
78
format_px, market_slippage_px, submit_exchange_request,
89
};
910

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

41+
/// Leverage multiplier before placing (e.g. 10 for 10x cross). Sets account leverage for this
42+
/// coin first, then places the order. Omit to keep the current account setting.
43+
#[arg(long)]
44+
pub leverage: Option<u32>,
45+
46+
/// Use isolated margin mode when --leverage is set (default is cross margin)
47+
#[arg(long)]
48+
pub isolated: bool,
49+
4050
/// Reduce only — only reduce an existing position, never increase it
4151
#[arg(long)]
4252
pub reduce_only: bool,
@@ -63,6 +73,13 @@ pub async fn run(args: OrderArgs) -> anyhow::Result<()> {
6373
.parse()
6474
.map_err(|_| anyhow::anyhow!("Invalid size '{}' — must be a number (e.g. 0.01)", args.size))?;
6575

76+
// Validate leverage range (Hyperliquid accepts 1–100)
77+
if let Some(lev) = args.leverage {
78+
if !(1..=100).contains(&lev) {
79+
anyhow::bail!("--leverage must be between 1 and 100 (got {})", lev);
80+
}
81+
}
82+
6683
// TP/SL bracket validation
6784
if let Some(sl) = args.sl_px {
6885
if is_buy && args.tp_px.map_or(false, |tp| tp <= sl) {
@@ -147,6 +164,10 @@ pub async fn run(args: OrderArgs) -> anyhow::Result<()> {
147164
}
148165
};
149166

167+
let leverage_preview = args.leverage.map(|l| {
168+
format!("{}x {}", l, if args.isolated { "isolated" } else { "cross" })
169+
});
170+
150171
println!(
151172
"{}",
152173
serde_json::to_string_pretty(&serde_json::json!({
@@ -157,6 +178,7 @@ pub async fn run(args: OrderArgs) -> anyhow::Result<()> {
157178
"size": args.size,
158179
"type": args.r#type,
159180
"price": args.price,
181+
"leverage": leverage_preview,
160182
"stopLoss": args.sl_px.map(format_px),
161183
"takeProfit": args.tp_px.map(format_px),
162184
"reduceOnly": args.reduce_only,
@@ -181,6 +203,27 @@ pub async fn run(args: OrderArgs) -> anyhow::Result<()> {
181203
}
182204

183205
let wallet = resolve_wallet(CHAIN_ID)?;
206+
207+
// Set leverage before placing the order if --leverage was provided
208+
if let Some(lev) = args.leverage {
209+
let is_cross = !args.isolated;
210+
let lev_action = build_update_leverage_action(asset_idx, is_cross, lev);
211+
let lev_nonce = now_ms();
212+
let lev_signed = onchainos_hl_sign(&lev_action, lev_nonce, &wallet, ARBITRUM_CHAIN_ID, true, false)?;
213+
let lev_result = submit_exchange_request(exchange, lev_signed).await
214+
.map_err(|e| anyhow::anyhow!("Leverage update failed: {}", e))?;
215+
if lev_result["status"].as_str() == Some("err") {
216+
anyhow::bail!(
217+
"Leverage update rejected by Hyperliquid: {}",
218+
lev_result["response"].as_str().unwrap_or("unknown error")
219+
);
220+
}
221+
println!(
222+
"Leverage set to {}x ({}) for {}",
223+
lev, if is_cross { "cross" } else { "isolated" }, coin
224+
);
225+
}
226+
184227
let signed = onchainos_hl_sign(&action, nonce, &wallet, ARBITRUM_CHAIN_ID, true, false)?;
185228
let result = submit_exchange_request(exchange, signed).await?;
186229

skills/hyperliquid/src/commands/withdraw.rs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,19 @@ pub struct WithdrawArgs {
2323
pub confirm: bool,
2424
}
2525

26+
/// Hyperliquid charges a fixed $1 USDC withdrawal fee on every withdrawal.
27+
/// The fee is deducted from your balance — the recipient receives the full requested amount.
28+
const WITHDRAWAL_FEE_USDC: f64 = 1.0;
29+
2630
pub async fn run(args: WithdrawArgs) -> anyhow::Result<()> {
2731
if args.amount <= 0.0 {
2832
anyhow::bail!("--amount must be positive (got {})", args.amount);
2933
}
3034
if args.amount < 2.0 {
31-
eprintln!("WARNING: Minimum withdrawal is $2 USDC. Amounts below $2 will be rejected by Hyperliquid.");
35+
anyhow::bail!(
36+
"Minimum withdrawal is $2 USDC (got ${}).",
37+
args.amount
38+
);
3239
}
3340

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

49-
if args.amount > withdrawable {
56+
// Check balance covers amount + $1 fee
57+
let total_deducted = args.amount + WITHDRAWAL_FEE_USDC;
58+
if total_deducted > withdrawable {
5059
anyhow::bail!(
51-
"Insufficient withdrawable balance: requested {:.6} USDC, available {:.6} USDC",
52-
args.amount, withdrawable
60+
"Insufficient balance: withdrawal ${:.2} + $1.00 fee = ${:.2} required, \
61+
but only ${:.2} USDC available.",
62+
args.amount, total_deducted, withdrawable
5363
);
5464
}
5565

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

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

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

89106
Ok(())

skills/hyperliquid/src/signing.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,20 @@ pub fn build_batch_cancel_action(orders: &[(usize, u64)]) -> Value {
287287
})
288288
}
289289

290+
// ─── Leverage ────────────────────────────────────────────────────────────────
291+
292+
/// Build an updateLeverage action.
293+
/// Sets account-level leverage for a coin before placing an order.
294+
/// isCross=true → cross margin; false → isolated margin.
295+
pub fn build_update_leverage_action(asset: usize, is_cross: bool, leverage: u32) -> Value {
296+
json!({
297+
"type": "updateLeverage",
298+
"asset": asset,
299+
"isCross": is_cross,
300+
"leverage": leverage
301+
})
302+
}
303+
290304
// ─── Spot/Class transfer ─────────────────────────────────────────────────────
291305

292306
/// Build a usdClassTransfer action (perp ↔ spot USDC).

0 commit comments

Comments
 (0)