From 5c848e974839bdcf0f1f1e64bd5cdd2009e7c457 Mon Sep 17 00:00:00 2001 From: brianna Date: Mon, 20 Jul 2026 14:33:25 +0800 Subject: [PATCH] feat(wallet): show per-position margin in the HL balance view A unified/HIP-3 Hyperliquid account can show a large account value but little free USDC because open positions hold the collateral as margin. Surface it so the tie-up is visible instead of surprising. - HyperliquidPerpPosition gains `marginUsed` - `acp wallet balance` PERPS table gains a MARGIN column Pairs with internal-trading-bot#138 (adds marginUsed to /trade/hl-status and names the culprit position in HL insufficient-funds errors). Co-Authored-By: Claude Opus 4.8 --- src/commands/wallet.ts | 12 ++++++++---- src/lib/api/agent.ts | 4 ++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/commands/wallet.ts b/src/commands/wallet.ts index c276bb2..b207c24 100644 --- a/src/commands/wallet.ts +++ b/src/commands/wallet.ts @@ -139,6 +139,7 @@ function hlPosition(p: HyperliquidPerpPosition): { size: string | null; entry: string | null; value: string | null; + margin: string | null; upnl: string | null; } { const pos = (p.position ?? p) as Record; @@ -150,7 +151,10 @@ function hlPosition(p: HyperliquidPerpPosition): { const v = Math.abs(parseFloat(size)) * parseFloat(entry); if (Number.isFinite(v)) value = v.toFixed(2); } - return { coin, size, entry, value, upnl: hlNum(pos.unrealizedPnl) }; + // Collateral locked against the position — explains why free/withdrawable USDC + // can be far below the account value. + const margin = hlNum(pos.marginUsed); + return { coin, size, entry, value, margin, upnl: hlNum(pos.unrealizedPnl) }; } // A spot balance worth showing — a strictly positive total. Used as the single @@ -215,14 +219,14 @@ function printHyperliquid(hl?: HyperliquidBalanceSummary | null): void { console.log( ` ${c.dim("COIN".padEnd(10))}${c.dim("SIZE".padEnd(16))}${c.dim( "ENTRY".padEnd(12) - )}${c.dim("VALUE".padEnd(12))}${c.dim("PnL")}` + )}${c.dim("VALUE".padEnd(12))}${c.dim("MARGIN".padEnd(12))}${c.dim("PnL")}` ); for (const p of positions) { - const { coin, size, entry, value, upnl } = hlPosition(p); + const { coin, size, entry, value, margin, upnl } = hlPosition(p); console.log( ` ${c.cyan(coin.padEnd(10))}${clip(size ?? "—", 14).padEnd(16)}${usd( entry - ).padEnd(12)}${usd(value).padEnd(12)}${pnl(upnl)}` + ).padEnd(12)}${usd(value).padEnd(12)}${usd(margin).padEnd(12)}${pnl(upnl)}` ); } } diff --git a/src/lib/api/agent.ts b/src/lib/api/agent.ts index 967f75f..029fb58 100644 --- a/src/lib/api/agent.ts +++ b/src/lib/api/agent.ts @@ -600,6 +600,9 @@ export interface HyperliquidPerpPosition { entryPx?: string | number | null; positionValue?: string | number | null; unrealizedPnl?: string | number | null; + // Collateral locked against the position — surfaced so the balance view can + // explain why free/withdrawable USDC is below the account value. + marginUsed?: string | number | null; leverage?: { type?: string; value?: number; rawUsd?: string } | null; position?: { coin?: string | null; @@ -607,6 +610,7 @@ export interface HyperliquidPerpPosition { entryPx?: string | number | null; positionValue?: string | number | null; unrealizedPnl?: string | number | null; + marginUsed?: string | number | null; [key: string]: unknown; }; [key: string]: unknown;