Query data
You query Shinzo data with GraphQL, and there are two places to run it. A local-first app queries its embedded DefraDB instance through the app-sdk helpers, with no network call. A direct-query app POSTs the same query to a Host client's /api/v0/graphql endpoint over HTTP. Same language, same collections, same filter operators on both. Only the transport changes. Connect your app to a Host covers the wiring.
Here are the patterns you'll reach for most. The examples use primitive collections (blocks, transactions, logs), and they all work against a View's output collection too.
Collection names are prefixed with <Chain>__<Network>__, derived from the chain.name and chain.network settings of the Generator client that indexed the data (for example <Chain>__<Network>__Block or Optimism__Mainnet__Block). The examples below use the <Chain>__<Network>__ placeholder. Substitute the prefix that matches your chain. See the chain config for details.
Get the latest N documents
Order by a field and cap the result with limit. This is the pattern behind most "recent activity" displays.
{
<Chain>__<Network>__Block(limit: 10, order: { number: DESC }) {
_docID
number
timestamp
hash
}
}
The same shape works for a View. This query fetches the 10 most recent decoded events from a View collection:
{
EventView(limit: 10, order: { blockNumber: DESC }) {
hash
from
to
blockNumber
logAddress
event
arguments
}
}Page through results
offset skips documents before limit applies, so stepping it walks a collection in pages:
{
<Chain>__<Network>__Block(order: { number: DESC }, limit: 10, offset: 20) {
number
hash
}
}
That returns items 21 through 30 of the ordering. There is no cursor pagination: offset is a plain skip. For steadily walking a growing collection, filter instead of skipping. Keep the last number you saw and ask for everything after it, which starts each page exactly where the last one ended:
{
<Chain>__<Network>__Block(
filter: { number: { _gt: 25930000 } }
order: { number: ASC }
limit: 10
) {
number
hash
}
}Fetch a document by DocID or CID
When you already know a document's _docID, pass it as the docID argument to fetch exactly that document:
{
<Chain>__<Network>__Transaction(docID: "bae-6ab5ece1-26a9-529d-a8af-3d10557672af") {
_docID
blockHash
blockNumber
hash
to
from
value
}
}
Documents are content-addressed too. Pass a commit CID as the cid argument and you get the document back at exactly that version:
{
<Chain>__<Network>__Transaction(cid: "bafyreifaiu62wsgf64tdgdeejyvnhuwio7yvkxfufadxe5yfjaf5w4cf6u") {
_docID
blockHash
blockNumber
hash
to
from
value
}
}{
"data": {
"<Chain>__<Network>__Transaction": [
{
"_docID": "bae-6ab5ece1-26a9-529d-a8af-3d10557672af",
"blockHash": "0x118954c3455addda1889a648d56faf8a7a2ab67909b473b06c7a9cc1981e73bc",
"blockNumber": 25937618,
"from": "0xf30b758081001716bBF99688C5233F5C74530eb0",
"hash": "0x06b7ab30c7d5d705ffa142acba266de475013a9e5617f1b87903663a98760c73",
"to": "0xf15A1F564669d29045D50698778DD7dFA1e1D07a",
"value": "7341990419000"
}
]
}
}
You usually get a CID from a document's _version field or from an attestation record. Verify data with signatures and CIDs covers that flow. The example DocIDs and CIDs on this page come from a live Host; Hosts prune old data over time, so if one no longer resolves, substitute a current one.
Filter by field values
The filter argument narrows results with operators like _eq, _geq, _and, and _like. This query returns blocks above a height:
{
<Chain>__<Network>__Block(filter: { number: { _geq: 19540000 } }) {
_docID
number
hash
}
}
Combine conditions with _and. This query returns Transfer events decoded from one contract:
{
EventView(
filter: {
_and: [
{ logAddress: { _eq: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" } }
{ event: { _eq: "Transfer" } }
]
}
limit: 10
) {
hash
from
to
arguments
blockNumber
}
}
Conditions inside _or behave like _and's opposite: a document matches when any one of them holds. This pattern finds both sides of a transfer, where the address is either the sender or the recipient:
query TransactionsInvolving($address: String!) {
<Chain>__<Network>__Transaction(
filter: {
_or: [
{ from: { _eq: $address } }
{ to: { _eq: $address } }
]
}
limit: 10
) {
hash
blockNumber
from
to
value
}
}
List fields take a quantifier. _any matches when at least one element satisfies the condition, which is the usual way to filter Log.topics. Since topics[0] is the event signature hash, this query pulls every log of one event type:
query LogsByTopic($topic: String!) {
<Chain>__<Network>__Log(filter: { topics: { _any: { _eq: $topic } } }) {
address
topics
data
blockNumber
transactionHash
}
}
The full operator table lives in the Viewkit reference.
Reuse queries with variables
Named queries take variables, so one query serves any block number or address:
query BlockByNumber($blockNumber: Int!) {
<Chain>__<Network>__Block(filter: { number: { _eq: $blockNumber } }) {
hash
number
timestamp
transactions {
hash
from
to
value
}
}
}
A direct-query app passes the values in the standard variables field of the request body:
{
"query": "query BlockByNumber($blockNumber: Int!) { ... }",
"variables": { "blockNumber": 23901130 }
}
One thing to know on the signed path: the request signature commits to the canonical JSON of query and variables together, so a new variable value means a new signature. Generate the envelope per request rather than caching one. Query your first View shows the hashing step. An embedded app does no signing, so it can reuse queries freely.
Get a block with nested data
Relations are nested in the query, so one round trip fetches a block with its transactions and their logs:
{
<Chain>__<Network>__Block(limit: 1) {
_docID
number
timestamp
hash
gasUsed
gasLimit
baseFeePerGas
parentHash
miner
transactions {
hash
transactionIndex
_docID
logs {
transactionHash
address
topics
data
}
}
}
}
Nested selections accept their own filter, order, and limit arguments, so you can shape each level independently.
Count the transactions in a block
There is no aggregate count field, but the transactionIndex values within a block are zero-based and contiguous. Fetch the highest transactionIndex and add one:
{
<Chain>__<Network>__Block(filter: { number: { _eq: 23901130 } }) {
number
transactions(
limit: 1
filter: { blockNumber: { _eq: 23901130 } }
order: { transactionIndex: DESC }
) {
transactionIndex
}
}
}
The total transaction count is the returned transactionIndex plus 1.
Check who signed a document
Each document exposes the CIDs of its commits in _version. The signature field on those entries is part of the schema but is not populated today: Generator clients sign per block rather than per document. To see who signed, query the BlockSignature collection for the block your document belongs to:
{
<Chain>__<Network>__BlockSignature(filter: { blockNumber: { _eq: 25938055 } }) {
blockNumber
blockHash
merkleRoot
signatureIdentity
signatureType
signatureValue
}
}
signatureIdentity is the public key of the Generator client that produced the block, and signatureValue is its ES256K signature over the block's Merkle root of document CIDs. For what these signatures cover, attestation records, and CID navigation, see Verify data with signatures and CIDs.
What a failed query looks like
A query that fails inside GraphQL returns the standard error envelope, with data absent or partial:
{
"errors": [
{
"message": "..."
}
]
}
Check for errors before trusting data. Rejections at the billing gate look different: a plain HTTP status with a plain text body, such as 403 with forbidden: stale or future request. Errors covers both layers with the full status tables.
Need help
- For onboarding and technical support, join the Shinzo Discord.
- To report a documentation bug or request a feature, open an issue in the docs repo.
- For a technical issue with the Host client, open an issue in the shinzo-host-client repo.