← Index
Source: docs/history/2026-07-21-rabbit-history.md (auto-generated by scripts/generate-docs-html.mjs — edit the .md, not this file)

2026-07-21 — rabbit history

Source docs: jun-30-rabbit-design.md — §3 Task 3-P2 (Phase 2a) + the "July 21 Request" block (currently under §4, to be relocated to §3 by jay).

Market: Hyperliquid testnet TradePanel (Phase 2a, MetaMask signing)

Added app/market/TradePanel.tsx and rendered it above the ETH Perp orderbook on /market (before <OrderBook coin="ETH" /> in app/market/page.tsx).

UI: shadcn-inspired restyle of the whole app (CSS-only)

Rewrote app/globals.css to adopt shadcn/ui's neutral "zinc" design language across all 32 components / 16 routes. No new dependencies (no Tailwind/Radix) — jay chose the lightweight approach.

UI: fancier top nav — sticky glassy header + active-page pill

Nav felt flat, so upgraded it: wrapped the bar in <header className="site-header"> (app/Nav.tsx) — full-width sticky header with backdrop-filter blur + translucent bg + bottom border. Nav links became pills with hover backgrounds, and the current route gets an active pill (aria-current="page") via usePathname() in app/NavLinks.tsx. Styles in app/globals.css. Verified light + dark, no console errors. Same branch.

Fix: TradePanel reads Spot + Perp balance (0-balance confusion)

Symptom: panel showed 0 USDC while the HL testnet site showed 999 for the same address 0xD64a…069c (wrong-account cause ruled out — address matched). Root cause: the testnet faucet credits the Spot balance, but the panel only read clearinghouseState.withdrawable (Perp), which is 0 until USDC is transferred Spot → Perp.

Fix in app/market/TradePanel.tsx: also fetch spotClearinghouseState, display both ("Perp X · Spot Y USDC"), and show a hint to transfer Spot → Perp when funds sit in Spot (perp < 1 && spot > 1). tsc clean, page renders, no console errors — actual values need jay's MetaMask connect to confirm. Same branch claude/work-2026-07-21. Full-address API query still offered as the definitive check if needed.

Feature: TradePanel now trades Perp AND Spot

Added a Perp / Spot toggle to app/market/TradePanel.tsx. Motivation: jay's mock USDC sits in Spot, so a Spot tab lets him trade it directly (no Spot→Perp transfer needed).

Fix: Unified-account aware hint (removed wrong Spot→Perp advice)

jay's account is in Unified mode, so the earlier "transfer Spot → Perp" hint was wrong — the unified pool already lets Spot USDC collateralize perps (no transfer needed). In app/market/TradePanel.tsx: replaced the red transfer instruction with a muted note ("With a Unified account, your Spot USDC also collateralizes perps — order without transferring"), and changed perp-mode usable to perp + spot so the "usable $" reflects the unified collateral pool (~999) instead of the classic perp-only withdrawable (0). tsc clean, no console errors. Same branch.

Feature: Perp leverage + margin-mode control

Added a Margin (Cross/Isolated) + Leverage control to perp mode in app/market/TradePanel.tsx. Leverage is bounded by the asset's meta.universe[idx].maxLeverage (and forced Isolated when onlyIsolated); a "Set leverage" button fires HL's updateLeverage({ asset, isCross, leverage }) action (one MetaMask signature) and refreshes. Defaults to 3x cross on connect. tsc exit 0, page renders, no console errors. Same branch claude/work-2026-07-21.

Spot default → ETH/USDC + Unit-token display names

jay asked for an ETH/USDC spot default. I wrongly claimed testnet had no ETH/USDC — root cause: HL shows "ETH" as the display name for the Unit token UETH (no token is literally named "ETH"; the API only has UETH/ETHER), and testnet spot has 1600+ junk/duplicate tokens, so matching by raw token.name was flaky. Fix in app/market/TradePanel.tsx: build tokens via an index map (not array position), add a Unit display map (UETH→ETH, UBTC→BTC, USOL→SOL) so labels read like the HL UI, and default the spot pair to ETH/USDC (→ HYPE/USDC → first). tsc exit 0, no console errors. Caveat: testnet also has fake "ETH"-named tokens; the default lands on Unit UETH — jay to confirm it's the intended market. Same branch.

Debug: surface real cause of "Failed to sign the typed data" error

jay hit Failed to sign the typed data using the wallet when placing a spot order. That string is a generic wrapper the SDK (@nktkas/hyperliquid signTypedData) throws around the underlying error, exposed on error.cause — my catch blocks only showed e.message, hiding the real reason. Added an errMsg(e) helper that appends error.cause, switched all catch blocks to it, and added console.error("[TradePanel]", e) so the full error (with cause) is inspectable. Now a retry will show whether it's a MetaMask rejection (user closed/rejected the popup) or a genuine data/signing issue. tsc exit 0, no console errors. Panel itself is working (connected, ETH/USDC, Spot 739.27 USDC shown). Same branch claude/work-2026-07-21.

Root cause + fix: agent wallet (MetaMask can't sign HL orders)

Real signing error surfaced: Provided chainId "1337" must match the active chainId "42161". HL L1 actions (order/cancel/updateLeverage) sign over an EIP-712 domain with chainId 1337 (phantom agent), but MetaMask refuses typed data whose domain chainId ≠ the wallet's active chain (Arbitrum One 42161). So MetaMask fundamentally cannot sign HL orders — this is why HL's own site uses agent wallets. (jay's original ".env stored key" instinct was actually correct.)

Fix (jay chose agent wallet) in app/market/TradePanel.tsx: