Skip to content

fix(revenue): bucket fees by true UTC days and detect frozen feeds - #16

Merged
Yaugourt merged 2 commits into
mainfrom
fix/revenue-series-integrity
Jul 27, 2026
Merged

fix(revenue): bucket fees by true UTC days and detect frozen feeds#16
Yaugourt merged 2 commits into
mainfrom
fix/revenue-series-integrity

Conversation

@Yaugourt

Copy link
Copy Markdown
Owner

The bug on production right now

/market/revenue/history is serving $59,286 for 2026-07-26 and $48 for the running day, against a real ~$675K. On the charts that reads as the protocol collapsing.

The daily series was built by diffing consecutive points of Hypurrscan's cumulative counter and filing each diff under the newer point's date. That measures the interval between two timestamps, not a calendar day. Harmless mid-series where points land every ~24h; fatal at the tail, where a cached snapshot whose newest point is intraday emits a day worth an hour of fees.

The same blindness applied to source health. A frozen upstream keeps answering 200 with a stale payload, which bucketing turns into zeros indistinguishable from a quiet day, and priorityStatus only checked that the promise settled. It has reported ok through sixteen consecutive days of silent zeros while HypeDexer's priority aggregation sits frozen at 2026-07-11 (its gossip auction endpoint is frozen at 2026-07-11T17:55:26 too).

The fix

Interpolate the cumulative counters onto UTC midnight and diff midnight to midnight:

  • every emitted day is a true UTC day
  • the running day is never emitted, its closing boundary is in the future
  • an outage is spread across the days it spans instead of dumped on one. 2024-12-18 was showing a fabricated $24.1M record day for what was actually a sixteen-day gap
  • boundaries inside a gap wider than 30 days are skipped rather than guessed

Both perpSpot and priority are now judged on the newest day they actually populate, and meta.coverage carries those dates so the client can stop a series at its real end instead of drawing it to the floor.

The computation moves to revenue.daily.ts as a pure function, testable without a singleton, Redis or upstream clients.

Impact on the numbers

Replayed against the full 587-point production series:

before after
days emitted 586 616
lifetime $1,262,764,431 $1,262,046,882

Lifetime moves 0.057%, almost all of it the running day we now correctly withhold. 29 days change materially, every one of them a poller hiccup the old diff had mis-attributed (2026-02-21 $110,734 → $503,665, 2026-02-22 $966,679 → $583,126, and so on).

Verification

  • 11 new unit tests: steady cadence, intraday tail point, running day, late point spanning two days, gap refusal, backwards counter, malformed points, frozen-feed detection
  • full suite 111 passed, tsc --noEmit and eslint clean
  • run against live Hypurrscan + the real frozen priority feed:
    coverage: {perpSpot: '2026-07-25', priority: '2026-07-11'}, priority: 'stale' (was ok), no junk tail day

Front counterpart, which surfaces coverage on the cards: Yaugourt/liquidterminal_front#12

Follow-up, not in this PR

The priority feed itself is still frozen upstream. This PR stops us reporting zeros as fact; it cannot restore the data.

🤖 Generated with Claude Code

https://claude.ai/code/session_013zGKQKm432ZKEw1GjVWPZG

Yaugourt and others added 2 commits July 27, 2026 05:47
…erEVM

Comment-only. The client that feeds the revenue breakdown's `priority`
field was documented as a "Daily HyperEVM priority-fees burn chart". It is
neither HyperEVM nor the whole of priority fees.

Per the protocol docs there are two priority mechanisms, both on HyperCore
and both burning HYPE: order priority, charged from undelegated staking
balance as up to 8 bps of filled notional (IOC) or resting notional (ALO);
and gossip priority, two Dutch auctions on a three-minute cycle for faster
market-data reads, charged from spot balance, resetting at 10x the last
winning bid with a 0.1 HYPE floor. This endpoint returns the first only.

Worth knowing for whoever picks this up: gossip priority is already served
by /hip3/priority-fees/gossip/*, but that feed has been frozen since
2026-07-11 — 200 rows all stamped that day, 8 of them ever cleared — so the
stream cannot be added to revenue until the ingestion is fixed.

HyperEVM priority is a third and unrelated stream. Its sink, the EVM zero
address, has been flat for at least a week (282,606.50 HYPE, moving by
0.03 over seven days), so nothing is being lost by leaving it out.

No behaviour changes; every number the API returns is unchanged.

https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/priority-fees

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013zGKQKm432ZKEw1GjVWPZG
The daily series was built by diffing consecutive points of Hypurrscan's
cumulative counter and filing each diff under the newer point's date. That
measures the interval between two timestamps, not a calendar day, so a point
landing early turns its day into a partial one. Harmless mid-series, fatal at
the tail: production was serving $59,286 for 2026-07-26 against a real ~$675K,
and $48 for the running day, which reads as a collapse rather than as data we
do not have yet.

Interpolate the counters onto UTC midnight and diff midnight to midnight
instead. Every emitted day is now a true UTC day, the running day is never
emitted, and an outage is spread across the days it spans rather than dumped on
one: 2024-12-18 was showing a fabricated $24.1M record day for what was
actually a sixteen-day gap. Lifetime moves by 0.06%, all of it the running day
we now correctly withhold.

The same blindness applied to source health. A frozen upstream keeps answering
200 with a stale payload, which bucketing turns into zeros indistinguishable
from a quiet day, and priorityStatus only checked that the promise settled. It
reported ok through sixteen consecutive days of silent zeros while HypeDexer's
priority aggregation sat frozen at 2026-07-11. Both perp/spot and priority are
now judged on the newest day they actually populate, and meta.coverage carries
those dates so the client can stop a series at its real end.

The computation moves to revenue.daily.ts as a pure function so it is testable
without a singleton, Redis or upstream clients.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013zGKQKm432ZKEw1GjVWPZG
@Yaugourt
Yaugourt merged commit 2854ca3 into main Jul 27, 2026
2 checks 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.

1 participant