Skip to content

fix(build-web3): correct streams-reference against the live API, add CI gate - #8

Open
ferhatqn wants to merge 1 commit into
quicknode:mainfrom
ferhatqn:fix/streams-reference-api-accuracy
Open

fix(build-web3): correct streams-reference against the live API, add CI gate#8
ferhatqn wants to merge 1 commit into
quicknode:mainfrom
ferhatqn:fix/streams-reference-api-accuracy

Conversation

@ferhatqn

@ferhatqn ferhatqn commented Aug 18, 2026

Copy link
Copy Markdown

Every JavaScript filter example in streams-reference.md 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.

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

Example Published behaviour
Block Streams throws undefined reading 'length'
Transaction Streams throws undefined to a BigInt
Logs Streams throws undefined reading '0'
Receipt Streams throws undefined reading 'length'
Function Signature throws processedData is not defined
Available Utilities throws undefined to a BigInt
Complex Filter (DEX) throws undefined reading 'toLowerCase'
Monitor Specific Contract throws undefined reading 'toLowerCase'
Track Whale Transactions silent null
NFT Transfer Tracking throws undefined reading '0'
Monitor Program Logs silent null
Track SOL Transfers silent null

The three silent ones are the worst: no error, no data, indistinguishable from "nothing matched."

Root cause

stream.data is an array whose nesting depends on the dataset. The file treated it as a single object in all 16 usages — none indexed or iterated.

dataset shape
block, block_with_receipts data[block]
transactions, receipts, programs_with_logs data[block][item]
logs data[block][tx][log]

For logs the middle dimension is transactions, not blocks — on block 21000000 a block's entry holds 181 groups, one per transaction, each group's logs sharing one transactionHash. Nothing documented this.

Batch safety

The outer dimension is the batch: stream.data.length equals dataset_batch_size (default 1). Indexing stream.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

  • dataset enumlog / transaction / receipt are rejected with HTTP 400. Correct: logs / transactions / receipts. Table listed 5 of 19 valid values; now complete from the OpenAPI enum.
  • 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 untouched.)
  • Create Stream body — camelCase filterFunction and a nested destination object. The API takes snake_case filter_function (base64-encoded), a destination string enum, and separate destination_attributes.
  • metadata names — documented streamId; the API returns stream_id, all keys snake_case.
  • DEX example — filtered Swap 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 keys on event signature.
  • Solana examplesprograms_with_logs items have no programId; it's at programInvocations[].programId. Balance deltas come from instruction.accounts[].pre/postBalance, not meta.pre/postBalances.

Additions

  • A **Test:** line on every example naming network, block, and the result it produces. Track Whale Transactions is pinned to block 21000280 (a 2143 ETH transfer) since 1000+ ETH transfers are absent from most blocks.
  • Payload Shape section documenting nesting per dataset and the batch dimension.
  • Test a Filter section covering test_filter, including that block must be a string.

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.

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 null because 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 structured decodedLogs, 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.

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
ferhatqn force-pushed the fix/streams-reference-api-accuracy branch from d28820b to a6e514b Compare August 18, 2026 15:10
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