Skip to content

feat(perps): expose market tradability for unavailable HIP-3 markets#9205

Merged
abretonc7s merged 8 commits into
mainfrom
TAT-3039-feat-block-unavailable-hip3-trading
Jun 23, 2026
Merged

feat(perps): expose market tradability for unavailable HIP-3 markets#9205
abretonc7s merged 8 commits into
mainfrom
TAT-3039-feat-block-unavailable-hip3-trading

Conversation

@abretonc7s

@abretonc7s abretonc7s commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Explanation

HIP-3 (builder-deployed) markets can become temporarily untradable when their market (mid) price
drifts too far from the oracle (reference) price. HyperLiquid then rejects every order with
Order price cannot be more than 95% away from the reference price (observed in Mixpanel). Today
nothing in @metamask/perps-controller surfaces this, so the user only finds out after an order
fails on submission.

This PR computes per-market trading availability in Core and exposes it so clients can warn the
user proactively (banner reusing the existing "OI cap" design — the banner UI itself lands in the
mobile/extension clients):

  • Add an optional isTradable boolean to PriceUpdate, set on the existing price subscription.
    It is false when a market's mid price has drifted past the protocol's deviation limit;
    undefined means tradability is unknown and the market should be treated as tradable. It is
    computed per provider from that protocol's own rules, so clients never reimplement
    HyperLiquid-specific logic (important once other providers join the aggregated market list).
  • Export a pure isMarketTradable({ midPrice, oraclePrice, deviationLimit }) helper. Missing or
    non-positive prices return true, so a transient "no data" tick never produces a false banner.
  • Add HYPERLIQUID_CONFIG.OraclePriceDeviationLimit (0.95) as the HyperLiquid default.
  • Add a protocol-agnostic fallbackPriceDeviationLimit to PerpsControllerConfig so a client can
    tune the threshold without a package release. It is threaded through the controller → provider →
    subscription service; the controller stays protocol-agnostic (passes the optional value through,
    each provider applies its own default when omitted).

Why a streamed flag rather than only a util or a new subscription: clients already subscribe to the
price stream and already receive both inputs (price = mid, markPrice = oracle). A derived flag
fully insulates clients from the rule and inputs and mirrors the OI-cap banner, which is likewise
driven by a live subscription.

All changes are additive and optional, so this is non-breaking for Mobile and Extension consumers.

Validation: new unit tests for the helper (boundary at 95%, both deviation directions, custom
limit, missing/zero/NaN guards) and service tests covering tradable / untradable (>95%) / an
injected custom limit; targeted suites for the subscription service, provider, and controller pass;
yarn eslint and changelog:validate pass; the full monorepo build and the standalone
perps-controller build both succeed.

References

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them — N/A, all changes are additive/optional (non-breaking)

Note

Medium Risk
Touches live price subscription data used for trading UX; the new required PriceUpdate.isTradable field may require consumer/test updates even though semantics default to tradable when uncertain.

Overview
Adds isTradable to streamed PriceUpdate payloads so mobile/extension can warn before submit when HyperLiquid would reject an order (mid vs oracle deviation beyond the protocol limit, often on HIP-3 markets).

HyperLiquid price updates now set isTradable via exported isMarketTradable and HYPERLIQUID_CONFIG.OraclePriceDeviationLimit (0.95); missing or invalid prices stay tradable. Optional fallbackPriceDeviationLimit on perps client config is passed controller → HyperLiquidProviderHyperLiquidSubscriptionService. MYX tickers always emit isTradable: true. Unit/service tests cover boundaries and custom limits.

Reviewed by Cursor Bugbot for commit 584542c. Bugbot is set up for automated code reviews on this repo. Configure here.

Add PriceUpdate.isTradable, computed per provider from the market's
mid-vs-oracle price deviation, so clients can warn before placing an
order the protocol would reject (HyperLiquid rejects orders >95% from
the reference price, which most often affects HIP-3 markets).

- Export pure isMarketTradable helper; missing/non-positive prices
  default to tradable so transient no-data ticks never raise a banner.
- Add HYPERLIQUID_CONFIG.OraclePriceDeviationLimit (0.95) HL default.
- Add protocol-agnostic PerpsControllerConfig.fallbackPriceDeviationLimit
  client override, threaded controller -> provider -> subscription service.
@abretonc7s

Copy link
Copy Markdown
Contributor Author

Automated dev run — TAT-3039

Metric Value
Run 0a23206b
Duration ?
Model claude/opus
Nudges 0
Session 22 turns · in=43,943 · out=14,389 · cache-read=865,299 · cache-write=153,911
Worker report

Report — TAT-3039: Block trading on unavailable HIP-3 markets

Ticket: https://consensyssoftware.atlassian.net/browse/TAT-3039
Branch: TAT-3039-feat-block-unavailable-hip3-trading
Draft PR: #9205
Package: @metamask/perps-controller

Problem

HIP-3 (builder-deployed) markets can become temporarily untradable when their market (mid)
price drifts too far from the oracle (reference) price. HyperLiquid then rejects every order
with Order price cannot be more than 95% away from the reference price (observed in Mixpanel).
Users only discover this after a failed order. Core needs to expose a signal so clients can
proactively warn the user (banner reusing the "OI cap" design). The banner UI lives in
mobile/extension; this Core change provides the data.

Solution

Compute tradability in Core and stream it as a boolean on the existing price subscription:

  • PriceUpdate.isTradable?: booleanfalse when a market's mid price has drifted past the
    protocol's deviation limit; undefined means unknown → treat as tradable. Computed per
    provider from that protocol's own rules (encapsulation: clients never reimplement HyperLiquid's
    95% rule, which matters once MYX / other providers join the aggregated market list).
  • isMarketTradable({ midPrice, oraclePrice, deviationLimit }) — exported pure helper.
    Non-positive / missing prices return true so transient "no data" never produces a false banner.
  • HYPERLIQUID_CONFIG.OraclePriceDeviationLimit = 0.95 — the HyperLiquid default.
  • PerpsControllerConfig.fallbackPriceDeviationLimit?: number — protocol-agnostic client
    override, threaded controller → provider → subscription service. The controller stays
    protocol-agnostic (passes the optional value through; each provider applies its own default).

Why a streamed flag (not just a util, not a new subscription)

Clients already subscribe to the price stream and already receive both inputs (price = mid,
markPrice = oracle). A derived flag fully insulates clients from the rule and the inputs, and
mirrors the existing OI-cap banner, which is also driven by a live subscription. A bare util would
leak the wiring; a dedicated subscription would be redundant and heavier.

Files changed (9 files, +244/−1)

File Change
src/types/index.ts PriceUpdate.isTradable; PerpsControllerConfig.fallbackPriceDeviationLimit
src/utils/marketDataTransform.ts isMarketTradable helper
src/constants/hyperLiquidConfig.ts OraclePriceDeviationLimit = 0.95
src/services/HyperLiquidSubscriptionService.ts compute isTradable in #createPriceUpdate; injected #priceDeviationLimit
src/providers/HyperLiquidProvider.ts priceDeviationLimit? option → service (HL default)
src/PerpsController.ts pass clientConfig.fallbackPriceDeviationLimit to provider
src/index.ts export isMarketTradable
CHANGELOG.md Added entry
tests/src/utils/marketDataTransform.test.ts (new) 14 util cases
tests/src/services/HyperLiquidSubscriptionService.market-data.test.ts 3 service cases

Validation (all green)

  • marketDataTransform.test.ts — 14/14 (boundary at 95%, both directions, custom limit,
    missing/zero/NaN guards)
  • HyperLiquidSubscriptionService.market-data.test.ts — 49/49, incl. tradable / untradable >95% /
    honors injected limit
  • subscription lifecycle + streams + cache — 75 pass
  • provider lifecycle/standalone + controller configuration/lifecycle — 112 pass
  • yarn eslint clean on all 9 files (fixed one prefer-readonly)
  • changelog:validate — pass
  • Full monorepo build and standalone perps-controller build — both succeed

Build note: the first standalone/closure builds failed with TS6305/cascade errors because the
monorepo's dependency dist/ folders had never been built (plus a pre-existing dependency cycle).
None referenced the changed files. After a full yarn build, the standalone build is green.

Remaining risks / follow-ups

  • isTradable is additive + optional on the public PriceUpdate type → non-breaking for
    Mobile/Extension.
  • Heuristic: mid-vs-oracle deviation is not identical to HyperLiquid's exact order-price-vs-reference
    rule, so extreme limit-order edge cases differ. Real order rejection still guards on submit.
  • No remote-flag runtime tuning — only static clientConfig override. Deliberate;
    #priceDeviationLimit is an easy future hook if ops-level tuning is wanted.
  • Client follow-up (out of Core scope): Mobile + Extension consume PriceUpdate.isTradable to
    render the OI-cap-style "trading unavailable" banner; copy/design pending @Varada Stern.

@abretonc7s abretonc7s marked this pull request as ready for review June 19, 2026 13:34
@abretonc7s abretonc7s requested review from a team as code owners June 19, 2026 13:34
@abretonc7s abretonc7s enabled auto-merge June 19, 2026 16:43
Comment thread packages/perps-controller/src/types/index.ts Outdated
@michalconsensys

Copy link
Copy Markdown
Contributor

There are conflicts in the changelog file

…navailable-hip3-trading

# Conflicts:
#	packages/perps-controller/CHANGELOG.md
@abretonc7s

Copy link
Copy Markdown
Contributor Author

Resolved in 507de24fa — merged latest main and fixed the packages/perps-controller/CHANGELOG.md conflict, keeping both Unreleased entries in the correct order (### Added for #9205, then ### Changed for the @metamask/controller-utils bump). yarn changelog:validate passes.

Address review feedback (#9205): instead of leaving isTradable undefined
when tradability is unknown, the field is now a required boolean that
defaults to true. Providers report false only when they determine a market
is currently untradable; MYX, which has no oracle-deviation rule, always
reports true. Clients no longer need to coerce undefined.
…navailable-hip3-trading

# Conflicts:
#	packages/perps-controller/CHANGELOG.md
@abretonc7s abretonc7s added this pull request to the merge queue Jun 23, 2026
Merged via the queue into main with commit b6b6913 Jun 23, 2026
392 checks passed
@abretonc7s abretonc7s deleted the TAT-3039-feat-block-unavailable-hip3-trading branch June 23, 2026 08:14
@abretonc7s abretonc7s mentioned this pull request Jun 23, 2026
4 tasks
pull Bot pushed a commit to dmrazzy/core that referenced this pull request Jun 23, 2026
## Explanation

Since release **1062.0.0**, perps feature/fix PRs landed on `main`
without a corresponding publish. This release bumps the root monorepo to
**1063.0.0** and publishes **only** `@metamask/perps-controller@8.3.0`.

**`@metamask/perps-controller@8.3.0`** (minor)

- Terminal API integration for market metadata with HyperLiquid fallback
(MetaMask#9137)
- `isTradable` flag on `PriceUpdate` for HIP-3 oracle-deviation markets
(MetaMask#9205)
- Terminal API fetch timeout and display-name enrichment guard (MetaMask#9224)

Validation: perps changelog validation passed. No build required
(version/changelog-only changes).

**Client follow-up:** Mobile / Extension should bump
`@metamask/perps-controller` to `^8.3.0`.

## References

- https://consensyssoftware.atlassian.net/browse/TAT-3392
- MetaMask#9137 — Terminal API integration (perps-controller)
- MetaMask#9205 — Market tradability flag (perps-controller)
- MetaMask#9224 — Terminal API timeout/name fixes (perps-controller)

## Checklist

- [ ] I've updated the test suite for new or updated code as appropriate
— N/A: release-only version/changelog bumps
- [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or
updated code as appropriate — N/A: no new code in this PR
- [x] I've communicated my changes to consumers by [updating changelogs
for packages I've
changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md)
- [ ] I've introduced [breaking
changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md)
in this PR and have prepared draft pull requests for clients and
consumer packages to resolve them — No breaking changes in this release

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Diff is limited to version numbers and changelog documentation; no
runtime code changes in this PR.
> 
> **Overview**
> **Release 1063.0.0** — bumps the root monorepo version from `1062.0.0`
to `1063.0.0` and publishes **`@metamask/perps-controller` `8.3.0`**
(package version `8.2.0` → `8.3.0`).
> 
> The changelog documents what ships in **8.3.0**: optional **Terminal
API** market metadata (`useTerminalApi`, `TerminalMarketService`,
enriched `PerpsMarketData` and search), **`isTradable`** on price
updates with configurable oracle-deviation limits,
**`@metamask/controller-utils` ^12.3.0**, and fixes (10s Terminal fetch
timeout, safer display-name merge). No application source changes appear
in this diff—only version and changelog updates.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
c37dac3. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants