This document defines the preferred vocabulary for Parseon code, API design, documentation, and future architecture work.
Naming should stay boring and precise. Clever names are cute until they become migration pain, API confusion, and a future archaeology project with grep.
| Term | Meaning | Notes |
|---|---|---|
Chain |
One EVM network indexed by Parseon. | Ethereum, Base, Arbitrum, Optimism, etc. |
Monitor |
User-defined indexing rule for a contract function. | Prefer this over Watcher. |
Target |
What a monitor matches. | Usually chain_id + contract address + function selector. |
Filter |
Optional condition applied after a call is matched and decoded. | Prefer this over Predicate, Where, or Condition. |
Cursor |
Per-monitor indexing progress. | Last indexed or finalized block for a monitor. |
BlockSource |
Adapter that fetches blocks, transactions, and receipts. | Prefer this over generic Provider in core abstractions. |
Storage |
Adapter that persists chains, monitors, progress, and decoded results. | PostgreSQL first, MongoDB later. |
Cache |
Adapter that stores fetched blocks or receipts temporarily. | Memory first, Redis later. |
Worker |
Runtime task that indexes one chain. | One worker per chain in the multi-chain architecture. |
Scheduler |
Component that decides which block ranges to fetch next. | Owns batching and concurrency decisions. |
DecodedCall |
A matched transaction call with decoded ABI parameters. | Core/domain term. |
DecodedEvent |
A matched EVM log with decoded event parameters and log identity. | Core/domain term. |
ResultRecord |
Storage-level persisted decoded call. | Internal persistence term. |
MonitorResult |
API representation of a persisted decoded call. | User-facing API term. |
Adapter |
Compile-time integration around the core. | Prefer this over Plugin for now. |
Sink |
Optional output destination for decoded data. | Kafka, webhook, ClickHouse, files, etc. |
Reorg |
Chain rewrite that invalidates already indexed blocks. | Must be handled by core logic. |
Finality |
Guarantee level of indexed data. | Exposed through API/status metadata. |
Use Monitor for the user-defined rule:
Monitor = Target + block range + optional Filter + Cursor
A monitor has an immutable definition and mutable operational state. Its chain,
target, block range, and filter are fixed at creation. enabled controls
pause/resume, while the worker owns cursor and completion progress.
Prefer:
POST /monitors
GET /monitors/{id}
PATCH /monitors/{id} { "enabled": false }
GET /monitors/{id}/results
POST /monitors/{id}/reindex # future dedicated operationAvoid using Watcher for public API, database entities, or new core models. Watcher sounds like a running process and collides with Worker.
Monitor behavior lives in core::monitor; runtime execution belongs to core::worker.
A monitor target defines what onchain call should be matched.
The human-readable ABI signature is accepted only when creating a monitor and
is not persisted. A function selector is the fixed four-byte dispatch value
derived from that signature, while an event topic0 is its fixed 32-byte
signature hash.
Typical target fields:
chain_id
address
selector
Recommended Rust-style shape:
pub struct MonitorTarget {
pub chain_id: ChainId,
pub address: Address,
pub selector: Selector,
}A filter is an optional condition applied after matching and decoding.
Use Filter for user-facing and internal naming. Avoid Predicate unless referring to implementation internals of an expression evaluator.
Example JSON filter DSL:
{
"and": [
{ "field": "tx.from", "op": "eq", "value": "0x1111111111111111111111111111111111111111" },
{ "field": "params.value", "op": "gte", "value": "1000000000000000000" }
]
}The first filter language is a bounded, versioned JSON AST compiled against the monitor ABI before indexing. It supports scalar equality, integer ordering, and short-circuit boolean composition over decoded parameters and the metadata already fetched for successful calls or events.
Keep the JSON frontend separate from the typed expression representation. A later textual language can add richer paths, arithmetic, composite values, table state, and SQL compilation without moving parsing or database concerns into worker evaluation.
Use BlockSource for core abstractions that read chain data.
Implementations may include:
JsonRpcSource
ErpcSource
EtherscanSource
FallbackSource
Reasoning:
Provideralready has specific meaning in EVM/RPC libraries.Sourceworks for JSON-RPC, eRPC, Etherscan, archive nodes, and fallback chains.- Etherscan is not equivalent to JSON-RPC and should usually be used for backfill, fallback, ABI discovery, or metadata enrichment, not primary live indexing.
Use Storage for primary state and queryable decoded results.
Examples:
PostgresStorage
MongoStorage
Use Sink for optional output destinations that receive decoded data but do not own Parseon state.
Examples:
KafkaSink
WebhookSink
ClickHouseSink
FileSink
Do not call Redis storage in core terminology. Redis is a cache unless it becomes an explicitly supported source of truth, which it should not by default.
Use Cache for temporary fetched data.
Examples:
MemoryBlockCache
RedisBlockCache
NoopBlockCache
Cache keys must be chain-aware:
chain_id + block_number
chain_id + block_hash
chain_id + tx_hash
Use different names for different layers:
| Layer | Term |
|---|---|
| Core | DecodedCall |
| Core | DecodedEvent |
| Storage | ResultRecord |
| API | MonitorResult |
Use DecodedCall for calldata and DecodedEvent for EVM logs; do not collapse these distinct result kinds into one domain term.
Use finality_status for user-facing result/block status.
Preferred values for early versions:
provisional
finalized
reorged
Internal lifecycle states may also include:
indexed
failed
Default API result queries should prefer finalized data where the chain and source can provide that guarantee.
Use Adapter for now.
Parseon should start with compile-time adapters backed by stable Rust traits:
Storage adapter
BlockSource adapter
Cache adapter
Sink adapter
Avoid runtime-loaded plugins in early versions. Dynamic loading in Rust adds ABI stability, versioning, async trait, panic-safety, and deployment complexity. That is a lot of machinery just to rename a dependency problem.
External plugin processes over gRPC or another protocol can be considered later if real user needs appear.
Prefer:
Monitor, not Watcher
Filter, not Predicate
Target, not Subscription
BlockSource, not Provider
Storage, not DB plugin
Cache, not Cache plugin
Worker, not Watcher
DecodedCall or DecodedEvent, not generic Event
MonitorResult, not Transaction
Cursor, not Offset
Adapter, not Plugin
Sink, not Export plugin
A good Parseon sentence should read like this:
Parseon runs chain workers. Each worker reads finalized calls and logs from a block source, matches them against monitor targets, decodes calldata into decoded calls and logs into decoded events, persists monitor results through storage, and uses cache adapters to reduce repeated reads.