When building blockchain applications, you rarely need to process every event on the chain. Adder provides a composable pipeline filter system that lets you focus on exactly the data your application needs — before it reaches your event handler.
In this lesson you'll learn all four filter types: event type, address, policy ID, and stake pool.
Two Filtering Approaches
In Lesson 201.2 you saw events flow through handleEvent unfiltered. Adder gives you two ways to filter:
Approach
How it works
When to use it
Manual (in handler)
Check evt.Type and return early
Learning, simple one-off filters
Pipeline filters
Add filter components before the handler
Production code, composable filters
This lesson covers both, starting with manual filtering for clarity and moving to pipeline filters for the address, policy, and pool cases.
Prerequisites
- Completed Lesson 201.2 (Adder connected to your Dolos instance)
- Your Dolos instance running on preprod
- The Adder starter kit cloned and configured
Part 1: Filter by Event Type
What are Event Types?
Adder produces three primary event types:
Event Type
Description
chainsync.block
A new block added to the chain
chainsync.transaction
A transaction within a block
chainsync.rollback
The chain rolling back
How to Filter
Open ./cmd/adder-publisher/main.go and modify handleEvent:
func handleEvent(evt event.Event) error {
if evt.Type != "chainsync.transaction" {
return nil
}
slog.Info(fmt.Sprintf("Transaction: %v", evt))
return nil
}
Returning nil early for unwanted types is the standard pattern for event-driven filtering.
To filter for blocks instead, change the condition to evt.Type != "chainsync.block". Block events contain slot number, block hash, issuer pool ID, and transaction count. They appear less frequently (~every 20 seconds on preprod).
Optional: Quieter Output
To suppress the ChainSync status messages and see only your filtered events, comment out WithStatusUpdateFunc in inputOpts:
// input_chainsync.WithStatusUpdateFunc(updateStatus),
Part 2: Filter by Address
Address filtering uses Adder's pipeline filter system — a more powerful approach than manual checks.
The Filter Packages
import (
filter_chainsync "github.com/blinklabs-io/adder/filter/chainsync"
filter_event "github.com/blinklabs-io/adder/filter/event"
)
filter_event— filters by event type (block, transaction, rollback)filter_chainsync— filters by Cardano-specific criteria (address, policy, pool)
Note: In Adder v0.36.0+,
filter/chainsyncis renamed tofilter/cardano. The API is identical — only the import path and type names change. Checkgo.modto see which version you have.
The Example Script
Open ./cmd/event-address-filter/main.go. This script demonstrates pipeline filters:
// Event type filter — only transactions
filterEvent := filter_event.New(
filter_event.WithTypes([]string{"chainsync.transaction"}),
)
p.AddFilter(filterEvent)
// Address filter
filterChainsync := filter_chainsync.New(
filter_chainsync.WithAddresses(
[]string{
"addr_test1qz...", // your preprod address
},
),
)
p.AddFilter(filterChainsync)
Make sure to update the Config at the top of main() with your Dolos socket path and magic 1, the same as in Lesson 201.1.
-- INSERT SCREENSHOT 1 HERE: event-address-filter/main.go showing updated Config and filters --
Running It
go run ./cmd/event-address-filter
The indexer starts silently. Once you send a transaction from your monitored address, it will appear in the terminal within ~20 seconds (next block). All other network transactions are silently dropped by the pipeline.
-- INSERT SCREENSHOT 2 HERE: terminal output showing a filtered transaction event --
Composing Filters
You can monitor multiple addresses by adding them to the slice:
filter_chainsync.WithAddresses([]string{"addr_test1q...", "addr_test1q..."})
Filters are composable — both the event type filter and the address filter must pass before an event reaches your handler.
Part 3: Filter by Policy ID
Policy ID filtering catches all transactions that mint, burn, or transfer any asset under a given policy.
What is a Policy ID?
Every native asset on Cardano is identified by a Policy ID (56-character hex string) and an optional Asset Name. Filtering by policy ID catches the entire token family — every asset minted under that policy.
How to Filter
In ./cmd/event-address-filter/main.go, replace the address filter with a policy filter:
filterChainsync := filter_chainsync.New(
filter_chainsync.WithPolicies(
[]string{
"29aa6a65f5c890cfa428d59b15dec6293bf4ff0a94305c957508dc78", // Andamio access token
},
),
)
-- INSERT SCREENSHOT 3 HERE: main.go showing WithPolicies filter --
Choosing a Policy ID
You need a policy with active transactions on preprod. Options:
- Andamio access token (shown above) — has regular activity on preprod
- Your own tokens from Module 102
- Any policy with recent activity on Cardanoscan Preprod
In the output, look for your policy ID in the mint field (if tokens are being minted/burned) or in the multi-asset values of transaction outputs.
Part 4: Filter by Stake Pool
Pool ID filtering operates at the block level — you're tracking which pools produce blocks, not which transactions those blocks contain.
How to Filter
You need to change both filters: event type to chainsync.block, and the chainsync filter to WithPoolIds:
filterEvent := filter_event.New(
filter_event.WithTypes([]string{"chainsync.block"}),
)
filterChainsync := filter_chainsync.New(
filter_chainsync.WithPoolIds(
[]string{
"pool1ynfnjspgckgxjf2zeye8s33jz3e3ndk9pcwp0qn8kq9dv4geus6",
},
),
)
Adder accepts pool IDs in both bech32 (pool1...) and hex (56-char) formats.
-- INSERT SCREENSHOT 4 HERE: main.go showing block event filter with WithPoolIds --
Choosing a Pool to Track
Visit Cardanoscan Preprod Pools and pick an active pool. The key field in the block output is the issuer — this will match your filter.
Blocks from a specific pool arrive infrequently — a pool may produce one every few minutes depending on its stake. Be patient.
You'll Know You're Successful When:
- Event type filtering: Only the event type you selected appears in your terminal
- Address filtering: Your test transaction appears after being included in a block; all other transactions are silently dropped
- Policy filtering: Transactions involving your policy ID are detected; you can identify the policy in the output
- Pool filtering: Block events appear only for your target pool's blocks
Common Issues
No events after adding filter — Check for typos in event type strings (chainsync.transaction, chainsync.block, chainsync.rollback are case-sensitive). For address/policy filters, verify the value exactly matches what's on-chain.
Magic mismatch / connection errors — Confirm your Config in event-address-filter/main.go has the correct socket path and magic 1 for preprod.
Still seeing all events — Verify p.AddFilter(filterChainsync) is called in the pipeline setup.
No pool blocks after several minutes — The pool may be inactive on preprod. Check its recent block history on a block explorer and try a different pool.
Tips
Tip 1: Filters are composable — add both a filter_event and a filter_chainsync to a pipeline and only events passing both will reach your handler.
Tip 2: For debugging, temporarily remove all filters to confirm events are flowing through, then add filters back one at a time.
Tip 3: To combine address and policy filtering (e.g., track a specific token at a specific address), use WithAddresses and WithPolicies together in the same filter_chainsync.New(...) call.
Next Steps
- Module 202 — querying the blockchain for historical and global data
- Consider what filtering logic your project needs and sketch out which filter types you'd combine