Repository navigation
OKX's DEX feed is open after all, on a channel their docs do not mention - #2791
Merged
Merged
Conversation
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>
Flotapponnier
marked this pull request as draft
October 3, 2026 16:36
Flotapponnier
marked this pull request as ready for review
October 3, 2026 17:34
Flotapponnier
added a commit
that referenced
this pull request
Oct 3, 2026
* OKX's DEX feed is open after all, on a channel their docs do not mention (#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> * the BNB column was measuring BNB's block time, not the providers (#2792) 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> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Oct 4, 2026
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.
Adds OKX as a fifth provider on bench 001, on Base and Solana.
The access problem, and why it is not one
OKX's documented trades channel is
wss://wsdex.okx.com/ws/v6/dex, channeltrades. It answers{"event":"error","msg":"Please log in","code":"60011"}toevery subscribe, including their documentation's own verbatim example. Their fee
table explains it:
Their web frontend holds a different socket that needs no credential. Two
things hid it, and both fail silently:
dex-market-trade-history, not anytradesvariant. Twentyother names were rejected before this one;
extraParamsJSON string. With flatchainIndex/tokenContractAddresskeys the gateway still answers{"event":"subscribe"}and then pushes nothing, which is indistinguishablefrom a dead channel. An ack proves nothing here; only frames do.
Both are pinned by tests, because a silent move should fail loudly.
Why Base and Solana only
Neither exclusion is provisional.
BNB publishes
head_lag_secondsasreceiveTimeminus the provider's owntimestamp — it is in neither
referenceChainsnorraceChains. Every OKXtimestamp is a whole second: 1,800 sampled across three chains, all
ts%1000 == 0. Putting a second-quantised clock in a column of millisecond oneswould publish our arithmetic as OKX's latency. Base reads the reference clock
and Solana the race, neither of which touches the provider timestamp.
Robinhood Chain is not in OKX's supported set at all.
Why a token-scoped feed is still apple-to-apple
OKX cannot scope a subscription to a pool. Six ways of passing the pool address
(
poolAddress,poolId,pairAddress,dexContractAddress, with and withoutthe token, plus a
dexNamefilter) are accepted and silently ignored, and thepaid v6 channel is token-scoped too — so this is the shape of the product, not a
level of access.
It does not change what is scored. Only emissions matched to the reference by
transaction hash are scored, and the reference is the bench pool, so the scored
set is identical to the other four providers'. The off-pool trades are
discarded.
Measured before shipping rather than assumed. Two independent observers over the
same window: our own
eth_subscribeon the bench pool, and OKX's stream. Bothsides buffered with a 90 s deadline, because a swap OKX pushes before our node
sees it is the case the bench exists to find.
Base is settled. 266 of 266, against a denominator that is complete by
construction (
eth_getLogson the pool and Swap topic cannot miss a swap ordeliver one late), with no socket gap during the run. That puts coverage at
98.9% or better with 95% confidence, which rules out the failure that would
have broken comparability: a provider publishing only a subset would be scored
on its easy trades while the others are scored on all of them.
Solana is not settled, and I am not claiming it. 52 transactions in 30
minutes on SOL/USDC Raydium is about 1.7 a minute, which is implausibly low for
that pool, so
getSignaturesForAddressplainly did not return everything. 52 of52 is not contradicted by anything, but it rests on a denominator I do not
believe is complete. Treat the Solana column as unverified until the match
counter has run in staging.
Why the earlier figures in this PR's history were wrong
An initial run used a live
eth_subscribeas ground truth and decayed from 100%at 25 minutes to 67% at 190, with OKX apparently arriving up to 24 s before
the chain. That is impossible, and it was the tell: the reference was the
degraded half. My rig held only a publicnode logs subscription, while
production's Base reference is the flashblock preconfirmation stream and
refClock.observekeeps the FIRST observation, so production has a materiallybetter clock than that rig did.
The fix was to stop using a stream for this at all. Coverage asks what happened
on chain, which a historical query answers completely; only latency needs a fast
clock.
Latency, measured on genuinely matched pairs, was stable across every window:
p50 0.636 / 0.615 / 0.552 s on Base.
The one cost, and why it is contained
An off-pool emission never matches and would expire as a miss. That would read
as a 96%-miss provider and drown the signal the counter exists for: whether the
reference itself is healthy.
tokenScopedAggregatorsexempts OKX, and a testkeeps the exemption to providers that genuinely cannot scope to a pool — adding
mobulaorcodexto that map fails.Stability
The feed is undocumented frontend infrastructure and carries no guarantee. That
is the bet
geckoterminal_monitor.goalready makes in this harness (its pool idsare commented "extracted via reverse engineering"), and the failure mode is the
same: the flow watchdog purges the gauge and the column reads "Feed down" rather
than a wrong number. The monitor holds one socket per chain, because the gateway
keys subscriptions by channel name alone and silently ignores a second token on
the same connection.
Also
okx.svgreplaces the opaque 400x400 JPEG, which drew a white box around themark on dark backgrounds. Same mark, same black ground, geometry read off the
original.
Checks
go build ./...andgo test ./cmd/script/clean, 7 new testspnpm validate235 specs validpnpm exec tsc --noEmitcleanbun test src/425 pass, 0 failWhat a reviewer should push back on
The Solana denominator. 52 pool transactions in 30 minutes is too low for
SOL/USDC Raydium, so that ground truth is incomplete and the 52/52 proves less
than it looks. Base carries the coverage argument; Solana should be confirmed
from
head_lag_ref_matches_total{aggregator="okx"}in staging before its columnis trusted.
The stability bet. This is undocumented frontend infrastructure. If that is
not an acceptable dependency for a published bench, say so now rather than after
it moves: the alternative is $99/month for the documented channel, which is
token-scoped too and would therefore need every other part of this PR
unchanged.
🤖 Generated with Claude Code