EvmQueryBuilder
Fluent API for building queries.Copy
import { EvmQueryBuilder } from "@subsquid/pipes/evm";
const query = new EvmQueryBuilder()
.addFields({
block: { number: true, hash: true },
log: { address: true, topics: true, data: true },
})
.addLog({
request: { address: ["0x..."] },
range: { from: 20000000 },
});
Required Fields
Must includeblock.number and block.hash:
Copy
.addFields({
block: {
number: true, // required
hash: true, // required
}
})
Select Fields
Block Fields
Copy
.addFields({
block: {
number: true,
hash: true,
parentHash: true,
timestamp: true,
size: true,
gasUsed: true,
gasLimit: true,
baseFeePerGas: true
}
})
Log Fields
Copy
.addFields({
log: {
address: true,
data: true,
topics: true,
transactionHash: true,
transactionIndex: true,
logIndex: true,
removed: true
}
})
Transaction Fields
Copy
.addFields({
transaction: {
hash: true,
from: true,
to: true,
value: true,
input: true,
gas: true,
gasPrice: true,
maxFeePerGas: true,
maxPriorityFeePerGas: true,
nonce: true
}
})
Filter Logs
Copy
.addLog({
request: {
address: ['0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'],
topic0: ['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'],
topic1: ['0x000000000000000000000000...'], // optional
},
range: {
from: 20000000,
to: 20000100
}
})
Multiple Contracts
Copy
.addLog({
request: {
address: [
'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC
'0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT
'0x6b175474e89094c44da98b954eedeac495271d0f' // DAI
]
},
range: { from: 20000000 }
})
Multiple Event Types
Copy
.addLog({
request: {
address: ['0x...'],
topic0: [
'0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // Transfer
'0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925' // Approval
]
},
range: { from: 20000000 }
})
Instead of manually looking up raw topic0 hashes, use
evmDecoder with
typed ABIs generated by @subsquid/evm-typegen. This approach provides type
safety and eliminates the need to find event signatures. See the Event
Decoding guide for details.Filter Transactions
Copy
.addTransaction({
request: {
from: ['0x...'],
to: ['0x...'],
sighash: ['0xa9059cbb'] // function selector
},
range: { from: 20000000 }
})
Filter Traces
Copy
.addTrace({
request: {
from: ['0x...'],
to: ['0x...'],
callType: ['call', 'staticcall']
},
range: { from: 20000000 }
})
Filter State Diffs
Track storage changes:Copy
.addStateDiff({
request: {
address: ['0x...']
},
range: { from: 20000000 }
})
Static vs Dynamic Queries
Static Query (in Source)
Copy
const query = new EvmQueryBuilder()
.addFields({ block: { number: true, hash: true } })
.addLog({
request: { address: ["0x..."] },
range: { from: 20000000 },
});
const source = evmPortalSource({
portal: "https://portal.sqd.dev/datasets/ethereum-mainnet",
query,
});
Dynamic Query (in Transformer)
Copy
// Start with minimal query
const source = evmPortalSource({
portal: "https://portal.sqd.dev/datasets/ethereum-mainnet",
query: new EvmQueryBuilder(), // empty
});
// Transformer adds to the query
async function main() {
const transformer = createTransformer({
query: ({ queryBuilder }) => {
queryBuilder
.addFields({
block: { number: true, hash: true },
log: { address: true, topics: true, data: true },
})
.addLog({
request: { address: ["0x..."] },
range: { from: 20000000 },
});
},
transform: async (data) => processData(data),
});
await source.pipe(transformer).pipeTo(target);
}
void main();
Query Flow
Best Practices
Only Request What You Need
Copy
// BAD - requests everything
.addFields({
block: { /* all fields */ },
log: { /* all fields */ },
transaction: { /* all fields */ }
})
// GOOD - requests only necessary fields
.addFields({
block: { number: true, hash: true },
log: { address: true, topics: true, data: true, transactionHash: true }
})
Use Specific Filters
Copy
// BAD - no filtering
.addLog({ range: { from: 20000000 } })
// GOOD - filter by address and topic
.addLog({
request: {
address: ['0x...'],
topic0: ['0x...']
},
range: { from: 20000000 }
})

