This page provides ready-to-use filter examples for common use cases. Copy and modify these examples to match your specific needs.
Before You Start
Every filter function must follow these rules:
- Name: Your function must be named
Filter(Go) ormain(JavaScript) - Return value: Return data to deliver it, or return
nil/nullto skip delivery for that block - Input: The function receives a payload with
.Data(Go) /.data(JavaScript) and.Metadata/.metadata
- Go
- JavaScript
type Payload struct {
Data []interface{} `json:"data"`
Metadata map[string]interface{} `json:"metadata"`
}
func Filter(qn *qn.QNContext, payload Payload) interface{} {
// payload.Data contains the blockchain data
// payload.Metadata contains stream information
return payload.Data // Return data to deliver, or nil to skip
}
function main(stream) {
// stream.data contains the blockchain data
// stream.metadata contains stream information
return stream.data; // Return data to deliver, or null to skip
}
Basic Examples
Extract Block Hash and Number
Dataset: block
Extracts the block hash and converts the block number from hex to decimal.
- Go
- JavaScript
type Payload struct {
Data []Block `json:"data"`
Metadata map[string]interface{} `json:"metadata"`
}
type Block struct {
Hash string `json:"hash"`
Number string `json:"number"`
}
func Filter(qn *qn.QNContext, payload Payload) interface{} {
numberDecimal, _ := strconv.ParseInt(strings.TrimPrefix(payload.Data[0].Number, "0x"), 16, 64)
return map[string]interface{}{
"hash": payload.Data[0].Hash,
"number": numberDecimal,
}
}
function main(stream) {
const block = stream.data[0];
return {
hash: block.hash,
number: parseInt(block.number, 16)
};
}
Filter ERC-20 Transfer Events
Dataset: block_with_receipts
Finds all ERC-20 token transfers in a block. The Transfer event has a specific topic signature and exactly 3 topics (event signature + from + to addresses).
- Go
- JavaScript
type Payload struct {
Data []Entry `json:"data"`
Metadata map[string]interface{} `json:"metadata"`
}
type Entry struct {
// Keep receipts as raw JSON so the full original object is preserved —
// only Receipt/Log below are decoded to evaluate the filter condition.
Receipts []json.RawMessage `json:"receipts"`
}
type Receipt struct {
Logs []Log `json:"logs"`
}
type Log struct {
Topics []string `json:"topics"`
}
func Filter(qn *qn.QNContext, payload Payload) interface{} {
const transferTopic = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
var filteredReceipts []json.RawMessage
totalReceipts := 0
for _, entry := range payload.Data {
totalReceipts += len(entry.Receipts)
for _, raw := range entry.Receipts {
var receipt Receipt
if err := json.Unmarshal(raw, &receipt); err != nil {
continue
}
relevant := false
for _, log := range receipt.Logs {
if len(log.Topics) == 3 && log.Topics[0] == transferTopic {
relevant = true
break
}
}
if relevant {
filteredReceipts = append(filteredReceipts, raw)
}
}
}
if len(filteredReceipts) == 0 {
return nil
}
return map[string]interface{}{
"totalReceipts": totalReceipts,
"filteredCount": len(filteredReceipts),
"receipts": filteredReceipts,
}
}
function main(stream) {
const data = stream.data;
const TRANSFER_TOPIC = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';
const transferReceipts = data.receipts.filter(receipt => {
return receipt.logs.some(log =>
log.topics[0] === TRANSFER_TOPIC && log.topics.length === 3
);
});
return {
totalReceipts: data.receipts.length,
filteredCount: transferReceipts.length,
receipts: transferReceipts
};
}
Monitor Specific Addresses
Dataset: block_with_receipts
Tracks transactions and events involving a list of addresses. Useful for monitoring wallets or contracts.
- Go
- JavaScript
type Payload struct {
Data []Entry `json:"data"`
Metadata map[string]interface{} `json:"metadata"`
}
type Entry struct {
Block Block `json:"block"`
Receipts []Receipt `json:"receipts"`
}
type Block struct {
Transactions []Transaction `json:"transactions"`
}
type Transaction struct {
From string `json:"from"`
To string `json:"to"`
}
type Receipt struct {
Logs []Log `json:"logs"`
}
type Log struct {
Topics []string `json:"topics"`
}
func Filter(qn *qn.QNContext, payload Payload) interface{} {
addresses := []string{
"0x56220b7e25c7d0885159915cdebf5819f2090f57",
"0x351e1b4079cf180971025a3b35dadea1d809de26",
"0xa61551e4e455edebaa7c59f006a1d2956d46eecc",
}
addressSet := make(map[string]bool)
paddedAddressSet := make(map[string]bool)
for _, addr := range addresses {
lower := strings.ToLower(addr)
addressSet[lower] = true
hex := lower[2:]
paddedAddressSet["0x"+strings.Repeat("0", 64-len(hex))+hex] = true
}
var matchingTransactions []Transaction
var matchingReceipts []Receipt
for _, entry := range payload.Data {
for _, tx := range entry.Block.Transactions {
from := strings.ToLower(tx.From)
to := strings.ToLower(tx.To)
if (tx.From != "" && addressSet[from]) || (tx.To != "" && addressSet[to]) {
matchingTransactions = append(matchingTransactions, tx)
}
}
for _, receipt := range entry.Receipts {
matches := false
for _, log := range receipt.Logs {
if len(log.Topics) > 1 && (paddedAddressSet[log.Topics[1]] ||
(len(log.Topics) > 2 && paddedAddressSet[log.Topics[2]])) {
matches = true
break
}
}
if matches {
matchingReceipts = append(matchingReceipts, receipt)
}
}
}
if len(matchingTransactions) == 0 && len(matchingReceipts) == 0 {
return nil
}
return map[string]interface{}{
"transactions": matchingTransactions,
"receipts": matchingReceipts,
}
}
function main(stream) {
const data = stream.data;
// Addresses to monitor (lowercase)
const addresses = [
'0x56220b7e25c7d0885159915cdebf5819f2090f57',
'0x351e1b4079cf180971025a3b35dadea1d809de26',
'0xa61551e4e455edebaa7c59f006a1d2956d46eecc'
];
// Create lookup sets for efficient matching
const addressSet = new Set(addresses);
const paddedAddressSet = new Set(
addresses.map(addr => '0x' + addr.slice(2).padStart(64, '0'))
);
// Find transactions where address is sender or receiver
const matchingTxs = data.block.transactions.filter(tx =>
addressSet.has(tx.from?.toLowerCase()) ||
addressSet.has(tx.to?.toLowerCase())
);
// Find receipts where address appears in event logs
const matchingReceipts = data.receipts.filter(receipt =>
receipt.logs?.some(log =>
log.topics?.length > 1 && (
paddedAddressSet.has(log.topics[1]) ||
paddedAddressSet.has(log.topics[2])
)
)
);
// Skip delivery if no matches found
if (matchingTxs.length === 0 && matchingReceipts.length === 0) {
return null;
}
return {
transactions: matchingTxs,
receipts: matchingReceipts
};
}
Decoding EVM Events
The decodeEVMReceipts (JavaScript) / qn.DecodeEVMReceipts (Go) function transforms raw event logs into human-readable data using contract ABIs. This section covers the basics of decoding and then shows practical examples that combine decoding with filtering for common use cases like tracking token transfers, NFT sales, and DeFi activity.
How it works:
- Matches event signatures in logs with the provided ABIs
- Decodes parameters according to their types (addresses, integers, etc.)
- Returns structured data with named parameters in a
decodedLogsarray
Parameters:
receipts— Array of transaction receipts to decodeabis— Array of contract ABIs (strings or objects)
Instead of hardcoding ABIs in your filter, you can store them in Key-Value Store for easier updates and reuse. See Storing Contract ABIs.
Decode ERC-20 Transfers
Dataset: block_with_receipts
Decodes Transfer events into readable format with from, to, and value fields.
- Go
- JavaScript
type Payload struct {
Data []Entry `json:"data"`
Metadata map[string]interface{} `json:"metadata"`
}
type Entry struct {
Receipts []interface{} `json:"receipts"`
}
func Filter(qn *qn.QNContext, payload Payload) interface{} {
const erc20ABI = `[{
"anonymous": false,
"inputs": [
{"indexed": true, "type": "address", "name": "from"},
{"indexed": true, "type": "address", "name": "to"},
{"indexed": false, "type": "uint256", "name": "value"}
],
"name": "Transfer",
"type": "event"
}]`
// Collect receipts from every block in the batch
var receipts []interface{}
for _, entry := range payload.Data {
receipts = append(receipts, entry.Receipts...)
}
decoded := qn.DecodeEVMReceipts(receipts, []string{erc20ABI})
// Filter for receipts with decoded logs
var result []interface{}
for _, r := range decoded {
receipt, ok := r.(map[string]interface{})
if !ok {
continue
}
if logs, ok := receipt["decodedLogs"].([]interface{}); ok && len(logs) > 0 {
result = append(result, receipt)
}
}
return map[string]interface{}{"result": result}
}
function main(stream) {
const erc20Abi = `[{
"anonymous": false,
"inputs": [
{"indexed": true, "type": "address", "name": "from"},
{"indexed": true, "type": "address", "name": "to"},
{"indexed": false, "type": "uint256", "name": "value"}
],
"name": "Transfer",
"type": "event"
}]`;
const data = stream.data;
const decoded = decodeEVMReceipts(data[0].receipts, [erc20Abi]);
// Keep only receipts with decoded events
const results = decoded.filter(
receipt => receipt.decodedLogs?.length > 0
);
return { results };
}
Decoded output example:
{
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"name": "Transfer",
"from": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
"to": "0x6f1cdbbb4d53d226cf4b917bf768b94acbab6168",
"value": "138908045566"
}
Track NFT Marketplace Sales (OpenSea)
Dataset: block_with_receipts
Decodes OpenSea Seaport OrderFulfilled events to track NFT sales.
- Go
- JavaScript
type Payload struct {
Data []Entry `json:"data"`
Metadata map[string]interface{} `json:"metadata"`
}
type Entry struct {
Receipts []interface{} `json:"receipts"`
}
func Filter(qn *qn.QNContext, payload Payload) interface{} {
// OpenSea Seaport OrderFulfilled event
// test on Ethereum, 21292520 block
const seaportABI = `[{
"anonymous": false,
"inputs": [
{"type": "bytes32", "name": "orderHash", "indexed": false},
{"type": "address", "name": "offerer", "indexed": true},
{"type": "address", "name": "zone", "indexed": true},
{"type": "address", "name": "recipient", "indexed": false},
{
"components": [
{"type": "uint8", "name": "itemType"},
{"type": "address", "name": "token"},
{"type": "uint256", "name": "identifier"},
{"type": "uint256", "name": "amount"}
],
"type": "tuple[]",
"name": "offer",
"indexed": false
},
{
"components": [
{"type": "uint8", "name": "itemType"},
{"type": "address", "name": "token"},
{"type": "uint256", "name": "identifier"},
{"type": "uint256", "name": "amount"},
{"type": "address", "name": "recipient"}
],
"type": "tuple[]",
"name": "consideration",
"indexed": false
}
],
"name": "OrderFulfilled",
"type": "event"
}]`
const seaportAddress = "0x00000000006c3852cbef3e08e8df289169ede581"
// Collect receipts from every block in the batch
var receipts []interface{}
for _, entry := range payload.Data {
receipts = append(receipts, entry.Receipts...)
}
decoded := qn.DecodeEVMReceipts(receipts, []string{seaportABI})
var result []interface{}
for _, r := range decoded {
receipt, ok := r.(map[string]interface{})
if !ok {
continue
}
logs, ok := receipt["decodedLogs"].([]interface{})
if !ok {
continue
}
for _, l := range logs {
logMap, ok := l.(map[string]interface{})
if !ok {
continue
}
address := strings.ToLower(fmt.Sprintf("%v", logMap["address"]))
name := fmt.Sprintf("%v", logMap["name"])
if address == seaportAddress && name == "OrderFulfilled" {
result = append(result, receipt)
break
}
}
}
return map[string]interface{}{"result": result}
}
function main(stream) {
const seaportAbi = `[{
"anonymous": false,
"inputs": [
{"type": "bytes32", "name": "orderHash", "indexed": false},
{"type": "address", "name": "offerer", "indexed": true},
{"type": "address", "name": "zone", "indexed": true},
{"type": "address", "name": "recipient", "indexed": false},
{
"components": [
{"type": "uint8", "name": "itemType"},
{"type": "address", "name": "token"},
{"type": "uint256", "name": "identifier"},
{"type": "uint256", "name": "amount"}
],
"type": "tuple[]",
"name": "offer",
"indexed": false
},
{
"components": [
{"type": "uint8", "name": "itemType"},
{"type": "address", "name": "token"},
{"type": "uint256", "name": "identifier"},
{"type": "uint256", "name": "amount"},
{"type": "address", "name": "recipient"}
],
"type": "tuple[]",
"name": "consideration",
"indexed": false
}
],
"name": "OrderFulfilled",
"type": "event"
}]`;
const SEAPORT_ADDRESS = '0x00000000006c3852cbEf3e08E8dF289169EdE581'.toLowerCase();
const data = stream.data;
const decoded = decodeEVMReceipts(data[0].receipts, [seaportAbi]);
const results = decoded.filter(receipt =>
receipt.decodedLogs?.some(
log => log.address?.toLowerCase() === SEAPORT_ADDRESS &&
log.name === 'OrderFulfilled'
)
);
return { results };
}
Next Steps
- Using Key-Value Store in Filters — Access Quicknode's Key-Value Store product directly from your filter code to store ABIs, maintain watchlists, and build stateful filters
- What are Filters? — Learn about filter execution environment and available utilities