Data access · 9 min read
One query shape, every VM: declarative cross-chain data
Most ways of getting onchain data make you commit before you see a single row: define a schema, deploy an indexer, wait for it to sync. SQD's Portal inverts that. You POST one JSON-shaped request, network plus table plus filters plus a block window, and the matching raw rows stream back. The same shape reads EVM logs, Solana instructions, and Bitcoin inputs and outputs against any contract on any supported chain, immediately. This guide shows three real query bodies and the one field that trips people up. Every result below is from a live Portal query.
1. The shape: network, table, filters, window
Every Portal request is a POST to /datasets/{network}/stream with a JSON body. The body always has the same four parts: a type for the virtual machine, a fromBlock and toBlock window, one or more filter objects named after the table you want, and a fields selection. The response streams back as newline-delimited JSON, one object per line. That envelope does not change between chains.
- typeevm
- tablelogs
- filteraddress + topic0
- windowfromBlock, toBlock
- typesolana
- tableinstructions
- filterprogramId + discriminator
- windowfromBlock, toBlock
- typebitcoin
- tableoutputs
- filterscriptPubKeyAddress
- windowfromBlock, toBlock
2. EVM: logs by address and topic
On an EVM chain the useful table is usually logs, filtered by a contract address and a topic0 event signature. This reads USDC Transfer events on Base. Scope it to one contract over a tight range: a single stream response is bounded, so an over-broad scan does not fail loudly, it returns a partial window that stops early at a block boundary, and you continue from the last block received.
The first row the query returns is a raw log, exactly as the chain stores it: topics[0] is the Transfer signature, topics[1] and topics[2] are the indexed from and to (each left-padded to 32 bytes), and data is the value:
Decoded against the Transfer signature, that is a transfer of 20.66844 USDC (data 0x013b6018 = 20668440, with 6 decimals) in transaction 0x22e7…2378.
3. Solana: instructions by program
Solana has no indexed event logs in the EVM sense; its logs table holds free-text program log lines, not topic-keyed events you filter by address and signature. The unit of activity is the instruction, identified by a programId and an Anchor discriminator. Same envelope, different table and filter: this reads Jupiter's sharedAccountsRoute instructions, where the 8-byte discriminator d8 selects the instruction kind.
A committed match comes back with its place in the call tree and the full ordered account list:
The instructionAddress: [3] places it in the transaction's call tree (top-level, inner, and so on); the ordered accounts list begins with the Token program and includes the mints in the route. The same record also exposes whether the instruction succeeded, which is the subject of the Solana decoding guide.
4. Bitcoin: inputs and outputs
Bitcoin has no contracts and no logs. Value moves through transaction inputs and outputs, and those are the tables. The same request shape attaches them inline:
Bitcoin uses Bitcoin Core's field names: an input's sender is prevoutScriptPubKeyAddress, an output's recipient is scriptPubKeyAddress. A real transaction (a89a5662…3f3d30) from block 954,236 spends one input into a payment and the change, each output tagged by its script type:
- inputbc1qxp3gemd4xe0s4d7kxe3dt8t7ndwp5yg7kl63460.01640293
- outputbc1q6jd3mvuy7wgdy402t3wg3k6cs62dteunc80w5q0.00741561witness_v0_keyhash
- outputbc1qxp3gemd4xe0s4d7kxe3dt8t7ndwp5yg7kl63460.00895269witness_v0_keyhash (change)
Following value through these inputs and outputs is the whole of Bitcoin flow analysis, covered in the Bitcoin UTXO guide.
5. The type-field trap
The one thing that does change, and the one place a cross-chain query goes wrong, is the type field and the table name. They have to match the dataset's virtual machine. Two cases catch people out: HyperEVM is an EVM chain, while Hyperliquid fills are their own machine on a separate dataset. Check a dataset's virtual machine and tables before you query it.
- ethereum-mainnet, base-mainnet evm logs, transactions, traces, state_diffs
- hyperliquid-mainnet (HyperEVM) evm logs, transactions (not fills)
- hyperliquid-fills hyperliquidFills fills
- solana-mainnet solana instructions, transactions, balances, token_balances, rewards, logs
- bitcoin-mainnet bitcoin inputs, outputs (no logs, no contracts)
portal_list_networks returns the virtual machine and table set for every dataset, which is the reliable way to pick the right type.
6. Why this is harder elsewhere
An indexer framework asks you to author a schema, deploy it, and wait for a sync before any data exists to query, and that work is per project and per chain. The declarative model returns raw rows on any contract immediately, in the same shape across virtual machines.
The hard part a provider has to solve to offer this is normalizing very different virtual machines, an account-and-log model, an instruction model, and a UTXO model, into one declarative interface without flattening away what makes each one useful. That normalization is the work; the uniform request is the result. Getting the three rows above anywhere else usually means three integrations: an EVM indexer or archive RPC for the log, a Solana-specific decoder for the instruction, and a Bitcoin or UTXO API for the output, each with its own query language. Here it is the same POST three times.
For the same idea applied to one asset across chains, see stablecoin data, which adds time alignment across chains. For the architecture behind landing many chains in one place, see multi-chain indexing. To run these queries from an agent rather than by hand, start with the Portal MCP setup.
Frequently asked questions
What does a declarative blockchain query mean?
Is the query shape really the same across EVM, Solana, and Bitcoin?
What is the type-field trap when querying multiple chains?
Why is querying any contract immediately different from using a subgraph?
How do I align a query window across chains that produce blocks at different rates?
Related guides
Querying several chains at once?
See how one query model feeds analytics across chains on the analytics solution page.