Skip to content

OKX on bench 001, and BNB onto the reference clock - #2794

Merged
Flotapponnier merged 2 commits into
mainfrom
feat/okx-bnb-to-main
Oct 3, 2026
Merged

Flotapponnier merged 2 commits into
mainfrom
feat/okx-bnb-to-main

Conversation

@Flotapponnier

Copy link
Copy Markdown
Collaborator

Cherry-picks of #2791 and #2792, already merged to dev, with nothing else from
that branch. Two commits, ten files.

Both are harness changes, so neither takes effect until ocb-aggregator-head-lag
is rebuilt on the VPS. Merging this only puts the spec, the copy and the logo on
production, which is the point: the harness is shared between staging and prod
through one Prometheus, so deploying it before this lands would show corrected
BNB numbers on production under the old explanation.

#2791 — OKX as a fifth provider, Base and Solana

OKX's documented trades channel needs a $99/month plan ("Free: WebSocket not
supported"). Their web frontend holds a different socket that needs no
credential: dex-market-trade-history on wss://wsdexpri.okx.com/ws/v5/ipublic,
with the params in an extraParams JSON string — flat keys get an
{"event":"subscribe"} ack and then silence, which is how it stayed hidden.

Coverage verified against eth_getLogs on the bench pool rather than assumed:
266 of 266 Base pool swaps present in OKX's stream, no socket gap, p50 0.636 s
behind our node. Solana is not verified — its ground truth returned 52 pool
transactions in 30 minutes, implausibly low, so that denominator is incomplete.

BNB and Robinhood are excluded, each for a structural reason documented in the
monitor header and pinned by tests.

#2792 — BNB moves onto the reference clock

The spec claimed BNB's chain-supplied timestamps sat "within roughly 60 ms of
the moment a trade is observable". The gap between that series and the
reference-matched one is that quantity, and it measures 569 ms.

So the published column was dominated by block time, which every provider
shares. eu-west p50 over 8,604 samples:

provider published against the reference
mobula 0.621 s 0.052 s
serialized 0.657 s 0.102 s
codex 0.676 s 0.215 s

Within 9% of each other before, a factor of four after, ranking unchanged.

Production BNB figures will drop 5-10x once the harness is redeployed. That
is a methodology correction, not providers getting faster. The spec text in this
PR is what explains it, which is why this lands before the harness does.

Checks on this base

  • go build ./... and go test ./cmd/script/ clean
  • pnpm validate 235 specs valid
  • pnpm exec tsc --noEmit clean
  • bun test src/ 458 pass, 0 fail

After this merges

  1. rebuild ocb-aggregator-head-lag on the VPS — until then OKX has no data and
    the site omits a provider with no series, so the column will not appear
  2. rebuild ocb-bridge-monitor for the freshness restoration already on main,
    which is why bridge-execution-latency and bridge-realized-cost still
    serve noindex
  3. read head_lag_ref_matches_total{aggregator="okx"} to settle Solana coverage

🤖 Generated with Claude Code

Flotapponnier and others added 2 commits October 4, 2026 00:32
…ion (#2791)

Their documented trades channel, wss://wsdex.okx.com/ws/v6/dex, answers
"Please log in" to every subscribe including the docs' own verbatim example,
and their fee table says why: Free reads "WebSocket not supported", cheapest
WS-capable plan $99/month.

Their web frontend holds a different socket that needs no credential. Two
things hid it, both of which fail silently:

  - the channel is dex-market-trade-history, not any `trades` variant. Twenty
    other names were rejected before this one;
  - the params go in an extraParams JSON STRING. With flat keys the gateway
    still answers {"event":"subscribe"} and then pushes nothing, which is
    indistinguishable from a dead channel. An ack proves nothing here, only
    frames do.

Base and Solana only, and both exclusions are structural. BNB publishes
head_lag_seconds as receiveTime minus the provider's own timestamp, and every
OKX timestamp is a whole second (1,800 sampled across three chains, all
ts%1000 == 0), so measuring it there would publish our arithmetic as OKX's
latency. Robinhood Chain is not in OKX's supported set.

OKX cannot scope a subscription to a pool: six ways of passing the pool
address are accepted and silently ignored, and the paid channel is
token-scoped too, so this is the shape of the product rather than a level of
access. It does not change what is scored. Only emissions matched to the
reference by transaction hash are, and the reference is the bench pool, so the
scored set is identical to the other four providers'. Verified before shipping
rather than assumed: over 20 minutes on the Base pool, 213 of 213 pool swaps
appeared in OKX's stream, p50 636 ms behind our node, OKX never first.

The one cost is bookkeeping. An off-pool emission never matches and would
expire as a miss, which would read as a 96%-miss provider and drown the signal
that counter exists for: whether the reference itself is healthy.
tokenScopedAggregators exempts it, and a test keeps the exemption to providers
that genuinely cannot scope to a pool.

The feed is undocumented frontend infrastructure with no stability guarantee.
That is the bet geckoterminal_monitor.go already makes here, and the failure
mode is the same: the flow watchdog purges the gauge and the column reads
"Feed down" rather than a wrong number. Tests pin the socket and channel
constants so a silent move fails loudly.

okx.svg replaces the opaque JPEG, which drew a white box around the mark on
dark backgrounds.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
head_lag_seconds on bnb published receiveTime minus the provider's OWN
timestamp, because bnb was in neither referenceChains nor raceChains. The
spec justified that by claiming BNB's chain-supplied timestamps sat
"within roughly 60 ms of the moment a trade is observable".

The gap between that series and the reference-matched one IS that
quantity, and it is 569 ms.

So the dominant term in the published column was block time, which every
provider shares, and the column barely separated them. Measured over 24 h,
8,604 samples per provider, eu-west p50:

              published    against the reference
  mobula        0.621 s          0.052 s
  serialized    0.657 s          0.102 s
  codex         0.676 s          0.215 s
  geckoterminal 33.881 s        31.196 s

Within 9% of each other before, spread over a factor of four after, and
the ranking is unchanged. This removes a common offset rather than
reshuffling anyone. p90 moves the same way (1.0-1.2 s to 0.12-0.33 s).

The gate in pending_match.go is that the reference must arrive before
every provider. Our node is beaten 6.6% of the time by mobula on bnb,
4.7% by serialized, 3.7% by codex. Base, already on the reference and
shipping, sits at 4.2% / 3.1% / 0%. Same order of magnitude, so this
clears the bar the project already applies to itself.

The reference subscription this reads already existed and already
populated head_lag_ref_seconds; nothing new is collected. Only which
series the headline reads changes.

Published numbers on the bnb column will drop by roughly 5-10x the day
this deploys. That is a methodology correction, not providers getting
faster, and the spec now says so where it used to carry the 60 ms claim.

TestReferenceChainsScope asserted bnb must NOT be here. Updated
deliberately rather than deleted, with the reason, and a new test pins
why bnb belongs now and why robinhood and solana still do not.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Flotapponnier
Flotapponnier merged commit 6082123 into main Oct 3, 2026
2 checks passed
@Flotapponnier
Flotapponnier deleted the feat/okx-bnb-to-main branch October 3, 2026 22:36
Flotapponnier added a commit that referenced this pull request Oct 4, 2026
Both monitors shipped covering Base and Solana, and both headers gave the
same reason for leaving BNB out: the chain published head_lag_seconds as
receiveTime minus the provider's own timestamp, and both providers send
whole seconds, so the column would have carried our arithmetic rather than
their latency.

That reason expired in #2794, which moved BNB onto the reference clock. I
wrote the exclusion and then retired its premise an hour later without
connecting the two, and the stale reason stayed in two file headers, two
published formulas and a methodology bullet.

The feed was never the obstacle. Measured 45 s per chain on the non-stable
leg of the bench's USDT/WBNB PancakeSwap V3 pool, before changing either
table:

  birdeye  /bsc/socket-optimize  825 trades, 825 with a hash
  okx      chainId 56            938 trades, 938 with a hash

BNB is pool-scoped (eth_subscribe logs on the bench pool address), so a
token-scoped provider's off-pool emissions find no match and are dropped,
exactly as on Solana. This adds a chain where the comparison is sound and
does not touch the open question about Base, whose flashblock reference is
chain-wide.

Robinhood stays out on both, and now for a measured reason instead of one
read off a chain list:

  birdeye  /robinhood/socket-optimize  WELLCOME frame, 0 trades in 45 s
  okx      chainId 4663                acked, 0 trades in 40 s

Both on the pool address and on its non-stable leg, while Base and BNB on
the same code delivered hundreds. An invented chain name 404s where
/robinhood/ does not, so the route exists and the chain is simply not
indexed. Same ack-proves-nothing shape as the six pool-scoping attempts
documented in the OKX header, which is why an accepted subscription is not
counted as coverage anywhere in this bench.

Three comments on main asserted things the code contradicts, and they go
with it. The Birdeye header still told the next reader that "no TLS
fingerprinting is involved and the uTLS machinery is not needed here",
forty lines above the uTLS dialer that exists because it is and it was;
errBirdeyeForbidden still asserted the egress-IP diagnosis that #2802
retired. Both now point at the fingerprint.

Two guard tests fired on this change, which is what they were for, but
they asserted the conclusion (OKX must not cover BNB) rather than its
premise (OKX may cover a chain only while that chain reads a clock we
hold). Re-derived as the coupling, so taking bnb back out of
referenceChains now fails and names OKX as the reason, which the
unconditional pair could not do.

Published copy also stopped overstating two things. The cardinality
bullet still counted 4 aggregators on 4 chains; it is 22 aggregator-chain
pairs now. And Birdeye's coverage is disclosed rather than implied by the
presence ratio: its stream carried 106 of 442 and 13 of 44 bench-pool
swaps across two Base windows, about a quarter, so its percentiles
describe the trades it reports and not the pool.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.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.

1 participant