All notable changes to perp-cli. Format follows Keep a Changelog, versioning follows SemVer.
Adds Hyperliquid Outcome markets (HIP-4) support — a new asset class. Verified end-to-end against mainnet (place + cancel real order against asset id 100,000,010). Two rounds of independent Codex review closed before release.
perp outcomecommand tree —list,book,positions,orders,buy,sell,cancel. USDH-quoted, fully collateralized binary/range contracts; no leverage / no liquidation. $10 USDH min order. Currently 1 live market on mainnet (BTC binary daily settling at 06:00 UTC).HyperliquidOutcomeAdapter— composes withHyperliquidAdapterfor signing. Asset id formula100_000_000 + (10 * outcome + side). Bypasses HL's cached spot-state for fresh post-fill positions, then invalidates theacct:cache after place/cancel for downstream readers.OutcomeAdapterinterface insrc/exchanges/outcome-interface.ts(mirrors the SpotAdapter shape; ready for additional venues if HIP-4 pattern spreads).- Probe scripts under
scripts/probe-outcome-{ws,order}.tsdocumenting the WebSocket and exchange-action shapes that informed this implementation. - +16 unit tests covering encoding (
10*outcome+side), coin name conventions (#<enc>/+<enc>), description parsing, venue-rejection assertion helpers, and cancel-status validation. Total: 1305 → 1323.
- Rule #2 (No Fallback): venue rejections embedded in
status:"ok"+statuses[0].errorare now thrown asEXCHANGE_ERROR(not silently masked); unknown outcome →SYMBOL_NOT_FOUND; out-of-range side / encoding overflow →INVALID_PARAMS._resolveUserAddress()prefers_hl.address, falls back to OWS-stored agent metauserEvmAddress, and throwsNO_SIGNER_AVAILABLEonly if both are absent (no silent zero-balance substitution). - Rule #3 (Single Secret Source): no new env vars; reuses existing HL agent.
--dry-runusescommand.optsWithGlobals()so the parent program's flag is not shadowed; pre-validates min-notional so the dry-run output cannot lie about a viable order.- Encoded-side mismatch detection —
outcome buy 2 #10(where#10encodes outcome 1) throwsINVALID_PARAMSwith guidance instead of silently routing to outcome 2. - Pre-checks spot USDH balance on buy and surfaces
INSUFFICIENT_BALANCEwith a "bridge USDC→USDH" remediation instead of an opaque venue error. outcome ordersuses the normalised lowercasebuy/sellfrom the HL adapter (was rendering all buys as SELL).
- Portfolio aggregation of outcome holdings (roll into
perp portfolio). - Landing page outcome line.
outcome close <outcome> <side>shortcut (useoutcome sell <outcome> <side> <usd>for now).- HIP-4 builder/deployer mechanics for creating new outcomes/questions.
Closes Codex independent review of v0.12.17. 1 HIGH regression + 1 MEDIUM Rule #2 gap.
account twap-ordersover-broad catch (HIGH, v0.12.17 regression) —src/commands/account.tscatch wrapped bothgetAdapter()andpac(), then unconditionally returnedNOT_SUPPORTED+ Pacifica remediation in JSON mode. This mislabeled unrelated failures (network errors, locked wallets, missing PK, other typedPerpErrors). Now uses an explicithasPacificaSdk()guard so only the actual Pacifica-only assertion is rewritten; other errors propagate to the standard classifier untouched.- Landing page Aster agent-required false-positive (MED) —
LandingExchangeStatusnow carrieserrorCodeand the agent-required hint requires both (a) local Aster agent absent AND (b) the failure was actuallyNOT_IMPLEMENTED/NO_SIGNER_AVAILABLE/AGENT_EXPIRED. Generic Aster outages no longer render "agent required" — they fall through to the red dash (Rule #2: no silent classification fallback). Tests 1305 → 1307 (+2 regression cases).
account twap-ordersHL/LT/Aster generic error — non-Pacifica venues surfaced "Market settings are only available on Pacifica" via the sharedpac()helper, leaking an internal helper name. Now returns a TWAP-specificNOT_SUPPORTEDenvelope withremediation: "Use 'perp -e pacifica account twap-orders'".- Landing page Aster agent-missing detection — tightened detection so only the
NOT_IMPLEMENTED"agent required" path renders the agent-required hint; other failure modes still fall through to the red dash (Rule #2 — no silent fallback).
- Skill bundle alignment —
skills/perp-cli/SKILL.mdnow reflects v0.12.16 commands and follows the Anthropic skill-authoring guide.
0.12.16 — 2026-05-02
- Landing page Aster status clarity —
perpno-arg landing page renderedAster: —(generic dash) for users with env-PK but no registered Aster agent, indistinguishable from a venue outage. Aster venue ships only signed account endpoints (no public address-based balance query exists, unlike HL/PAC/LT confirmed via v3 docs). Now renders⚙ Aster agent required → perp wallet agent approve asterwhen agent missing; other failure modes still fall through to the red dash.
0.12.15 — 2026-05-02
- Top-level catch preserves PerpError code + remediation —
index.tsprogram-level catch was the last spot where typed errors got downgraded tocode:"FATAL"even after v0.12.13's classifyError fix. Now branches onerr instanceof PerpErrorand forwards the full structured payload (status, retryable, retryAfterMs, remediation) into the JSON envelope. Aster's NOT_IMPLEMENTED for missing agent now surfaces with the correct typed code and actionable remediation. Verified live in Docker.
0.12.14 — 2026-05-02
Release follow-up for v0.12.13. Closes remaining MCP surface drift and fixes invalid-symbol trade validation ordering.
- MCP server stale v0.12.x surfaces (MED) — closed 10 leftover stale user-facing references in
mcp-server.ts: 4-DEX lists now include Aster, and advisor/schema/prompt output no longer points at removedstatus/account balancecommands. - Trade validator invalid-symbol flow (MED) —
validateTrade()now checksgetMarkets()membership beforegetOrderbook(), so invalid symbols returnsymbol_valid=falseinstead of surfacing venue orderbook errors. Added a regression test.
0.12.13 — 2026-05-01
Release-stable target. Fixes 2 BLOCKERS + 2 MEDIUM identified in Codex final QA of v0.12.12. Tests 1282 → 1301 (+19).
- HL standard/default mode portfolio undercount (BLOCKER) —
HyperliquidAdapter.isUnifiedAccountwas a hardcodedtrue, causingportfolio.tsto drop spot USDC fromtotalAccountValueUsdfor standard/default-mode users. Replaced with dynamic getter from_getAbstractionMode(). Updates 3 call sites: portfolio.ts, spot-perp-arb-strategy.ts, funding-arb-v2-strategy.ts. - PerpError → JSON envelope semantic loss (BLOCKER) —
classifyError()ignorederr instanceof PerpErrorand re-derived from message text;withJsonErrors()droppedcodeandremediation. NowPerpErroris the source of truth: typed code preserved, remediation surfaced at top level. AsterNOT_IMPLEMENTEDfor missing agent now correctly surfaces with remediation. - Aster 429 retry — 8s backoff reachable (MED) —
MAX_ATTEMPTS=3made the documented 8s wait unreachable. Bumped to 4 (initial + 3 retries with 2s/4s/8s). - MCP server v0.12.x alignment (MED) — added Aster support, portfolio queries 4 exchanges, drops references to renamed commands (
status/account balance).
0.12.12 — 2026-05-01
Discovered via HypurrQuant_FE reference comparison: Aster venue rejects master self-signing entirely. Codex's v0.12.11 user/signer split was correct syntax but didn't address the venue rule.
- Aster requires agent for Tier 2/3 (HIGH) —
aster.ts:_resolveSigner()now throwsNOT_SUPPORTEDfor master/PK self-signing with remediationperp wallet agent approve aster --master <wallet>. Per Aster V3 spec,signerMUST be a registered API_WALLET (agent); master is never a valid signer. Reference: HypurrQuant_FEAsterPerpAdapter.ts:773-775.
- Aster signed GET/DELETE 429 retry loop restored (regression from v0.12.11 dd85a96) — up to 3 attempts with exponential backoff (2s/4s/8s) and fresh nonce/signature per attempt. Non-429 errors still throw immediately.
alerts.ts:303env passthrough —ASTER_PRIVATE_KEYwas missing from the alerts daemon spawn env list. Fixed (Codex v0.12.11 re-review #5 follow-up).
- HL portfolio non-USDC collateral math (sum HYPE/BTC/USDH at mark prices) — Codex v0.12.11 re-review #3 PARTIAL. v0.12.11 stderr warning preserved; full math deferred to a separate change once spotMetaAndAssetCtxs pricing logic is in place.
0.12.11 — 2026-05-01
Aggregate fix from Codex independent review of v0.12.0→v0.12.10. 7 commits, +21 tests (1260 → 1281).
- Aster Tier 2/3 signer model (HIGH) — master/PK paths now emit
userandsigneras separate query fields per V3 spec, even when values are identical. Was the actual root cause of "Signature check failed" on/fapi/v3/accountWithJoinMargin. - Aster signed GET/DELETE error code validation (HIGH) — unified
_handleAsterResponse()validates HTTP status AND venue JSON error envelope. Previously HTTP 200 +{code, msg:"..."}was silently cached as zero balance (Rule #2 violation). - HL
dexAbstractionmode (MED) — was throwing INVALID_PARAMS; now mapped to unified semantics (spot pool = collateral). Eliminates a venue-side state that blocked legacy users. - HL portfolio non-USDC collateral (MED) — getBalance() emits stderr note when portfolio-margin user has non-USDC eligible collateral (HYPE/BTC/USDH) so undercount is visible.
wallet key create --passphraseshadow (MED) — appliedoptsWithGlobals()fix from v0.12.3 to this missed subcommand.- Setup landing-page Aster check (LOW) — recognizes
ASTER_PRIVATE_KEY(missed by v0.12.10 envMap restore). - Aster testnet support (LOW) —
aster-typed-data.tsnow branches Domain B chainId by_testnetflag (1666 mainnet, 714 testnet).
- Stale "env-key fallback" comment in
manage.ts:315(no longer accurate post-keystore migration).
0.12.10 — 2026-05-01
tryLoadPrivateKey("aster")now readsASTER_PRIVATE_KEYenv var. v0.12.4 cleared the entry (legacy HMAC removal) and v0.12.6 only restored theEXCHANGE_ENV_MAPhalf — theconfig.tsenvMapused by adapter init was missed, so Aster Tier 3 PK direct path returned "No signing path configured" even with a valid env-PK set. Discovered during v0.12.9 Docker QA: PAC/HL/LT routed via env, Aster blocked.
0.12.9 — 2026-05-01
_getAbstractionMode()now maps HL's implicit"default"response (returned when a user has never invokeduserSetAbstraction) to"standard". Previously threwINVALID_PARAMSUNKNOWN_ACCOUNT_MODE for unset accounts, blockinggetBalance()for any new HL user. Discovered during v0.12.8 Docker QA on a fresh wallet.
0.12.8 — 2026-05-01
perp wallet manage account-mode [<unified|standard|portfolio>]— set or query HL account abstraction mode viauserSetAbstractionaction (master-signed). No-arg form prints current mode without changing it.perp account balanceredirect — friendly pointer toperp portfolio(renamed in v0.12), exits 1 with remediation instead of "unknown command".CHANGELOG.md— Keep a Changelog format; v0.12.0 onward documented.
HyperliquidAdapter.setAddress(address)— read-only path lets the show branch queryuserAbstractionwithout unlocking the master key.
0.12.7 — 2026-05-01
HyperliquidAdapter._getAbstractionMode()— explicit query to HL infouserAbstraction. Returns"unified" | "standard" | "portfolio". Cached viaTTL_ACCOUNT.
getBalance()now branches by actual account abstraction mode instead of the!_dexheuristic. Standard-mode users (required for builder fee accrual) no longer receive unified accounting by mistake.- Removed silent catch fallback in
getBalance()(SSOT Rule #2): spot fetch errors propagate with remediation instead of falling back to perp values silently.
0.12.6 — 2026-05-01
- Restored Aster as a normal EVM exchange in
EXCHANGE_ENV_MAP. v0.12.4 over-corrected by removing Aster entirely; users importing an EVM key got HL/LT/PAC configured but not Aster despite Plan v3.0 retainingASTER_PRIVATE_KEYTier 3 path. wallet set aster <evm-pk>now validates as EVM (32-byte secp256k1) — same as HL/LT.
0.12.5 — 2026-05-01
scripts/sync-skill-version.mjs— auto-syncsskills/perp-cli/SKILL.mdmetadata.versiontopackage.jsonversion on every release. Wired intoprepublishOnly.pnpm sync-skill-versionscript for manual runs.
- SKILL.md
metadata.versionwas stuck at"0.7.7"for 5 major version cycles. Synced to current package version.
Tagged but never published to npm — paused for the SKILL.md version-sync finding from clean-state Docker QA. Contents folded into v0.12.5.
0.12.3 — 2026-05-01
--non-interactiveflag forperp setup— full scripted onboarding via--wallet-name,--passphrase,--default-exchange(CI / Docker / agent-driven init).wallet generateandwallet importnow accept--passphraseflag and respectOWS_PASSPHRASEenv via the standard 3-path resolver.
- Auto-rollback orphan local OWS wallet on pre-venue failure of
agent approve(HL / PAC / Aster). Retry no longer hits "wallet name already exists". - Improved remediation strings for
APPROVE_PARTIALerrors.
- Aster legacy HMAC path:
EXCHANGE_ENV_MAP.aster(chain: "apikey"),EXCHANGE_PK_ENV_VARS.aster, setup wizard'sASTER_API_KEYprompt. (Note: over-corrected — restored as EVM in v0.12.6.)
0.12.2 — 2026-05-01
- Pinned
lighter-ts-sdkto exact1.0.10. SDK 1.0.11 shipped a breaking change tosignChangePubKey(object-args → positional-args) under a patch version bump (SemVer violation). --passphraseflag on subcommands (wallet generate/import,wallet agent *,setup) now usescommand.optsWithGlobals()to break the parent program's same-named flag shadow. Previously--passphrase Xon subcommand was silently ignored, causingwallet generateto encrypt with empty string.
0.12.1 — 2026-05-01
- Lighter agent wiring in
_initWithOwscode path. The OWS-active flow skipped Tier 1 (agent) wiring for Lighter, falling through to auto-setup-at-slot-4 and breaking under SDK 1.0.11.
0.12.0 — 2026-05-01
- SSOT Rule #3 (Single Secret Source per Key) — see
docs/SSOT_RULES.md. - Lighter L2 keystore at
~/.perp/lighter-agents/<account>-<slot>.json(AES-256-GCM, mode 0600). Replaces plaintext env storage. - One-time auto-migration of legacy
LIGHTER_API_KEYenv to encrypted keystore. wallet show --jsonnow exposesowsActive: { name, evmAddress, solanaAddress }so agents can discover the master EVM/Solana address without env-derived fallbacks.- Lighter referral apply is now L2-signed (works via agent path; previously gated by master
pk).
- Referral codes are now mandatory —
settings.referralsfield andsettings referrals on/offcommand removed. Pacifica's builder code was already always-on; HL/LT aligned. ~/.perp/.envis the only auto-loaded env file. CWD.envauto-load removed (was a Rule #3 violation surface).
- Referral apply
.catchno longer marksapplied=trueon failure (SSOT Rule #2). Failures propagate to stderr;referralAppliedstays false until next try.
- Lighter L2 slot key is no longer stored in
.envplaintext. Aligned with Aster/HL/PAC agents which were already in OWS vault.