Skip to content

fix(hypercore): accept null venue payloads in predicted_fundings - #83

Merged
ifdario merged 1 commit into
infinitefield:mainfrom
mergt:fix/predicted-fundings-nullable-venue
Aug 31, 2026
Merged

ifdario merged 1 commit into
infinitefield:mainfrom
mergt:fix/predicted-fundings-nullable-venue

Conversation

@mergt

@mergt mergt commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Fix predicted_fundings deserialization failure on unlisted coins

HttpClient::predicted_fundings fails on every call against mainnet. The endpoint is currently unusable.

Error: [predicted_fundings] body=[["0G",[["BinPerp",{"fundingRate":"0.00005", ...
Caused by: invalid type: null, expected struct PredictedFundingVenue at line 1 column 1998

Cause

The exchange sends null for a venue's payload when the coin is not listed on that venue. predicted_fundings models the payload as a bare PredictedFundingVenue, so the first null aborts the whole response.

The first one lands at byte 1995 — coin AI, venue BybitPerp, the 4th of 232 coins:

["AI", [
  ["BinPerp",   {"fundingRate":"0.0", "nextFundingTime":1788091200000}],
  ["HlPerp",    {"fundingRate":"0.0", "nextFundingTime":1788087600000, "fundingIntervalHours":1}],
  ["BybitPerp", null]
]]

Nulls are not an edge case. Measured against live mainnet:

Coins 232, exactly 3 venues each (696 venue slots)
Null payloads 69BybitPerp 45, BinPerp 24. HlPerp never null
fundingIntervalHours present on 608/627 non-null payloads; values 1, 4, 8

Changes

  • Option<PredictedFundingVenue> per venue in the return type. None means the coin is not listed there — real information, and distinct from a zero funding rate. A consumer that collapses the two invents a funding spread against the venues that did report one, so the distinction is worth the signature change.
  • PredictedFundingVenue::funding_interval_hours: Option<u32> — sent by the exchange, absent from the struct. Option because 19 of 627 payloads omit it, with skip_serializing_if per CLAUDE.md. (This is a response type, never signed, so it is unrelated to the ActionRequest signing concern in c395da8.)
  • Coverage in test_http_undocumented_typed_responses, which is the test CLAUDE.md names for exactly this failure mode but which never included this endpoint. Asserting a null exists means reverting the Option is a compile error, not a silent regression:
    error[E0599]: no method named `is_none` found for reference `&PredictedFundingVenue`
    
  • Doc comment and CHANGELOG entries.

Why it shipped broken

The struct arrived in 4757e9d, "align SDK with Hyperliquid API docs" — modelled from documentation, which shows the happy-path shape and does not mention that a venue payload is nullable. The only existing test asserts the request serializes to {"type":"predictedFundings"}; nothing checked the response. This is the gap CLAUDE.md already describes:

A serialization test proves nothing about the endpoint existing... the live audits parse responses as serde_json::Value, so they prove an endpoint answers, not that a modelled response type still matches.

Breaking

The signature change breaks callers that destructure the venue tuple. Scope is small: 3 references in the lib, none in hypecli or examples/, so no cd hypecli && cargo check fallout. Warrants 0.3.0. No version bump in this branch — left to cargo release.

Verification

  • cargo fmt — clean
  • cargo clippy -p hypersdk --all-targets — no new warnings (the 4 too many arguments are pre-existing)
  • cargo test --lib158 passed, 0 failed
  • Live mainnet after the fix: 232 coins, 696 venue slots, 69 nulls, 608 with fundingIntervalHours

Note: the new assertion makes test_http_undocumented_typed_responses hit live mainnet for one more endpoint, consistent with the nine already there.

The exchange sends null for a venue's payload when the coin is not listed
there, which aborts deserialization of the whole response:

    invalid type: null, expected struct PredictedFundingVenue
    at line 1 column 1998

The first null lands on coin AI, venue BybitPerp, the 4th of 232 coins, so
predicted_fundings fails on every mainnet call. On mainnet 69 of 696 venue
slots are null: BybitPerp 45, BinPerp 24.

Model the venue payload as Option<PredictedFundingVenue>. None means the coin
is not listed on that venue, which is distinct from a zero funding rate --
collapsing the two invents a funding spread against the venues that did
report one.

Add funding_interval_hours, which the exchange sends but the struct lacked.
Optional: 19 of 627 non-null payloads omit it.

Cover the endpoint in test_http_undocumented_typed_responses, which is the
test for this failure mode but never included predicted_fundings. Asserting
a null exists makes reverting the Option a compile error rather than a silent
regression.
@ifdario
ifdario merged commit f8a073e into infinitefield:main Aug 31, 2026
1 check passed
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