Skip to content

Docs: Solana RFQ, cube streaming matrix, retention fixes, and the 10 Aug BalanceUpdates migration - #229

Merged
buddies2705 merged 13 commits into
mainfrom
docs/solana-rfq-api
Aug 3, 2026
Merged

Docs: Solana RFQ, cube streaming matrix, retention fixes, and the 10 Aug BalanceUpdates migration#229
buddies2705 merged 13 commits into
mainfrom
docs/solana-rfq-api

Conversation

@buddies2705

@buddies2705 buddies2705 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

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 with onBrokenLinks: throw.

Time-sensitive

EVM.BalanceUpdates, EVM.TokenHolders and Tron.BalanceUpdates sunset on 10 August 2026. 44 query blocks across 22 pages used them, and 20 of those pages carried no warning.

  • All 22 pages now carry the sunset notice
  • 21 of 44 queries migrated to Balances / Holders, old version kept in a collapsed <details>
  • 23 remain, none mechanical — see "Known gaps"

If review will take time, the sunset commits can be cherry-picked ahead of the rest.

New pages

Page Why
blockchain/Solana/solana-rfq-api RFQ fills appear in no trade table. DEXTrades/DEXTradeByTokens return zero rows for Jupiter Z, Mayan Swift, HumidiFi, Tessera V, ZeroFi
subscriptions/which-cubes-stream All 42 cubes accept a subscription; 5 never emit and 3 get dropped with close 1013. Built by opening a real socket against every cube
blockchain/Solana/solana-blocks-api Solana.Blocks had zero examples anywhere. Slot vs height, skipped-slot detection
blockchain/Solana/solana-fee-anatomy Base vs priority vs Jito tip. Jito tips are transfers, not fees, so summing Fee undercounts
usecases/wallet-portfolio-api "What does this address hold" was answered with ad-hoc IDE links
usecases/end-of-day-balances Zero pages existed; accounting and tax need it
usecases/wash-trading-signals Framed as signals with reader-set thresholds, not a classifier

Corrections to existing docs

  • joins.md Example 2 was wrong. It claimed joinBalanceUpdates gives 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 cap
  • joins.md Example 3 errored on dataset: combined with the deprecated cube. Migrated to Balances
  • tron-balance-updates.md claimed 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 code
  • data-coverage-retention.mdx listed a Solana Balances cube that does not exist, and shipped a literal **[confirm start]** placeholder. Added measured per-cube windows
  • New joins section on why a join silently returns empty fields (default left join, no match, blank values)

Measured findings worth knowing

  • Solana retention splits by cube shape. DEXTrades ~12h, DEXTradeByTokens ~7 days — same trades, ~15x apart. Same split for InstructionBalanceUpdates vs BalanceUpdates
  • Solana(dataset: combined) returns a ClickHouse 500 on all 37 documented queries, on both /graphql and /eap. EVM Events and Calls too. Not addressed here; needs a backend fix
  • Balances/Holders are currently less reliable than the cube they replace. Retried 3x per chain: Balances bsc 0/3, matic 1/3, base 3/3, eth 3/3, while BalanceUpdates answered 2/2 on the same runs

Known gaps

  • 6 NFT ownership queries have no Balances/Holders equivalent — neither exposes a token Id or URI. To be rebuilt on Transfers
  • 1 mempool subscription using BalanceUpdate.Type has no replacement at all. Balances does not stream and has no Type. That capability disappears on the 10th unless something replaces it
  • 11 blocks failed automated transform (alias-dependent calculate, filter shape, duplicate root keys) and need hand migration
  • 5 blocks need variables supplied to verify

Not done deliberately

  • 31 Solana(dataset: combined) queries left as-is. archive works and reaches 2024, but contradicts the "Solana realtime only" guidance, and realtime would silently truncate historical queries. Documented rather than silently changed
  • P3 subscriptions: 3 of 55 pages. Excluded 44 where a subscription would be wrong — deprecated-cube pages, historical/backfill pages, and syntax reference pages

Gaurav agarwal and others added 13 commits August 2, 2026 12:11
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 buddies2705 changed the title Docs: Solana RFQ, streaming matrix, retention, and BalanceUpdates migration Docs: Solana RFQ, cube streaming matrix, retention fixes, and the 10 Aug BalanceUpdates migration Aug 3, 2026
@buddies2705
buddies2705 merged commit 073d2ba into main Aug 3, 2026
1 check passed
buddies2705 added a commit that referenced this pull request Aug 3, 2026
Docs: fix contradictions found auditing #229
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