v0.2.1: scaled TP, pnl/liquidation alerts, bot fix#2
Merged
Conversation
- Add `trade scale-tp` command: place multiple TP limit orders at different price levels (분할익절). Supports --dry-run. Example: perp trade scale-tp BTC --levels "72000:25,75000:25,80000:50" - Add reduceOnly option to limitOrder interface (all 3 exchanges) - Add pnl/liquidation alert types to alert daemon: - `alert add -t pnl --loss 50` — triggers when uPnL drops below -$50 - `alert add -t pnl --profit 100` — triggers when uPnL exceeds $100 - `alert add -t liquidation --margin-pct 20` — margin ratio warning - Fix bot.ts PLACEHOLDER job-id: was hardcoded string, now removed (startJob already appends --job-id automatically) - Bump version to 0.2.1 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Hiksang
added a commit
that referenced
this pull request
Apr 30, 2026
HypurrQuant_FE CLAUDE.md SSOT rule #2 bans fallback patterns of any form: live failure must propagate, NOT be silently substituted by local cache pretending to be authoritative state. The previous verifyAster implementation returned local cache items with `source: "local-cache"` + warnings — that's the textbook "silent substitution" anti-pattern. It hid that live verify isn't supported behind a synthesized success envelope, exactly the failure mode the SSOT rule exists to prevent. Resolution: throw `NOT_IMPLEMENTED` with explicit remediation pointing to `wallet agent list aster` (the proper local-cache surface). The aggregate verify command surfaces Aster as an error slot — caller sees clearly that this DEX has no live verify path. Code: - src/commands/agent.ts:58-72 — throw, no synthesized result - Aggregate handler unchanged: existing slotResult() + error rendering already handle thrown PerpError correctly Tests deleted (obsolete with no-cache return): - aster local cache verify / agentName not in cache / empty cache - aster text output renders expired ms - aggregate text mode includes per-DEX warnings - aster locally-expired entry Tests updated: - aster — throws NOT_IMPLEMENTED with remediation - aggregate happy path — aster slot is NOT_IMPLEMENTED error - aggregate partial failure — aster NOT_IMPLEMENTED + lighter coexist - aster text output prints NOT_IMPLEMENTED + remediation - timestamp test moved from aster to hyperliquid 1223 unit tests pass.
Hiksang
added a commit
that referenced
this pull request
Apr 30, 2026
Per HypurrQuant_FE CLAUDE.md SSOT rule #2: "fallback 은 어떤 형태로든 절대 금지. 실패하면 실패." Removed silent error swallowing and default substitution from 13 critical paths. Errors now propagate; explicit fallback opt-in is preserved where the fallback is a documented caller-facing behavior. Tier 1 — silent functional substitution: - trade-validator: removed .catch defaults on getBalance/getPositions/ getOrderbook (errors propagate); throw on missing limit price / leverage - smart-order: flipped fallback default true → false; opt-in only - position-history: validate side / realizedPnl from stream events; throw on malformed payloads instead of defaulting "long" / "0" - bridge-engine: refuse to fall through to Arbitrum RPC on unknown chain - lighter-spot: throw on missing nonce / txType from signer - guardrail/perp-guardrail: fail closed when policy_config missing - arb-auto / arb: skip Aster symbols missing markPrice / lastFundingRate (no more 0-coercion polluting downstream comparisons) Tier 2 — silent .catch(() => null): - api/public/{hyperliquid,lighter,pacifica}: removed silent error swallow from raw fetchers; callers (arb / arb-auto) now use Promise.allSettled with explicit stderr logging per failed DEX Tests: - updated 5 trade-validator tests to assert error propagation (was: silent default behavior) - updated 4 smart-order tests to use explicit { fallback: true } where the fallback path is intentionally exercised - added 3 new tests verifying default-throws behavior (no asks / no bids / IOC reject) - 1227 tests pass; build clean Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hiksang
added a commit
that referenced
this pull request
Apr 30, 2026
…logging Two helpers extracted to remove repeated boilerplate the SSOT-rule-#2 fixes introduced: - src/api/public/_http.ts: assertOk(res, label). Replaces 7 copies of the "throw on non-2xx with body excerpt" guard across pacifica.ts (×2), hyperliquid.ts (×1 hlPost), lighter.ts (×4). - src/utils.ts: logSettledRejections(settled, labels, prefix). Replaces 5 copies of the "log each rejected outcome to stderr" loop across arb.ts (×2), arb-auto.ts (×1), arb/index.ts (×1), strategies/funding-arb.ts (×1). Behavior preserved (1230/1230 tests still pass). The dashboard/ws-feeds.ts allSettled site is intentionally untouched — it silently swallows feed failures by design (REST fallback covers it) and is out of scope for this SSOT pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hiksang
added a commit
that referenced
this pull request
May 1, 2026
- New `docs/SSOT_RULES.md` codifies Single Secret Source rule alongside Rule #2 (no fallback). All cryptographic secrets live in exactly one persistent location with one consistent format. - Lighter L2 slot private key migrates from `~/.perp/.env LIGHTER_API_KEY` plaintext to AES-256-GCM-encrypted keystore at `~/.perp/lighter-agents/<account>-<slot>.json` (mode 0600). New module `src/agent-wallet/lighter-keystore.ts` (save/load/delete + one-time `migrateFromEnvIfPresent` for legacy users). - Adapter init reads via `loadLighterKey()`; constructor no longer reads any LIGHTER_* env vars. Migration helper auto-clears legacy `.env` entries on first run. - `wallet show --json` now exposes `owsActive: { name, evmAddress, solanaAddress }` so agents can discover the master EVM/Solana address without env-derived fallbacks. Closes the friction surfaced by trader-persona-alpha v1. - `src/index.ts` removes the no-arg `dotenv.config()` call that silently auto-loaded any CWD `.env`. Only `~/.perp/.env` loads now, eliminating a Rule #3 violation surface where stray dev keys polluted master-address resolution. - Tests: 1230 -> 1249 (+19 keystore tests). Build green. Live signed-read smoke on all 4 DEXs (HL/PAC/LT/Aster) confirms keystore-driven Lighter signer initializes end-to-end. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hiksang
added a commit
that referenced
this pull request
May 1, 2026
The four referral apply sites in src/index.ts had .catch blocks that wrote referralApplied[exchange]=true even when the venue rejected the call. This violated SSOT Rule #2 (no silent fallback) — failure became indistinguishable from success in settings.json, blocking retry and hiding venue rejection (e.g. HL first-write-wins when a different referrer was already registered, or LT auth/network failures). The .catch handlers now only log the failure to stderr; settings stays unmodified so the next adapter init retries. Reapply still gates on referralApplied=false, so successful applies still lock once. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hiksang
added a commit
that referenced
this pull request
May 1, 2026
… remediation When `wallet agent approve` failed because a venue rejected the request (e.g. HL "Must deposit before performing actions", Aster "No aster user found"), the locally-generated `agent-<dex>-<master>` OWS wallet was left orphaned in `~/.ows/wallets/`. Retry hit "wallet name already exists" and the prior remediation only printed the agent address, not the cleanup command. This commit auto-deletes the orphaned local wallet on pre-venue rejection (HL/PAC/Aster — Lighter uses a keystore, not OWS wallet, so skipped). Post-venue / persist failures still leave a status:"partial" record so the user can revoke the venue-side registration. The new remediation is actionable: "Local agent wallet cleaned up. Deposit funds and retry: perp wallet agent approve <dex> --master <master>". Agent address still appears in the message body for diagnostics. Per SSOT Rule #2, rollback failures do NOT mask the original error — failures are logged to stderr and the original error propagates to the caller. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hiksang
added a commit
that referenced
this pull request
May 1, 2026
Plan v3.0 documented "HMAC removed entirely in v0.12 — no soft-deprecation" but only the runtime/signing path was removed. The setup wizard, EXCHANGE_ENV_MAP, and `wallet set aster <key>` surfaces kept the legacy `chain: "apikey"` entry, so new users following the wizard still got prompted for ASTER_API_KEY/ASTER_API_SECRET — values that no longer drive any signing path. Confusing UX surfaced during v0.12.3 clean-state Docker QA. Removes: - `EXCHANGE_ENV_MAP.aster` entry (init.ts) - `EXCHANGE_PK_ENV_VARS.aster` entry (config.ts) - The setup wizard's Aster API key prompt (replaced with redirect to `wallet agent approve aster`) Adds: - `wallet set aster <key>` now throws INVALID_PARAMS with remediation pointing at `wallet agent approve aster`. Failing loudly per SSOT Rule #2. The new path: every Aster user must onboard via `perp wallet agent approve aster --master <wallet>`. Same as HL/PAC/LT. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hiksang
added a commit
that referenced
this pull request
May 1, 2026
catch removal Replaces the !this._dex heuristic in getBalance() with an explicit query to HL info type "userAbstraction". Returns one of: - "unifiedAccount" → "unified" (spot USDC = true equity) - "portfolioMargin" → "portfolio" (treated like unified for now) - "disabled" → "standard" (perp clearinghouse = truth) Discovered during v0.12.6 Docker QA: existing branch assumed every mainnet (non-HIP-3) user was unified. Standard-mode users (required for builder fee accrual) silently received unified accounting → wrong equity when their actual collateral lived on the perp side. The previous silent catch fallback (spot-fail → perp values) violated SSOT Rule #2 — masked the mode mismatch instead of failing loudly. Removed; venue-side fetch errors now propagate. New helper: - `HyperliquidAdapter._getAbstractionMode()` (cached via TTL_ACCOUNT) Cache key: `acct:hl:abstraction:<address>`. HIP-3 dex accounts skip the lookup since cross margin scopes per-DEX in standard semantics. SKILL.md auto-synced to 0.12.7 via prepublish hook. Refs: HL docs `trading/account-abstraction-modes.md` for the 4 modes and the userSetAbstraction action shape. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hiksang
added a commit
that referenced
this pull request
May 1, 2026
Two small UX/docs fixes from v0.12.7 clean-state Docker QA: (#11) `perp account balance` was renamed to `perp portfolio` in v0.12, but typing the old command returned "unknown command" with no pointer. Adds an explicit redirect subcommand that prints the correct path and exits 1 (per SSOT Rule #2: don't silently retarget — tell the user what to run). (#15) New `CHANGELOG.md` (Keep a Changelog format). Documents v0.12.0 through v0.12.7 plus orphan v0.12.4 tag. Future releases auto-link back via the bottom version table. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hiksang
added a commit
that referenced
this pull request
May 1, 2026
…bstraction) Adds CLI surface for setting Hyperliquid account abstraction mode: perp wallet manage account-mode <unified|standard|portfolio> [--master <w>] [--passphrase <pp>] [--json] perp wallet manage account-mode # no arg → show current mode Reuses the existing EIP-712 master-signing infra from runHlApproveFlow: same HyperliquidSignTransaction domain (chainId 42161), same OwsEvmSigner and signTypedData plumbing — only the type struct (UserSetAbstraction) and action payload differ. Mode strings map unified→unifiedAccount, standard→disabled, portfolio→portfolioMargin. Read side reuses HyperliquidAdapter._getAbstractionMode() (added v0.12.7); adds a tiny setAddress helper so the show branch can scope the read to a master OWS wallet without unlocking it. Per Rule #2: no fallbacks. Best-effort prev-mode capture is metadata-only and never masks the write outcome. Signing/parse/venue failures throw PerpError with structured remediation.
Hiksang
added a commit
that referenced
this pull request
May 1, 2026
…#2) aster.ts signed GET/DELETE used _handleResponse() which only checks HTTP status; only POST validated `json.code/msg` via _handleEip712Response(). Aster's API commonly returns HTTP 200 + {code, msg: "..."} for signing or auth failures, so getBalance() silently cached zero balances on errors — masking the underlying signature failure and violating SSOT Rule #2 (no silent fallback). Unify under _handleAsterResponse(res): validate HTTP status AND parse the JSON error envelope, throw structured PerpError with venue message + remediation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hiksang
added a commit
that referenced
this pull request
May 1, 2026
Aggregate fix from Codex independent review (artifact .omc/artifacts/ask/ codex-review-perp-cli-v0-12-0-v0-12-10-...md). 7 atomic commits: - 7a7c594 fix(aster): Tier 2/3 signer model — separate user vs signer per V3 spec - dd85a96 fix(aster): validate venue JSON error codes on signed GET/DELETE (Rule #2) - 5a3cdfb fix(hyperliquid): handle dexAbstraction mode + portfolio non-USDC awareness - e5fa570 fix(passphrase): wallet key create — apply optsWithGlobals (parent shadow) - d105f54 fix(setup): landing-page check — recognize ASTER_PRIVATE_KEY - 9593cd3 fix(aster): testnet chainId=714 support - 1a0a461 chore: drop stale env-key fallback comment in manage.ts Tests 1260 → 1281 (+21). Build green. The first two HIGH commits target the actual root cause of "Signature check failed" observed during v0.12.10 Docker QA — Aster query fields needed user/signer split + GET/DELETE response validator was bypassed silently for non-200 venue codes wrapped in HTTP 200. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hiksang
added a commit
that referenced
this pull request
May 1, 2026
…gning v0.12.11's user/signer split was correct syntax but didn't address the venue's actual rule: per Aster V3 spec, `signer` MUST be a registered API_WALLET (agent). Master self-signing (user==signer==master) is rejected with `code:-1000 msg:"Signature check failed"`, even though the EIP-712 envelope is well-formed. Reference: HypurrQuant_FE's AsterPerpAdapter (canonical reference at packages/core/defi/perp/adapters/AsterPerpAdapter.ts:773-775) throws if agent isn't configured; never attempts master self-signing. Tier 2 (OWS master) and Tier 3 (PK direct) now throw NOT_SUPPORTED with remediation pointing at `wallet agent approve aster`. Tier 1 (agent) unchanged. SSOT Rule #2 compliant: explicit failure beats silent venue rejection. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hiksang
added a commit
that referenced
this pull request
May 1, 2026
…rom v0.12.11) Codex re-review of v0.12.11 found that unifying GET/DELETE through _handleAsterResponse() also dropped the prior multi-attempt 429 retry loop — leaving signed reads single-shot under rate-limit pressure. Restored: up to 3 attempts with exponential backoff (2s/4s/8s) and a fresh _buildSignedQueryString per attempt (Aster reuses recent nonces strictly, so reusing a stale signature on retry would itself be rejected). Non-429 venue errors still throw immediately via _handleAsterResponse — no silent fallback (Rule #2). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hiksang
added a commit
that referenced
this pull request
May 1, 2026
The program-level .catch handler at the bottom of index.ts always
serialized errors as code:"FATAL" with no remediation, even when the
underlying error was a typed PerpError carrying explicit code and
remediation. v0.12.13's classifyError fix preserved typed errors only
through withJsonErrors wrappers; errors that propagated past withJsonErrors
to the program top-level still got downgraded.
Symptom (live in v0.12.14): `perp -e aster account positions --json`
without an agent emitted
{"code":"FATAL","message":"Aster requires a registered agent..."}
instead of the typed
{"code":"NOT_IMPLEMENTED","status":501,"retryable":false,
"remediation":"perp wallet agent approve aster --master <wallet>",
"message":"Aster requires a registered agent..."}
The top-level catch now branches on `err instanceof PerpError` and
forwards the structured payload through jsonError. Verified live in
Docker: NOT_IMPLEMENTED + remediation now appears as expected.
SSOT Rule #2: machine consumers receive the correct semantic code +
actionable remediation rather than a generic FATAL stub.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hiksang
added a commit
that referenced
this pull request
May 2, 2026
Codex v0.12.16 verdict flagged the asterAgentMissing helper's try/catch silenced settings-corrupt failures by returning false (= 'agent IS present'). That violated SSOT Rule #2 and could leave a real broken- settings user staring at a generic red dash with no clue. Drops the silent catch — listAgents() is a file read; if it throws, let the outer landing catch handle the message. Adds a unit test asserting 'agent required' renders only when settings actually shows no aster agent + adapter call failed, distinct from venue-down. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hiksang
added a commit
that referenced
this pull request
May 2, 2026
…r agent-required Rule #2) - account.ts: HIGH regression — twap-orders catch wrapped getAdapter() AND pac(), then unconditionally returned NOT_SUPPORTED + Pacifica remediation. Mislabeled unrelated failures (network, locked wallet, missing PK, typed PerpError). Now uses explicit hasPacificaSdk() guard so only the Pacifica-only assertion is rewritten; other errors propagate to the standard classifier. - landing.ts + index.ts: MED Rule #2 gap — agent-required hint now requires both (a) local Aster agent absent AND (b) errorCode in {NOT_IMPLEMENTED, NO_SIGNER_AVAILABLE, AGENT_EXPIRED}. Generic Aster outages no longer render 'agent required'. - landing.test.ts: +2 regression cases (network-error path, AGENT_EXPIRED/NO_SIGNER path). Tests 1305 → 1307. - bump 0.12.17 → 0.12.18 (package.json, marketplace.json, SKILL.md, CHANGELOG). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hiksang
added a commit
that referenced
this pull request
May 3, 2026
Adds new asset class: HL outcome markets (binary/range contracts, fully
collateralized, no leverage, no liquidation, USDH-quoted, $10 min order).
Currently 1 live market: BTC binary daily settling at 06:00 UTC.
New surface:
perp outcome list # active markets
perp outcome book <outcome> <side> [--depth N] # orderbook
perp outcome positions # holdings
perp outcome orders # open orders
perp outcome buy <outcome> <side> <usd> # IoC by default, --limit for GTC
perp outcome sell <outcome> <side> <usd>
perp outcome cancel <outcome> <side> <oid>
Key facts (verified end-to-end against mainnet):
- Asset id formula: 100,000,000 + (10 * outcome + side)
- Coin name: '#<enc>' (l2Book/candle/allMids), '+<enc>' (spot balance)
- Universe: POST /info {type:"outcomeMeta"}
- Positions: filter spotClearinghouseState for '+<enc>' coins
- Order/cancel actions identical to spot, only assetId differs
- Quote token = USDH (NOT USDC) — outcome trades draw from spot USDH balance
- Min notional: price * size >= 10 USDH
- Probe scripts: scripts/probe-outcome-{ws,order}.ts (manual mainnet verify)
Adapter (HyperliquidOutcomeAdapter) composes with HyperliquidAdapter for
signing. Bypasses HL's cached spot state on getPositions() to surface
fresh balances after fills (cache TTL would be stale).
CLI uses optsWithGlobals() so the parent program's --dry-run flag is not
shadowed by the subcommand's local options (commander v13 behavior).
Tests: +10 unit tests covering encoding, coin formatting, description
parsing. Total: 1307 → 1317.
SSOT compliance:
- Rule #2: unknown outcome/side throws SYMBOL_NOT_FOUND/INVALID_PARAMS;
notional below $10 throws INVALID_PARAMS with remediation.
- Rule #3: no new env vars, reuses existing HL agent.
Out of scope for this commit (deferred):
- Portfolio aggregation of outcome holdings
- Landing page outcome line
- close subcommand (use sell with --limit + notional for now)
- HIP-4 builder/deployer mechanics
Plan reference: .omc/plans/v0.13.0-outcome.md (local).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hiksang
added a commit
that referenced
this pull request
May 3, 2026
Codex re-review #2 found one MED regression in the new USDH pre-check. HyperliquidAdapter._address is empty in agent-only setups (no master pk in OWS) because setAgentSigner does not propagate the address. The pre-check returned 0 USDH in that case, producing a false-positive INSUFFICIENT_BALANCE that blocked legitimate buys. Now both the pre-check and getPositions resolve the user address via _resolveUserAddress: prefer _hl.address, fall back to the agent meta's userEvmAddress in the OWS-stored agent registry, and only throw NO_SIGNER_AVAILABLE if neither is available. No silent zero substitution (Rule #2). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
trade scale-tp): 분할익절 — place multiple reduce-only limit orders at different price levelsalert add -t pnl --loss 50/-t liquidation --margin-pct 20opts.reduceOnlysupport to all 3 exchange adaptersTest plan
trade scale-tp --helpshows correct usage--dry-run trade scale-tp BTC --levels "72000:25,75000:25,80000:50"worksalert add --helpshows new pnl/liquidation options🤖 Generated with Claude Code