Home / Solutions / Infrastructure & Tooling
Infrastructure & Tooling

Blockchain data infrastructure for explorers, dashboards, and dev tools

When the product is built on chain data, the read path is the product. SQD serves logs, transactions, traces, and decoded transfers through one query shape across 225+ networks, and the same endpoint streams genesis backfill and live blocks. Your backfill code is your live code; the next chain is a dataset name, not a new integration.

225+ Networks
Genesis Full history
6 VMs One query shape
NDJSON Plain HTTP streaming
The problem

When the product is built on chain data, the read path is the product

An explorer's address page, a dashboard's chart, an alerting rule, a wallet's history tab: each one reads back what a contract or program did inside a block window. Built the naive way, that read is two stacks: an archive node behind eth_getLogs, with the per-call block-range caps that force pagination loops, and a separate Solana pipeline speaking a different RPC dialect. History and live split again into a backfill job to reconcile against a running feed, and every chain the product adds is another read path to build, host, and reconcile. The records are identical underneath; the cost is only in how many ways you have to read them.

One query, two VMs

Every surface of a data product is the same read

On the Portal, the read each surface performs, what a contract or program did inside a block window, is one request body. Below, the same conceptual query, USDC activity in one pinned block, runs against two different virtual machines.

EVM: Transfer logs on Ethereum

Filter the logs table by the USDC contract and the Transfer topic0 over block 23,000,000:

your terminal
curl -s -X POST https://portal.sqd.dev/datasets/ethereum-mainnet/stream \
-H 'content-type: application/json' \
-d '{
"type": "evm",
"fromBlock": 23000000, "toBlock": 23000000,
"logs": [{
"address": ["0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"],
"topic0": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]
}],
"fields": { "block": { "number": true }, "log": { "topics": true, "data": true, "transactionHash": true } }
}'

Decoded: the data word 0x0129d08a20 is 4,996,500,000, and USDC has 6 decimals, so 4,996.50 USDC moved from 0x28c6…1d60 to 0xf2ab…a3e8 in transaction 0x5946…bcac, block 23,000,000.

The block comes back as one NDJSON line carrying 31 Transfer logs. The first:

{
"transactionHash": "0x5946ef0de28db53ffe00f0fcdb4cc19c9da234819430bf957cc4c15200d8bcac",
"data": "0x0000000000000000000000000000000000000000000000000000000129d08a20",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x00000000000000000000000028c6c06298d514db089934071355e5743bf21d60",
"0x000000000000000000000000f2abf514dfbaf3323a5cb95bb2e4ab180a5ba3e8"
]
}

SVM: TransferChecked instructions on Solana

Same envelope, different table and filter keys: the instructions table, the Token program id, d1 0x0c for the TransferChecked discriminator, and a1 pinning the account at index 1, the mint, to USDC:

your terminal
curl -s -X POST https://portal.sqd.dev/datasets/solana-mainnet/stream \
-H 'content-type: application/json' \
-d '{
"type": "solana",
"fromBlock": 430000000, "toBlock": 430000000,
"instructions": [{
"programId": ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"],
"d1": ["0x0c"],
"a1": ["EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"],
"isCommitted": true
}],
"fields": { "block": { "number": true }, "instruction": { "accounts": true, "data": true, "instructionAddress": true, "transactionIndex": true } }
}'

The data field is base58. Decoded it is 0c 30023d0000000000 06: discriminator 12, amount 3,998,256 little-endian, 6 decimals, so 3.998256 USDC. accounts[1] is the USDC mint EPjF…Dt1v, in slot 430,000,000.

The slot comes back as one line carrying 16 committed instructions. The first:

{
"transactionIndex": 69,
"instructionAddress": [2],
"accounts": [
"DbjnPj7afLxfshxqwRZpzGWsF5x9kzw8H6vsaFuJ42Yq",
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"EvW6DwQnq3Vq2GQo3kRvxUXjxCzSk7jkZbnsDW8ARJTy",
"GmU33taGgRn7DHk5Pa8VXmjQGVk7au39iPMg9TgVE7aH"
],
"data": "giPfERcgyycMs"
}

One gotcha: on Solana, filtering by mint only catches TransferChecked. The plain Transfer instruction (discriminator 3) carries no mint account, just source, destination, and owner, per the token program's own instruction layout, so a mint-keyed instruction filter cannot see it. Counting all of a token's movements means resolving its token accounts or reading the token_balances table; the 16 rows above are the TransferChecked subset.

Getting these two rows elsewhere is two stacks: an archive node behind eth_getLogs, with the per-call block-range caps that force pagination loops (measured here), and a separate Solana pipeline speaking a different RPC dialect. Every chain a product adds is another read path to build, host, and reconcile. Here the second virtual machine changed the dataset name and the filter keys, nothing else. That is the property CoolWallet runs its transaction history pipeline on across 35+ chains, Bitcoin, Substrate, and Tron included, and the reason RAILGUN could take call tracing across four EVM chains from one integration.

Backfill and live

Backfill code is live code

A product that renders history walks the chain from the beginning; a feed that stays current follows the head. On the Portal both are the same query with a different window. This request scans Ethereum from block 0 through 46,147, the block holding the first transaction the chain ever processed:

your terminal
curl -s -X POST https://portal.sqd.dev/datasets/ethereum-mainnet/stream \
-H 'content-type: application/json' \
-d '{
"type": "evm",
"fromBlock": 0, "toBlock": 46147,
"transactions": [{}],
"fields": { "block": { "number": true }, "transaction": { "hash": true } }
}'

The 46,148-block window streams back as two lines, verbatim:

the full response
{"header":{"number":0}}
{"header":{"number":46147},"transactions":[{"hash":"0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060"}]}

A header line at the chunk boundary, and the one block in the window that carries a transaction: 0x5c50…2060 in block 46,147 (2015-08-07). Omit toBlock and the identical request follows the chain head instead, so the loop that backfills is the process you leave running in production. There is no separate historical API to reconcile against a live feed.

One gotcha: the stream skips empty blocks by default. That is the right behavior for a filtered feed and the wrong default for an explorer backend, which needs a row for every block: block pages, gap checks, per-block metrics. Add "includeAllBlocks": true to the body above and the same window returns 46,148 lines, one per block from 0 to 46,147, whether or not anything matched.

Playground

Try the Portal API

Live queries against real blockchain data. Pick a network, choose a query, hit run.

The public endpoint is free for development. See plans for dedicated portals and higher limits.

Network+200 more
Query
curl --compressed -X POST \
'https://portal.sqd.dev/datasets/base-mainnet/stream' \
-H 'Content-Type: application/json' \
-d '{
"type": "evm",
"fromBlock": 23000000,
"toBlock": 23000010,
"fields": {
"block": {"number":true,"timestamp":true},
"transaction": {
"hash": true,
"from": true,
"to": true,
"value": true,
"gasUsed": true
}
},
"transactions": [{}]
}'
Basebase-mainnet/stream
Raw L2 transaction throughput
Capabilities

Built for Infrastructure & Tooling at scale

One query shape across virtual machines

The same request envelope, a type, a block window, filter objects, and a fields selection, reads EVM logs, Solana instructions, Bitcoin inputs and outputs, Substrate events, and Hyperliquid fills. Adding a chain to an explorer or wallet backend is a dataset name and filter-key change, not a new client.

Backfill and live from the same endpoint

History from genesis and new blocks at the head stream through the same POST; only the block window changes. Omit toBlock and the stream follows the chain head. There is no separate historical API to reconcile against a live feed.

Every block, or only matching ones

By default the stream returns just the blocks where your filters matched, which keeps backfills compact. Set includeAllBlocks and every block in the window arrives, empty or not: block pages, gap detection, and per-block metrics come from the same query.

NDJSON into your own serving layer

The stream is newline-delimited JSON over plain HTTP, consumable from any language without an SDK. When you want a database under your API, the Apache-2.0 Squid and Pipes SDKs land the same data in PostgreSQL, ClickHouse, or Parquet. The layer your users hit stays yours.

Why SQD

What you would build yourself

The blocks are public. What a data product rebuilds elsewhere is the read path, and it multiplies with every virtual machine, every chain, and the split between history and live.

  • Cross-VM reads
    Yourself An archive node behind eth_getLogs for EVM, plus a separate Solana pipeline speaking a different RPC dialect.
    With SQD The same request envelope reads both; a second virtual machine changes the dataset name and filter keys, nothing else.
  • Adding a chain
    Yourself Every chain the product adds is another read path to build, host, and reconcile.
    With SQD One query shape spans 225+ networks, so a new chain is a dataset name, not a new client.
  • Backfill vs live
    Yourself A separate historical API to reconcile against a live feed.
    With SQD One POST serves both; omit toBlock and the same query follows the chain head, so backfill code is live code.

See this on your own contracts

Tell us the chains and contracts you care about, and we will show you the query that returns them.

Use cases

What teams build with SQD

  • Multi-chain block explorer backends
  • Public dashboards with genesis-depth history
  • Wallet platforms serving full transaction history
  • Alerting and monitoring products on live block streams
  • Developer tools that decode and inspect onchain activity
  • Data APIs built on decoded chain records

Frequently asked questions

Can SQD power a blockchain explorer backend?
Yes, as the read layer. The Portal serves blocks, transactions, logs, traces, and state diffs on EVM chains, instructions and balance changes on Solana, and transaction inputs and outputs on Bitcoin, from genesis to the chain head over one streaming API. Set includeAllBlocks to receive a row for every block in a window, which block pages and gap detection need. The serving API, cache, and UI stay yours: SQD is the data path underneath, not a hosted explorer.
Does SQD replace RPC nodes for a data product?
For bulk reads it does: backfill and live streaming come from one endpoint, without the per-call block-range and result caps that force eth_getLogs pagination loops (measured in the eth_getLogs guide). A product still uses an RPC node for the interactive path: submitting transactions and eth_call against current contract state. The Portal serves the record the chain produced; it does not serve reconstructed contract state such as balanceOf at a block.
Is the SQD tooling open source?
The Squid SDK and the Pipes SDK are Apache-2.0 licensed; the Portal server is AGPL-3.0. None of it is required to consume the data: the stream is NDJSON over plain HTTP, so a backend in any language reads it with a standard HTTP client, the same way every query on this page uses curl.
Get started

Your blockchain data infrastructure, handled.

Private Portal. Dedicated. Validated. Managed. Tell us what you're building, we'll show you what it looks like on SQD.