fix(build-web3): correct streams-reference against the live API, add CI gate - #8
Open
ferhatqn wants to merge 1 commit into
Open
fix(build-web3): correct streams-reference against the live API, add CI gate#8ferhatqn wants to merge 1 commit into
ferhatqn wants to merge 1 commit into
Conversation
Every JavaScript filter example in this reference threw or silently returned
nothing, and the documented `dataset` values are rejected by the API. Agents read
this file to generate filter code, so each defect became broken generated code.
Verified with POST /streams/test_filter (read-only, creates nothing) on Ethereum
blocks 21000000 / 21000280 and Solana slot 300000000.
## Defects
1. dataset enum — `log`, `transaction`, `receipt` are rejected with HTTP 400; the
API accepts `logs`, `transactions`, `receipts`. The table listed 5 of 19 valid
values; now complete, sourced from the OpenAPI enum.
2. stream.data shape — it is an array whose nesting depends on the dataset, and
was treated as a single object throughout. All 12 runnable examples failed:
Block / Receipt Streams Cannot read properties of undefined ('length')
Transaction / Utilities Cannot convert undefined to a BigInt
Logs / NFT Transfer Cannot read properties of undefined ('0')
DEX / Monitor Contract Cannot read properties of undefined ('toLowerCase')
Function Signature processedData is not defined
Whale, Solana x2 silent null — no error, no data
For `logs` the middle dimension is TRANSACTIONS, not blocks: the real shape is
data[block][tx][log]. On block 21000000 a block's entry holds 181 groups, one
per transaction, each group's logs sharing one transactionHash.
3. Batch safety — the outer dimension is the batch. `stream.data.length` equals
`dataset_batch_size` (default 1), so indexing `stream.data[0]` silently
processes only the first block. Every example now iterates. This is not
hypothetical: 9 of 20 streams on our own account run batch sizes of 5 or 50.
4. auth header — Management API examples used `Authorization: Bearer`; the API's
only securityScheme is `x-api-key`. The webhook destination's own Bearer
header is unrelated and left alone.
5. Create Stream body — was camelCase `filterFunction` with a nested
`destination` object. The API takes snake_case `filter_function`
(base64-encoded), a `destination` string enum, and separate
`destination_attributes`. Added the required-field list.
6. metadata field names — documented as `streamId`; the API returns `stream_id`,
and every metadata key is snake_case.
7. DEX swap example — filtered `Swap` events by router address, but the emitter
is always the pool. Block 21000000 has 22 Swap events and zero from a router,
so it could never match at any nesting depth. Now filters by event signature.
8. Solana examples — `programs_with_logs` items have no `programId`; it lives at
`programInvocations[].programId`. Balance deltas come from
`instruction.accounts[].pre/postBalance`, not `meta.pre/postBalances`.
## Additions
- A `**Test:**` line on every example naming the network, block, and the result
it produces, so a reader can confirm their setup before adapting the code.
Track Whale Transactions is pinned to 21000280 (a 2143 ETH transfer) because
1000+ ETH transfers are absent from most blocks.
- Payload Shape section documenting nesting per dataset and the batch dimension.
- Test a Filter section covering POST /streams/test_filter, including that
`block` must be a string and that a thrown filter still returns HTTP 201 with
the error nested in `result.error`.
## Verified after the change
Block Streams block @ 21000000 ok
Transaction Streams transactions @ 21000000 ok
Logs Streams logs @ 21000000 ok
Receipt Streams receipts @ 21000000 ok
Function Signature logs @ 21000000 ok
Available Utilities transactions @ 21000000 ok
Complex Filter Example logs @ 21000000 ok
Monitor Specific Contract logs @ 21000000 ok
Track Whale Transactions transactions @ 21000280 ok
NFT Transfer Tracking logs @ 21000000 ok
Monitor Program Logs programs_with_logs @ 300000000 ok
Track SOL Transfers programs_with_logs @ 300000000 ok
Key-Value Store Integration is the only example not executed; it needs a
pre-populated KV list.
ferhatqn
force-pushed
the
fix/streams-reference-api-accuracy
branch
from
August 18, 2026 15:10
d28820b to
a6e514b
Compare
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.
Every JavaScript filter example in
streams-reference.mdthrew or silently returned nothing, and the documenteddatasetvalues are rejected by the API. Agents read this file to generate filter code, so each defect became broken generated code.All findings verified with
POST /streams/test_filter— read-only, creates nothing — on Ethereum blocks 21000000 / 21000280 and Solana slot 300000000.12 of 12 runnable examples were broken
undefined reading 'length'undefined to a BigIntundefined reading '0'undefined reading 'length'processedData is not definedundefined to a BigIntundefined reading 'toLowerCase'undefined reading 'toLowerCase'nullundefined reading '0'nullnullThe three silent ones are the worst: no error, no data, indistinguishable from "nothing matched."
Root cause
stream.datais an array whose nesting depends on the dataset. The file treated it as a single object in all 16 usages — none indexed or iterated.block,block_with_receiptsdata[block]transactions,receipts,programs_with_logsdata[block][item]logsdata[block][tx][log]For
logsthe middle dimension is transactions, not blocks — on block 21000000 a block's entry holds 181 groups, one per transaction, each group's logs sharing onetransactionHash. Nothing documented this.Batch safety
The outer dimension is the batch:
stream.data.lengthequalsdataset_batch_size(default1). Indexingstream.data[0]silently processes only the first block and drops the rest.Not hypothetical — 9 of 20 streams on our own account run batch sizes of 5 or 50. A filter written with
[0]on a batch-50 stream processes 2% of its data with no error. Every example now iterates, matching the pattern the public docs' Go examples already use.Other defects
log/transaction/receiptare rejected with HTTP 400. Correct:logs/transactions/receipts. Table listed 5 of 19 valid values; now complete from the OpenAPI enum.Authorization: Bearer; the API's onlysecuritySchemeisx-api-key. (The webhook destination's own Bearer header is unrelated and untouched.)filterFunctionand a nesteddestinationobject. The API takes snake_casefilter_function(base64-encoded), adestinationstring enum, and separatedestination_attributes.streamId; the API returnsstream_id, all keys snake_case.Swapby router address, but the emitter is always the pool. Block 21000000 has 22Swapevents and zero from a router, so it could never match at any nesting depth. Now keys on event signature.programs_with_logsitems have noprogramId; it's atprogramInvocations[].programId. Balance deltas come frominstruction.accounts[].pre/postBalance, notmeta.pre/postBalances.Additions
**Test:**line on every example naming network, block, and the result it produces.Track Whale Transactionsis pinned to block 21000280 (a 2143 ETH transfer) since 1000+ ETH transfers are absent from most blocks.test_filter, including thatblockmust be a string.Verified after the change
Key-Value Store Integrationis the only example not executed — it needs a pre-populated KV list.Note for reviewers
A filter that throws still returns HTTP 201, with the error nested in
result.error:{ "logs": [], "result": { "error": "Cannot read properties of undefined (reading '0')" } }Status code alone does not reveal a broken filter, and a filter returning
nullbecause it crashed is indistinguishable from one that correctly matched nothing. That is how all 12 of these survived.Related
A companion PR fixes the same class of defect on the public docs page: quiknode-labs/docs#3444 — two examples there had the identical missing-outer-loop bug, plus a stale Seaport 1.1 address and three zero-activity sample addresses.
Not fixed here
The reference never mentions
decodeEVMReceipts, a built-in runtime helper returning structureddecodedLogs, and instead teaches manual'0x' + log.topics[1].slice(26)hex slicing throughout. Not wrong, but the hard way past a built-in. Swapping the decoding approach is a larger editorial change than a bug fix, so I left it — happy to follow up.