Skip to content

Webhooks, CVD, HIP-3 oracle, HIP-4 questions and wallet classification - #18

Merged
0xFantomMenace merged 3 commits into
mainfrom
feat/surface-parity
Sep 29, 2026
Merged

0xFantomMenace merged 3 commits into
mainfrom
feat/surface-parity

Conversation

@0xFantomMenace

@0xFantomMenace 0xFantomMenace commented Sep 29, 2026 •

Copy link
Copy Markdown
Member

Brings the crate to parity with the published API for 1.12.0. The release is not published yet, so everything is recorded under 1.12.0 in the changelog and the version is unchanged.

Added

Webhooks (client.webhooks, all 21 routes under /v1/webhooks)

Method Route
event_types() GET /v1/webhooks/event-types
limits() GET /v1/webhooks/limits
list_endpoints() GET /v1/webhooks/endpoints
create_endpoint(params) POST /v1/webhooks/endpoints
delete_endpoint(id) DELETE /v1/webhooks/endpoints/{id}
enable_endpoint(id) POST /v1/webhooks/endpoints/{id}/enable
rotate_secret(id) POST /v1/webhooks/endpoints/{id}/rotate
test_endpoint(id) POST /v1/webhooks/endpoints/{id}/test
list_deliveries(id, limit) GET /v1/webhooks/endpoints/{id}/deliveries
redeliver(id) POST /v1/webhooks/deliveries/{id}/redeliver
list_subscriptions() GET /v1/webhooks/subscriptions
create_subscription(params) POST /v1/webhooks/subscriptions
update_subscription(id, params) PATCH /v1/webhooks/subscriptions/{id}
delete_subscription(id) DELETE /v1/webhooks/subscriptions/{id}
resume_subscription(id) POST /v1/webhooks/subscriptions/{id}/resume
resume_all_subscriptions() POST /v1/webhooks/subscriptions/resume
dry_run(params) POST /v1/webhooks/subscriptions/dry-run
estimate(params) POST /v1/webhooks/subscriptions/estimate
list_addresses() GET /v1/webhooks/addresses
add_address(address, label) POST /v1/webhooks/addresses
delete_address(id) DELETE /v1/webhooks/addresses/{id}

Responses are typed from the spec, including subscription pause state and the window a resume closed. Members next to data (the secret note, the watched wallet cap, the resume gap and count) are kept. WebhookSubscriptionConfig and WebhookSubscriptionCondition build a rule's config.

Signature verifier (webhook_signature::WebhookVerifier): takes the raw body bytes, tries every v1 in the header against every secret held (two during a rotation), enforces a replay window (300 seconds by default), and compares in constant time. Debug never prints a secret. Ported from the earlier webhooks branch and reconciled with the current spec.

Market data

Method Route
hyperliquid.cvd(symbol, params) GET /v1/hyperliquid/cvd/{symbol}
hyperliquid.hip3.cvd(symbol, params) GET /v1/hyperliquid/hip3/cvd/{symbol}
hyperliquid.hip3.oracle.external_price(symbol) GET /v1/hyperliquid/hip3/oracle/external-price/{symbol}
hyperliquid.hip3.oracle.discovery_bounds(symbol) GET /v1/hyperliquid/hip3/oracle/discovery-bounds/{symbol}
hyperliquid.hip4.list_questions(params) GET /v1/hyperliquid/hip4/questions
hyperliquid.hip4.get_question(id) GET /v1/hyperliquid/hip4/questions/{question_id}
hyperliquid.wallets.classify(params) GET /v1/hyperliquid/wallets/classify
hyperliquid.hip3.wallets.classify(params) GET /v1/hyperliquid/hip3/wallets/classify
hyperliquid.breadth.current() / history(params) GET /v1/hyperliquid/breadth/above-vwap[/current]
symbols() GET /v1/symbols

CVD is cursor-paged and returns a MetaResponse, so the notice on a response that is one page of several is visible. Core breadth reuses the existing breadth resource; BreadthSnapshot is a venue-neutral alias of Hip3BreadthSnapshot. symbols() reads the unwrapped {"symbols": [...]} body into SymbolEntry rows.

Reconciled with the live API

Removed (they called routes the API does not serve and always failed): hyperliquid.hip3.liquidations.by_user(), and flow(), tpsl(), trigger_levels() and trigger_levels_history() on hyperliquid.spot.orders. HIP-3 liquidations are now a Hip3LiquidationsResource (history, volume, levels, levels history), and Spot orders a SpotOrdersResource with history() only. Core liquidations.by_user() stays and keeps sending coin, which is the filter the API honours.

Parameters the API ignores, dropped (each checked live): side on trade history for every venue, depth on L2, L4 and HIP-4 L4 history, and user, status and order_type on Spot order history (SpotOrderHistoryParams).

Parameters the API honours, added (each checked live): timestamp and account on the L3 order book (l3_orderbook.get_with_params()), account on L3 history, status, exchange, since, limit and offset on incidents (data_quality.list_incidents_with(), with the pagination totals), and from and to on symbol coverage (data_quality.symbol_coverage_with()).

Fixed: data-quality coverage percent-encodes its path segments and keeps the symbol's case, so km:US500, HYPE-USDC and HIP-4 #0 reach the right route.

Documentation

  • Order flow: the params, method docs and README page with next_cursor (folds in HELD until the API returns next_cursor on order flow: document order-flow paging #14). Core, HIP-3 and HIP-4 order flow already forward cursor.
  • WebSocket: the channel table lists orderbook_full and hip3_orderbook_full as live-only. replay() and replay_multi() now refuse them before sending (LIVE_ONLY_FULL_DEPTH_CHANNELS, is_live_only_full_depth_channel(), FULL_DEPTH_REPLAY_ERROR), next to the existing live-only L4 check. Their frames decode as ServerMsg::L4Snapshot and ServerMsg::L4Batch, now documented on those variants.
  • README sections for webhooks (design and size a rule, endpoints and subscriptions, pauses, verifying deliveries), CVD, the HIP-3 oracle, HIP-4 questions, wallet classification and core breadth.

Dependencies

hmac 0.12 and sha2 0.10, used only by the verifier. Tests pass on Rust 1.85.

Test plan

  • cargo test --all-features: 154 passed (59 unit, 16 coverage contract, 18 positions, 29 new surface tests, 19 WebSocket, 13 doc).
  • cargo test (default features): pass.
  • cargo +1.85.0 test --all-features: pass.
  • cargo clippy --all-targets with and without --all-features: no warnings.
  • RUSTDOCFLAGS="-D warnings" cargo doc --all-features --no-deps: clean.
  • cargo package --list: only the five new src/ files are added to the crate.
  • Webhook routes are tested against mocks only. The read routes (CVD, oracle, questions, classify, symbols, core breadth, event types, limits, list endpoints) were checked against the live API.

client.webhooks covers the 21 routes under /v1/webhooks: the event catalog,
plan limits, endpoints (create, delete, enable, rotate, test), the delivery
log and redelivery, subscriptions (create, update, delete, resume, resume
all), the estimate and dry-run previews, and watched wallets. Responses are
typed from the published spec, including subscription pause state and the
window a resume closed. Siblings of `data` (the secret note, the watched
wallet cap, the resume gap) are kept.

webhook_signature::WebhookVerifier verifies a delivery from the raw body
bytes: HMAC-SHA256 over `<t>.<body>`, every `v1` in the header tried against
every secret held (two during a rotation), a replay window of 300 seconds by
default, and a constant-time comparison. Its Debug output never prints a
secret.

Also added:
- client.hyperliquid.cvd() and client.hyperliquid.hip3.cvd(), cursor-paged,
  returning the full meta so the one-page-of-several notice is visible.
- client.hyperliquid.hip3.oracle: external_price() and discovery_bounds().
- client.hyperliquid.hip4.list_questions() and get_question().
- client.hyperliquid.wallets.classify() and the HIP-3 equivalent.
- client.hyperliquid.breadth for core perps, on the existing breadth
  resource.

Order flow: the params and README document paging with next_cursor, which
the API returns once this release is out. The WebSocket channel table lists
orderbook_full and hip3_orderbook_full.

hmac and sha2 are new dependencies, used only by the verifier. All of it is
recorded under 1.12.0 in the changelog.
orderbook_full and hip3_orderbook_full have no replay path, so replay() and
replay_multi() now refuse them before sending, with
LIVE_ONLY_FULL_DEPTH_CHANNELS, is_live_only_full_depth_channel() and
FULL_DEPTH_REPLAY_ERROR alongside the existing live-only L4 checks. The
README channel table marks them live-only and points at the l2_orderbook
REST resource for history.

client.symbols() reads GET /v1/symbols, which answers {"symbols": [...]}
without a data wrapper, into SymbolEntry rows.
Removed, because they called routes the API does not serve and always
failed: hip3.liquidations.by_user(), and flow(), tpsl(), trigger_levels()
and trigger_levels_history() on spot.orders. HIP-3 liquidations get their
own resource type without by_user; Spot orders get one with history only.

Parameters the API ignores are no longer offered: side on trade history
(core, Spot, HIP-3, Lighter and HIP-4), depth on L2 and L4 history, and
user, status and order_type on Spot order history.

Parameters the API honours are now sent: account and timestamp on the L3
order book (get_with_params) and account on L3 history; status, exchange,
since, limit and offset on incidents (list_incidents_with, with the
pagination totals); from and to on symbol coverage (symbol_coverage_with).

Data-quality coverage percent-encodes its path segments and keeps the
symbol's case, so km:US500 and a HIP-4 #0 reach the right route.
@0xFantomMenace
0xFantomMenace merged commit cea6b9f into main Sep 29, 2026
1 check passed
@0xFantomMenace
0xFantomMenace deleted the feat/surface-parity branch September 29, 2026 03:32
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.

1 participant