Docs: Solana RFQ, cube streaming matrix, retention fixes, and the 10 Aug BalanceUpdates migration - #229
Merged
Merged
Conversation
RFQ and intent trades settle without a pool, so DEXTrades and DEXTradeByTokens return zero rows for Jupiter Z, Jupiter Limit Order v2, Mayan Swift, HumidiFi, Tessera V and ZeroFi. Nothing in the docs covered how to read that flow, and some assets (tokenized equities) trade there and nowhere else. The page documents durable structure only — program IDs, the order_engine fill layout, positional account gotchas, mint references, and the query patterns — rather than point-in-time statistics. Market-state numbers (maker rosters, notional shares, live equity prices, per-hour counts) are deliberately left out; each section instead gives the query that regenerates them, plus the qualitative pattern that holds across runs. Contents: - program roster for both families (RFQ/intent and proprietary MM AMMs) - order_engine fill layout, sample response, and a tested JS decoder - InstructionBalanceUpdates for currency/decimals/USD per leg, since the raw Instructions response returns only base58 and raw integers - quote expiry, maker leaderboard, fee payer, landing rate, bps method - xStocks and Ondo mint reference, Mayan cross-chain intents, quote tape All 16 GraphQL examples were executed against the live EAP endpoint and return rows. Also adds inbound links from the Jupiter, DEX Trades and xStocks pages so the new page is not orphaned. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Schema introspection says all 42 cubes on EVM/Solana/Trading/Tron accept a subscription. Live WebSocket testing shows three distinct behaviours, and nothing in the docs distinguished them. New: subscriptions/which-cubes-stream.md - Per-cube matrix built by opening a real socket against every cube, not read off the schema. - 5 cubes accept a subscription and never emit: EVM Balances/Holders/Uncles and Tron Balances/Holders. They are derived views with no underlying event. Documents what to stream instead. - 3 Solana cubes (Instructions, BalanceUpdates, InstructionBalanceUpdates) are dropped with close code 1013 when unfiltered. Adding a where filter fixes it; verified that the same subscription which gets dropped unfiltered delivers cleanly when scoped to one token or program. - Lists cubes that need a specific network or dataset (Prediction* need network: matic; Uncles needs archive), which today is only discoverable from an error message that reads like an outage. Also: - silent-disconnect-reconnect: name close code 1013 and its two fixes. The page already recommended non-blocking consumption but never said what happens if you ignore it. - Solana Blocks API: new page. This cube had zero examples anywhere. Covers slot vs height, skipped-slot detection via ParentSlot, slot to timestamp lookup, and throughput via TxCount aggregation. - Tron: blocks and Super Representative attribution via Witness, on the transactions page. Tron Blocks also had zero coverage. - InstructionBalanceUpdates: add the missing subscription, with the filter requirement and the native-SOL double-counting caveat. - Balances/Holders cube and schema pages: query-only callouts. - joins: new section on why a join silently returns empty fields. The default left join returns blank values on no match, and the usual cause is the joined cube having no rows in the same window. Includes a reliably-matching inner join example. Every query and subscription added here was executed against the live endpoint; subscriptions were verified over graphql-ws. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BalanceUpdates and TokenHolders are deprecated on EVM and Tron in favour of Balances and Holders, which read from the balances_by_address / balances_by_currency aggregate-state tables. Two consequences the joins page got wrong. Example 3 (pool liquidity + price) used the deprecated BalanceUpdates cube with dataset: combined. The deprecated cube has no combined support, so the published query fails on Ethereum with "Database eth does not exist". Migrated to Balances, which supports realtime, archive and combined, and returns the pool's balance directly instead of summing deltas. Verified working on all three datasets. An earlier draft of this page blamed that error on a missing plan entitlement. That was wrong and is corrected: it is a deprecated-cube issue, and the note now says so. Example 2 claimed joinBalanceUpdates gives total supply and market cap. It does not. BalanceUpdates records one account's change, so the joined PostBalance is whichever account updated most recently. Checked against BONK: the join returns a few million tokens worth tens of dollars, while actual supply is ~88 trillion at a market cap in the hundreds of millions. Added the correct approach, querying TokenSupplyUpdates directly, and kept the join example with an accurate description. Also documents that a join from Balances cannot be ordered by Block_Time, because Balances is current-state with no block dimension, so the joined row is arbitrary and can come back as PriceInUSD 0. which-cubes-stream: explain why Balances and Holders never emit. They are aggregate-state tables, which is the same property that makes them fast and gives them combined support. Adds the migration note for anyone moving off BalanceUpdates, who loses a streamable event log and should stream Transfers or TransactionBalances instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"What does this address hold, and what is it worth" was answered with
ad-hoc IDE links rather than a page. Existing PnL content covers realised
profit on trades, not current holdings, so this fills the portfolio gap
rather than duplicating it.
Covers EVM and Tron through the Balances cube, and Solana separately
because Solana has no Balances cube — there the pattern is BalanceUpdates
with limitBy on the mint to collapse to the latest balance per token.
Documents three things that are easy to get wrong:
- Balances exposes only count, uniq and calculate. There is no sum, so a
portfolio total has to be computed client-side. The Holders cube does
support the full aggregate set, which is the opposite of what most
people assume from the names.
- Non-zero filtering goes on the field via Amount(selectWhere: {gt: "0"}),
not in the where block.
- A dated Holders snapshot scales with the holder set and can exceed the
request timeout on a very large token, while working fine on a normal one.
Also surfaces FirstChangeTime / LastChangeTime / UpdateCount, which turn a
balance list into a dormancy profile at no extra cost, and the Holders
statistics set (gini, nakamoto, median) for concentration, computed
server-side instead of by pulling every holder.
All six queries executed against the live endpoint.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"What did this address hold at the close of day X" had no page at all and was answered with ad-hoc IDE links. Accounting, tax and reconciliation all need it. EVM and Tron use Holders with its date argument, filtered to one address. Verified this returns a genuine per-day series rather than a running total: the same wallet's USDT balance moves 520M / 638M / 561M / 819M / 550M across five consecutive dates. Solana has no Holders cube, so the pattern is BalanceUpdates with a till cutoff, descending Block_Time, and limitBy on the mint to keep the last update per token. All three parts are required; dropping any one gives a wrong answer, so the page says so explicitly. Documents the trap that costs the most time: filtering Balances by Block.Date does not produce an as-of snapshot. Balances is current-state, so that filter selects underlying records and returns rows stamped with unrelated dates. It looks like it worked. Point-in-time needs Holders(date:). Also covers using dataset: archive for past dates (a realtime query on an old date returns empty and reads as a zero balance), UTC day boundaries, and the difference between an absent row and a zero balance on Solana. Both queries executed against the live endpoint. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Completes P6. Both topics were answered ad-hoc rather than with a page. Solana fee anatomy: separates the three costs of a transaction, only two of which appear in Transaction.Fee. Jito tips are transfers, not fees, so anyone summing Fee is undercounting all-in cost. Measured rather than asserted: the median Solana fee sits exactly at the 5,000 lamport base fee, p25 and p75 are at or barely above it, and the distribution only lifts in the last percentile. In a 200-transaction sample, 198 paid exactly 5,000 and 2 paid 10,000 (two signatures, zero priority). So most transactions pay no meaningful priority fee and a small minority accounts for nearly all priority spend, which is why comparing your fee to the mean is misleading. Also documents that failed transactions pay a higher average fee than successful ones, and that a substantial share of all fees is spent on transactions that never land. Estimating strategy cost from successful transactions alone understates it. Notes honestly that the base/priority split is inexact: signature count is not exposed, so a two-signature transaction at zero priority is indistinguishable from a one-signature transaction paying 5,000 lamports of priority. fee - 5000 is an upper bound, exact only for known single-signature transactions. Wash-trading signals: four queries for the patterns used to flag inorganic volume — both sides of one trade, round-tripping via sum with an if condition, trades-per-account concentration, and counterparty diversity. Framed deliberately as signals rather than a classifier. There is no on-chain field for intent, and market makers, arbitrageurs and rebalancing bots all resemble wash traders in the data. The page sets no thresholds, tells the reader to own the conclusion, and warns against the two ways these numbers usually go wrong: reporting "wash volume" as a claim about intent, and extrapolating from a scored sample. All eight queries executed against the live endpoint. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The coverage page already existed and was broadly right — notably that Solana Transfers is roughly the last 8 hours. This adds measurement where it said "recent" and corrects two things. Measured per-cube realtime floors via Block Time(minimum: Block_Time) on /graphql. Solana splits sharply and the page did not say so: DEXTrades ~12 hours DEXTradeByTokens ~7 days InstructionBalanceUpdates ~12 hours BalanceUpdates ~7 days Same trades, same balance changes, ~15x difference. Aggregate-shaped cubes retain far longer than raw per-event cubes, which is the single most useful thing to know when a Solana query appears to lose history. Added as a warning rather than a table row. Corrections: - The Solana table listed a "Balances" cube. Solana has no Balances cube; balance changes are BalanceUpdates. Row replaced. - EVM Calls & Events realtime is ~24h, shorter than Transfers and the DEX cubes at ~4 days. Worth knowing if you decode contract activity. New caveat on combined. The page recommends it for "recent history + now", but it currently returns a ClickHouse 500 on every Solana cube tested (both /graphql and /eap) and on EVM Events and Calls, while working on EVM Transfers/Transactions/Blocks/DEX cubes. A raw 500 is distinct from the clean "no table can query" message that means a dataset is undeployed, so the page now says to re-test on archive before treating a 500 as an outage. Also resolved a shipped "[confirm start]" placeholder: all four Trading cubes (Trades/Tokens/Pairs/Currencies) measure the same ~30 day floor, so OHLC aggregates are not longer-retained within those cubes and the row now points at the Crypto Price API instead of implying otherwise. Added a self-measurement query so readers can confirm their own plan's windows rather than trusting a published figure, including the note that Balances and Holders have no Block.Time and error against it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
136 pages carry queries and no subscription. Filtering to pages whose cubes actually stream, that are protocol pages rather than reference or historical ones, and that are not built on deprecated cubes leaves 55 genuinely actionable. These are the first three, chosen where streaming changes the workflow rather than just adding an example. - Robinhood meme coin launches. A launch query tells you what already launched; by the time you re-run it the opportunity has moved. The same zero-address mint filter works as a subscription. - Blur / Seaport NFT trades. - Base Uniswap trades, filtered by ProtocolFamily so it covers every Uniswap version. Each page also states the query-to-subscription rule in place: change query to subscription and drop the orderBy, since a stream is already in block order. The Robinhood page additionally notes not to carry the dataset: combined tip over to a subscription, since streams always read live. All three verified over graphql-ws against wss://streaming.bitquery.io/graphql, exactly as written on the page: 97, 2 and 16 messages respectively. Excluded from this pass and routed elsewhere: tron-balance-updates (built on the removed Tron.BalanceUpdates, belongs in the deprecation migration), historical-aggregate-data (a backfill page, where a stream makes no sense), and the graphql/ and cubes/ reference pages. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The page stated that Tron.BalanceUpdates "was removed on 18 July 2026" and is "no longer available". It is not removed. Queried live: newest row timestamped 11 seconds before the check, 38.8M rows in the window. The EVM equivalent is also live, 16.9M rows. This mattered because the claim points the wrong way. A reader with working production code on Tron.BalanceUpdates would conclude it had already broken and rewrite it under time pressure, when the cube is deprecated but fully functional and the migration can be planned. Reworded to say deprecated but not yet removed, with the reason to move (Balances and Holders read aggregate-state tables and return the current balance directly rather than requiring you to sum deltas) and no false urgency. The 5 example queries lower down are relabelled the same way: they still run, the new cubes are simply the supported path. Found while triaging the 44 remaining deprecated-cube blocks. Worth noting that none of those are broken either — a separate check confirmed zero TokenHolders blocks exist in the docs, and TokenHolders is the one cube of the pair that genuinely no longer resolves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
EVM.BalanceUpdates, EVM.TokenHolders and Tron.BalanceUpdates sunset on 10 August 2026 — seven days out. 44 query blocks across 22 pages still teach them, and 20 of those pages carried no warning at all. A reader landing on four-meme-api or nft-ownership-api had no way to know their queries stop working next week. Adds the sunset notice to every affected page, linking the existing migration mapping. Banner-first because it reaches 100% of affected readers immediately; rewriting the 44 queries follows and is slower. Corrects an error I introduced earlier today. The Tron page had claimed the cube was already removed; I verified it was live and softened the wording to "migrate when convenient". With a hard date seven days out that was wrong in the opposite direction, so it now states the deadline plainly. Both extremes were misleading — the accurate position is deprecated, live today, gone on the 10th. Two things this surfaced that need a decision, not just documentation: - Change attribution has no replacement. BalanceUpdates exposes Type (transfer, fee, block_reward) per change; Balances has no equivalent field. The migration mapping previously said BalanceUpdates "remains the right tool" for this, which stops being true on the 10th. Now documented as an open gap with the nearest workarounds: derive from Transfers plus transaction context, or use Balances.UpdateCount where a count suffices. - EVM.TokenHolders is already gone, ahead of the others, and now returns "no table can query TokenHolder". No docs used it, so nothing broke, but it means the sunset is being rolled out in stages. Solana is unaffected and the pages say so: Solana.BalanceUpdates and Solana.InstructionBalanceUpdates are current there, since Solana has no Balances cube. Verified live before writing: both EVM and Tron BalanceUpdates are returning current data (newest row seconds old, 16.9M and 38.8M rows). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
I published wrong guidance and this reverses it.
end-of-day-balances.md carried a :::danger saying "do not filter Balances
by block date to get a historical balance", claiming it returns rows
stamped with unrelated dates rather than an as-of snapshot. That is
false. Balances holds daily balance aggregates exposed as Block.Date, and
filtering plus ordering on it returns exactly the balance for that day.
My original test omitted orderBy: {descending: Block_Date}, so it
returned an arbitrary day's row. I read that as the cube being unsuitable
instead of my query being incomplete, and wrote a danger callout telling
readers not to use a method that works.
Verified both ways now agree to the last decimal for the same wallet and
date:
Balances, Block.Date till 2026-07-01 = 819349860.876615
Holders(date: "2026-07-01") = 819349860.876615
The danger block is replaced with the daily-series query, which is the
better source for a range: one request returns 30 days, against one
request per day with Holders. The ordering requirement is documented as a
caution, since omitting it fails silently and looks like a current
balance.
This also reframes the sunset migration. Losing per-change rows is the
intended design, not a gap: BalanceUpdates gave one row per change,
Balances gives one row per address per day, and for most balance
questions the daily grain is what was wanted and is far cheaper. The
migration mapping and the balance-updates cube page now say that instead
of describing it as something lost. What genuinely does not carry over is
sub-daily attribution, since Type has no daily equivalent.
which-cubes-stream is corrected too: Balances and Holders never emit
because the grain is daily, not because they are a current-state snapshot.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
First batch of the BalanceUpdates migration ahead of the 10 August
sunset. Each deprecated query now has its Balances replacement directly
above it, with the old version kept in a collapsed <details> block marked
with the sunset date, so anyone mid-migration can still see the before
and after.
Patterns used, all verified live with retries:
liquidity for one token BalanceUpdates + sum(of: BalanceUpdate_Amount)
-> Balances { Balance { Amount } }
ranked token list orderBy descendingByField "balance"
-> orderBy { descending: Balance_Amount }
bonding-curve range sum(..., selectWhere: {ge, le})
-> Balance { Amount(selectWhere: {ge, le}) }
The conceptual change is that the aggregation is no longer yours to do:
Balances returns the amount when you do not select Block.Date, so the
sum() wrapper disappears and selectWhere moves onto the field.
Two things found while verifying that are worth separate attention:
Balances and Holders are returning intermittent 500s and timeouts on
every EVM chain tested. Retried three times each: Balances bsc 0/3,
matic 1/3, base 3/3, eth 3/3; Holders bsc 3/3, base 2/3, matic 2/3,
eth 2/3. Every chain succeeded at least once, so the cubes are deployed
everywhere and this is load rather than missing coverage — but these
become the only option in a week, and today they are less reliable than
the cube they replace, which answered 2/2 on the same runs.
NFT ownership cannot move to Balances or Holders. Neither exposes a token
Id or URI, so "who owns token #9996" has no equivalent. Per the agreed
approach those 6 blocks will be rebuilt on Transfers, which does carry Id
and URI; verified that the latest transfer for an Id returns the current
owner.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Second batch. Each deprecated query keeps its Balances replacement above
it and the original in a collapsed details block marked with the sunset
date.
Written as a transform rather than by hand, since 33 of the 40 remaining
blocks share one shape:
sum(of: BalanceUpdate_Amount) -> Balance { Amount }
sum(of: BalanceUpdate_Amount, selectWhere) -> Balance { Amount(selectWhere) }
uniq(of: BalanceUpdate_Address) -> count
orderBy descendingByField "balance" -> orderBy descending Balance_Amount
BalanceUpdate { } / BalanceUpdate: { } -> Balance { } / Balance: { }
The transform produced 28 candidates. Only the 17 that executed
successfully against the live endpoint are applied here; the other 11 are
left untouched deliberately.
That gap matters. Applying all 28 would have shipped broken queries:
removing the `balance:` alias silently breaks any
calculate(expression: "$balance ...") that referenced it, which is
exactly what happened on binance-memerush. Three others failed on filter
shape or duplicate top-level keys. Verification caught what the transform
could not reason about.
Remaining 23 blocks, none of them mechanical:
- 6 NFT ownership queries needing a Transfers rebuild (no Id or URI on
Balances/Holders)
- 1 mempool subscription using BalanceUpdate.Type, which has no
equivalent at all — Balances does not stream and has no Type
- 11 that failed transform (alias-dependent calculate, filter shape,
duplicate root keys)
- 5 that need variables supplied to verify
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
buddies2705
added a commit
that referenced
this pull request
Aug 3, 2026
Docs: fix contradictions found auditing #229
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.
Docs work across Solana RFQ, streaming behaviour, retention accuracy, four new recipes, and the BalanceUpdates sunset migration.
Every GraphQL query added here was executed against the live endpoint before commit, and every subscription verified over
graphql-ws. Build passes withonBrokenLinks: throw.Time-sensitive
EVM.BalanceUpdates,EVM.TokenHoldersandTron.BalanceUpdatessunset on 10 August 2026. 44 query blocks across 22 pages used them, and 20 of those pages carried no warning.Balances/Holders, old version kept in a collapsed<details>If review will take time, the sunset commits can be cherry-picked ahead of the rest.
New pages
blockchain/Solana/solana-rfq-apiDEXTrades/DEXTradeByTokensreturn zero rows for Jupiter Z, Mayan Swift, HumidiFi, Tessera V, ZeroFisubscriptions/which-cubes-stream1013. Built by opening a real socket against every cubeblockchain/Solana/solana-blocks-apiSolana.Blockshad zero examples anywhere. Slot vs height, skipped-slot detectionblockchain/Solana/solana-fee-anatomyFeeundercountsusecases/wallet-portfolio-apiusecases/end-of-day-balancesusecases/wash-trading-signalsCorrections to existing docs
joins.mdExample 2 was wrong. It claimedjoinBalanceUpdatesgives total supply and market cap. It returns whichever single account updated most recently. Verified against BONK: join returns ~6.3M tokens / $18, actual supply ~88T at a $248M market capjoins.mdExample 3 errored ondataset: combinedwith the deprecated cube. Migrated toBalancestron-balance-updates.mdclaimed the cube was already removed. It is live — newest row 11 seconds before the check, 38.8M rows. That would have pushed people to rewrite working codedata-coverage-retention.mdxlisted a SolanaBalancescube that does not exist, and shipped a literal**[confirm start]**placeholder. Added measured per-cube windowsleftjoin, no match, blank values)Measured findings worth knowing
DEXTrades~12h,DEXTradeByTokens~7 days — same trades, ~15x apart. Same split forInstructionBalanceUpdatesvsBalanceUpdatesSolana(dataset: combined)returns a ClickHouse 500 on all 37 documented queries, on both/graphqland/eap. EVMEventsandCallstoo. Not addressed here; needs a backend fixBalances/Holdersare currently less reliable than the cube they replace. Retried 3x per chain:Balancesbsc 0/3, matic 1/3, base 3/3, eth 3/3, whileBalanceUpdatesanswered 2/2 on the same runsKnown gaps
Balances/Holdersequivalent — neither exposes a tokenIdorURI. To be rebuilt onTransfersBalanceUpdate.Typehas no replacement at all.Balancesdoes not stream and has noType. That capability disappears on the 10th unless something replaces itcalculate, filter shape, duplicate root keys) and need hand migrationNot done deliberately
Solana(dataset: combined)queries left as-is.archiveworks and reaches 2024, but contradicts the "Solana realtime only" guidance, andrealtimewould silently truncate historical queries. Documented rather than silently changed