Repository navigation
Surface parity for 1.12.0: webhooks, CVD, HIP-3 oracle, HIP-4 questions, wallet classification, core breadth, symbols - #18
Merged
Conversation
A trades cursor is an opaque string, "<ms>_<tid>" on Hyperliquid, HIP-3, HIP-4 and spot and "<ms>_<tid>_<crossed>" on Lighter. trades.list() and alist() converted it as a timestamp, so asking for the second page raised ValueError (older releases sent a different number). The cursor now goes back exactly as returned; integer and datetime cursors are still accepted. CoinFreshness.funding is optional. The API leaves it out on HIP-4, which has no funding, so client.hyperliquid.hip4.get_freshness() failed validation on every response.
Folds in the docs from the order-flow paging branch. The API returns next_cursor on order flow once this release is published. - The flow() docstring says a page holds the oldest limit buckets and how to follow next_cursor with the same start, end and interval. - The README order-flow example pages a full day at 1m. - CHANGELOG: order flow pages with next_cursor. No code change: flow() and aflow() already send cursor on Hyperliquid, HIP-3 and HIP-4.
orderbook_full (Hyperliquid core) and hip3_orderbook_full (HIP-3) stream every price level of the book. A subscription opens with an l4_snapshot of the whole book, then l4_batch messages of changed levels; a level with sz 0 has been removed. Both reach on_l4_snapshot() and on_l4_batch(). WsChannel lists both channels. Without them the subscribe acknowledgement, snapshot and batch messages failed to parse. New helpers subscribe_orderbook_full(), subscribe_hip3_orderbook_full() and their unsubscribe_* counterparts, and FULL_DEPTH_L2_CHANNELS. The server currently answers a replay request on these channels with an error, so the docs point stored full-depth history at the REST l2_orderbook.history() and diffs() routes. Replay is not blocked client side.
client.webhooks covers all 21 operations under /v1/webhooks, sync and async: the event catalog and plan limits; endpoints (list, create, delete, enable, rotate secret, test); the delivery log and redelivery; subscriptions (list, create, update, delete, resume one, resume all); the dry-run and estimate previews; and watched addresses (list, add, delete). Ported from the earlier webhooks branch and reconciled with the current API contract: limits(), resume_subscription() and resume_all_subscriptions() are new; subscriptions carry their pause state; a resume returns the missed window; the catalog's cost floor is an object; deliveries can be "failed"; create returns WebhookEndpointCreated with the secret, and the envelope notes on create, rotate and redeliver are kept on the models. A configuration is passed as config, a dict or a WebhookSubscriptionConfig. oxarchive/webhook_signature.py verifies a delivery. It takes the raw request bytes rather than a parsed object, because the signature covers the exact bytes sent. It collects every v1 in the header, so a receiver holding either secret keeps verifying through a rotation, enforces a replay window (5 minutes by default), and compares in constant time. Write operations are tested against a mocked transport only.
…assification and symbols - client.hyperliquid.cvd and client.hyperliquid.hip3.cvd: history() returns one page of CvdBucket with next_cursor, passed back unchanged with the same start, end and interval; iterate() follows the cursor and stops on the cursor rather than a short page. Interval and limit are checked against the API's accepted values before sending. - client.hyperliquid.hip3.oracle: external_price() and discovery_bounds(). - client.hyperliquid.hip4.questions: list() (question id cursor) and get(), plus the flat helpers list_questions() and get_question(). - client.hyperliquid.wallets and client.hyperliquid.hip3.wallets: classify() with every filter, sort, limit and offset. date takes a date, a string or a datetime; a datetime without a time zone is UTC. - client.symbols.list(): the public symbol universe, one SymbolEntry per market. Every method has an a-prefixed async version. Models follow the API contract and were checked against live responses.
orderbook_full and hip3_orderbook_full are live-only: the API refuses a replay request on them. replay() and multi_replay() now raise ValueError with FULL_DEPTH_LIVE_ONLY_ERROR before anything is sent, as they already do for the live-only L4 channels, and the docs point stored full-depth history at the REST l2_orderbook.history() and diffs() routes.
client.hyperliquid.breadth serves /v1/hyperliquid/breadth/above-vwap and /current with the same BreadthResource as HIP-3 breadth: current(), history() and their async versions. Core history begins 2026-08-24, and core responses carry empty namespaces maps. Validation messages name the venue. The history() docstring now says start defaults to 24 hours before now, as the API applies it, not 24 hours before end. Also adds a test that the HIP-4 outcomes settlement filter is sent as is_settled.
Removed methods whose routes the API does not serve (each call was a 404): hip4.l2_orderbook, hip4.orders.trigger_levels and trigger_levels_history, spot.orders.flow, tpsl, trigger_levels and trigger_levels_history, and hip3.liquidations.by_user, with their async versions. The shared resources split accordingly: Hip4OrdersResource, SpotOrdersResource and Hip3LiquidationsResource. A scan of every SDK method against the API contract finds no other route it does not serve. Removed parameters the API never applied, each confirmed against live responses: side on trades.list(); user, status and order_type on spot order history; depth on the L2, L4 and L3 history() methods. Passing one raises TypeError before sending, since the methods keep **kwargs for the deprecated coin keyword and would otherwise drop it silently. data_quality.symbol_coverage() sends the symbol as given, URL-encoded, so HIP-3 symbols keep their case and HIP-4 "#N" symbols reach the route. SpotTableFreshness has the shape the API returns (a DataTypeFreshness per dataset); tables is now a property listing them. lighter.l3_orderbook.get() takes account; history() takes granularity and account.
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.
Brings the SDK to parity with the customer-facing API for the 1.12.0 release. Everything is recorded in the existing 1.12.0 CHANGELOG section; the version is unchanged.
Added
client.webhookscovers all 21 operations under/v1/webhooks, sync and async. That is the event catalog and plan limits; endpoints (list, create, delete, enable, rotate the secret, send a test); the delivery log and redelivery; subscriptions (list, create, update, delete, resume one, resume all); the dry-run and estimate previews; and watched addresses. The code comes from the earlier webhooks branch (Add the webhooks resource and a signature verifier #9, closed when history was rewritten), updated to the current API:limits()and the two resume routes are new, subscriptions report their pause state, and a resume returns the window that was missed.oxarchive/webhook_signature.py(WebhookVerifier,verify_webhook()and related helpers):v1=signature in the header, so a delivery signed during a secret rotation verifies under either secret.cvd.history()returns one page withnext_cursor; pass it back unchanged.cvd.iterate()follows the cursor for you.external_price()anddiscovery_bounds().questions.list()andquestions.get(), plus flat helpers.wallets.classify(), with every filter, sort and offset-based paging.client.symbols.list().client.hyperliquid.breadth, the same resource as HIP-3 breadth.orderbook_fullandhip3_orderbook_full, with subscribe helpers. They also go intoWsChannel; before, their messages failed to parse. Both channels are live-only, soreplay()andmulti_replay()reject them before sending, as they do for the live-only L4 channels.flow()docstring now page withnext_cursor.flow()andaflow()already sendcursoron Hyperliquid, HIP-3 and HIP-4, and new tests cover that.Removed
Methods that called routes the API does not serve, so every call returned 404 (each route checked on the live API):
hyperliquid.hip4.l2_orderbook:get,historyanddiffs.hyperliquid.hip4.orders.trigger_levelsandtrigger_levels_history.spot.orders.flow,tpsl,trigger_levelsandtrigger_levels_history.hyperliquid.hip3.liquidations.by_user.Their async versions go too. The shared resources are split so each venue exposes only what it serves:
Hip4OrdersResource,SpotOrdersResourceandHip3LiquidationsResource. A scan of every SDK method against the API contract finds no other unserved route.Changed
Parameters the API never applied are removed; each was confirmed live to have no effect. Passing one raises
TypeErrorbefore sending, so the call fails instead of returning unfiltered data as if the filter had worked:sideontrades.list().user,statusandorder_typeonspot.orders.history(). Core, HIP-3 and HIP-4 order history keep them.depthon thehistory()methods ofl2_orderbook,l4_orderbookand the Lighterl3_orderbook. It still applies toget().lighter.l3_orderbook.get()now takesaccount, andhistory()takesgranularityandaccount.Fixed
These existing methods broke on live responses:
trades.list()andalist()converted the cursor as if it were a timestamp. A trades cursor is an opaque string ("1790640000578_218303497631402"), so asking for the second page raisedValueError. The cursor is now sent back exactly as returned.CoinFreshness.fundingis now optional. HIP-4 has no funding, sohip4.get_freshness()failed validation on every response.data_quality.symbol_coverage()upper-cased the symbol, turning HIP-3km:US500into a symbol with no data. It also left the HIP-4#unencoded, which cut the path short. The symbol is now sent as given, URL-encoded.SpotTableFreshnessexpected atablesfield the API never sends, so it was always empty. It now has the dataset fields the API returns, andtableslists them.Notes
orderbook_fullandhip3_orderbook_full, but the API refuses it. The SDK treats both channels as live-only and points stored full-depth history at the RESTl2_orderbookroutes.Checks
pytest: 354 passed, 181 of them new.mypy --strictstill fails on master (325 errors). This branch adds none (304 total), and the new modules type-check clean.ruff: no new findings.python -m build: passes. The package-contents check passes with planted stray files, and onlyoxarchive/ships.