Processor contracts
These processors ship in the standard binary. Downstream projects can assemble a custom binary and register independent native processors through the public factory/registry API; see Build a custom processor.
blobs-money 1.6.0
Block-local Dencun-and-later blob economics and type-3 transaction output. It
accepts the filtered Xatu projection or complete normalized execution frames.
It declares the Header, Body, Senders, BlobFields, BlockSize and
ReceiptOutcome field groups, filtered to type-3 transactions.
All wei quantities are lossless integers.
history_mode = "on_demand" disables only automatic historical processor
coverage. It does not disable canonical live processing, normal JSON-RPC, or
WebSocket head subscriptions, and it still allows [processors.live_gap_fill],
which fills short ranges the live lane skips (see
Live gap fill).
The processor emits one atomic block change containing the block and all of its blob transactions. Materialized output stores only the block entity, transaction entities, and transaction-by-block index; snapshot queries reconstruct that same bundle without retaining a second permanent copy. The processor retains the exact post-Fusaka BPO activation schedule and keeps consensus-block size distinct from the exact execution-block RLP size.
Each block uses the parameters of the fork active at its timestamp. The blob
base fee and every blob burn use the protocol fee, EIP-4844’s
fake_exponential(1, excess_blob_gas, update_fraction), under every fork.
From Fusaka on, the EIP-7918 reserve price (execution base fee × 2^13 / 2^17)
is reported separately as reserveFeeWei and is not applied to the fee.
Version 1.5 fixes the fee and burn that 1.4 overstated wherever the reserve
bound; blobs.money’s own formula still clamps the fee to the reserve.
Each transaction reports its toAddress besides its senderAddress. EIP-4844
forbids contract creation, so every blob transaction has one: usually a
rollup’s inbox, which stays put while its batchers rotate. Version 1.6 adds it
and reads the Body group for it.
evm-events 1.1.0
A declarative event decoder. Configuration supplies contract
addresses, Solidity event ABI fragments, and output mappings. The
factory parses and validates the ABI before a source is opened, derives exact
topic-zero signatures, pushes address/topic predicates into capable sources,
and decodes without EVM execution. It declares the Logs field group.
An event’s abi is a human-readable fragment, one JSON ABI event object as a
block explorer shows it, or log0 (below). Fragments may use every ABI type
Solidity events use except function and fixed-point: address, bool,
bytes1..bytes32, uint/int widths from 8 through 256, bytes, string,
fixed-size and dynamic arrays, and tuples, nested at most six levels deep.
One element of a dynamic array holds at most 64 values, counting each nested
array and tuple as one, because the decoder copies that layout for every
element. A tuple is written with its component names, as
(uint256 amount, bool exactOutput) params or tuple(…) params. Every
parameter and tuple component needs a name (type name or
type indexed name), because decoded values are keyed by name. Unnamed
parameters, anonymous events, duplicate signatures, malformed keys, and more
than three indexed fields fail deterministically during doctor. The order of
the configured events is part of the configuration hash, so reordering them
creates a new processor instance.
Decoded values are exact strings: integers in decimal; address, bytesN,
and bytes as lowercase 0x hex; bool as true or false; and string as
text, with invalid UTF-8 replaced by U+FFFD. A tuple that is not indexed
flattens to one key per component, such as params.amount, and nested tuples
extend the path. An array, also one inside a tuple, is one JSON string whose
elements render as above, with tuple elements as objects in ABI order:
["1","2"] or [{"maker":"0x…","data":"0x01"}]. Ethereum stores an indexed
string, bytes, array, or tuple only as the keccak hash of its value, so
its value is that 32-byte topic as 0x hex. key_fields may name any value
key, including a flattened one.
Any contract can emit a log with a configured event’s topic zero. A log whose
topics or data do not decode as that event, such as an ERC-721 Transfer
(four topics, no data) against the ERC-20 Transfer ABI or a bool word
other than 0 or 1, is not that event: it is skipped rather than failing the
block, and the block’s mapped delta counts the skipped logs. Only the
canonical encoding decodes, the one Solidity’s encoder produces: offsets
without gaps or shared tails, zero padding, and no trailing bytes. A log that
breaks these rules, as a few old compilers’ event data did, is skipped and
counted rather than guessed at. The layout is checked before anything
decodes, so decoding takes memory in proportion to the log’s size.
abi = "log0" stores every topicless log of the configured contracts raw:
the entity’s event and signature are log0, and its values hold one
key, data, with the log data as 0x hex. Contracts such as Ekubo Core emit
LOG0 with a packed layout that no ABI describes, so the application decodes
those bytes. A log0 entry requires addresses, a processor may have one,
and its key_fields may name data. A topicless log cannot match a
topic-zero filter, so a processor with a log0 entry reads every log of its
contracts and selects the configured events itself. An anonymous event with
indexed parameters has topics but no signature, and is not decoded.
Without key_fields, an event’s key is its transaction hash and log index,
which no other event shares, so the processor is block-local: its history and
live blocks apply independently. A key_fields key can repeat across blocks,
and its entity holds the key’s newest event, so any keyed output makes the
processor ordered, like erc20-balances (ordered_state reduction and
canonical delivery ordering). It reduces blocks strictly in chain order: a
block emits one change per key, for its last event, and undoing a block
restores the key’s event from before it. Its live lane therefore waits for its
cold backfill, holding live blocks as pending deltas within
[budgets] pending_delta_bytes until history reaches them. Consumers read a
keyed instance’s stream through the generic consumer routes. Application
subscriptions and POST /admin/v1/materialization-jobs require a block-local
processor, so they refuse a keyed instance. With history_mode = "on_demand",
its history comes from the backfill command instead, which applies an
ordered processor’s history contiguously from start_block upward: each range
must start at the block after the last one applied, and the command refuses
any other start. Finalized-coverage compaction is block-local only, so a keyed
instance keeps one coverage row per block it applies, without bound, as other
ordered processors do.
publish = "finalized" holds unfinalized changes in the store until
verified finality promotes them. The stream then contains only finalized
apply records and system.finality markers; no undo is ever published.
Held bytes count against [processors.delivery] max_bytes from admission.
[[processors]]id = "evm-events"instance = "weth-transfers"version = "1.1.0"start_block = 12965000publish = "finalized"
[processors.state]mode = "durable"
[processors.artifacts]mode = "none"
[processors.output]mode = "window"
[processors.output.window]max_blocks = 216000
[processors.delivery]mode = "none"
[processors.checkpoint]mode = "automatic"keep = 3
[processors.undo]mode = "none"safety_blocks = 0
[processors.settings]addresses = ["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"]
[[processors.settings.events]]abi = "event Transfer(address indexed from, address indexed to, uint256 value)"
[processors.settings.events.output]collection = "weth.transfers"kind = "weth.transfer"key_fields = []Every entity/change uses evm.event.entity.v1/evm.event.change.v1, includes
block and transaction identity as camelCase 0x-hex fields, and renders
decoded values as exact decimal or hex strings. bucket_seconds plus _bucket_start in key_fields
supports deterministic wall-clock buckets from canonical block timestamps.
transaction-stats 1.0.0
An ordered A-to-B aggregate that retains constant-sized private working state:
transaction count, exact total value, cursor, checkpoints, and the bounded
unfinalized contribution journal. It declares the Body and Senders field
groups and sender/recipient predicates, and no receipt group, so it cannot tell
whether a transaction reverted: count and totalValueWei cover every
submitted transaction from the sender to the recipient. totalValueWei is the
submitted value, not the value the recipient received.
[[processors]]id = "transaction-stats"instance = "treasury-flow-total"version = "1.0.0"start_block = 15537394publish = "finalized"
[processors.state]mode = "checkpointed"
[processors.artifacts]mode = "none"
[processors.output]mode = "latest"
[processors.delivery]mode = "none"
[processors.checkpoint]mode = "automatic"keep = 3
[processors.undo]mode = "none"safety_blocks = 0
[processors.settings]from = "0x1111111111111111111111111111111111111111"to = "0x2222222222222222222222222222222222222222"complete_from_start = trueUse output = "latest" for a continuing aggregate or
[processors.delivery] emit = "job_result" for a bounded backfill. Job-result jobs commit state
and coverage for every block but spool only the final aggregate; after
acknowledgement the job is complete/reclaimable and deletion remains an
explicit operator action.
rollup-txs 1.0.0
A block-local processor for rollups’ L1 transactions other than blobs: calldata
batches, state root and output proposals, and proof submissions. It declares
the Header, Body, Senders, Calldata and ReceiptOutcome field groups,
filtered to the rules’ recipients. A transaction matches a rule when it is sent
to to, from from when set, calls selector when set, carries
chain_id_arg as its first ABI word when set (shared settlement contracts
serving several chains), in a block whose timestamp lies in [since, until).
The first matching rule per recipient wins, so list the more specific rules
first. A rule also names the rollup its transactions belong to and their
purpose (data, state, or proof), which the reported rows carry. Type-3
transactions never match: blobs-money reports them. Only top-level calls
match; a call made through a multisig or another contract is not seen.
The processor supports Ethereum mainnet only, and only from London (block
12,965,000) on, because executionBurnedWei and tipWei need the base fee
that earlier blocks lack. Startup refuses another chain and a pre-London
start_block. since and until are UNIX timestamps in seconds, below 10^10
(the year 2286), not block numbers or milliseconds: a block matches from
since (inclusive) up to until (exclusive). Startup refuses a since or
until below mainnet’s genesis timestamp (1438269973), as any block number
is, and one of 10000000000 or more, as any timestamp in milliseconds is: a
millisecond until would never close its window, and a millisecond since
would never match. It also refuses a window whose since is not below its
until. A rule with chain_id_arg also needs a selector: the chain id is
the argument after it, and without one the rows would render selector: null,
so calls to different functions could not be told apart.
Every block emits one rollups.block change (rollups.block-bundle.v1), also
when nothing matched, so a consumer can tell a block without rollup activity
from one it never received. Materialized output stores each block’s bundle by
block number in the rollups.blocks collection (rollups.entity.v1) and
renders it as the same JSON. The bundle is camelCase JSON:
block:network,blockNumber,blockHash,parentHash, andtimestamp. The bundle carries no finality of its own: the block’s finality is thefinalityof the change envelope, or of the retained entity, that carries the bundle.transactions, in block order:network,blockNumber, andblockHash, repeated from the block so that each transaction stands alone as a row; thentxHash,transactionIndex,rollupIdandpurpose(data,state, orproof), which are the matched rule’srollupandpurpose,success,senderAddress,toAddress,selector,gasUsed,executionBurnedWei= base fee × gas used, andtipWei= (effective price − base fee) × gas used.
A reverted transaction is reported like the others, with success: false,
because its gas was paid. selector is the matched rule’s selector, or null
when that rule has none: the first bytes of a calldata batch are batch data,
not a function selector. Hashes, addresses, and selectors are 0x followed by
lowercase hex digits: 64 for a hash, 40 for an address, and 8 for a selector.
gasUsed, executionBurnedWei, and tipWei are decimal strings;
blockNumber, timestamp, and transactionIndex are JSON numbers. The
rules and the start_block are the processor’s identity: changing either
needs a new instance.
[[processors]]id = "rollup-txs"instance = "rollup-txs-1"version = "1.0.0"history_control = "application_subscriptions"history_mode = "on_demand"start_block = 26000000
[processors.state]mode = "durable"
[processors.output]mode = "none"
[processors.delivery]mode = "until_acknowledged"
[[processors.delivery.consumers]]id = "app"required = truelease_ttl = "5m"
[processors.checkpoint]mode = "automatic"keep = 3
[processors.undo]mode = "unfinalized"safety_blocks = 256
[[processors.settings.rules]]rollup = "base"purpose = "state"to = "0x43edb88c4b80fdd2adff2412a7bebf9df42cb40e"selector = "0x82ecf2f6"Xatu serves this processor’s history from its transaction projection, for the rule recipients (see data sources).
erc20-balances 1.1.0
An ordered watchlist ledger derived from canonical ERC-20 Transfer logs.
Configuration declares watched addresses, an optional token allowlist, a start
block, and whether the opening coverage is complete. It declares the Logs
field group.
The stored method is explicitly erc20_transfer_ledger. Rebasing,
reflection, and non-standard tokens are not represented as general EVM
balances. Any contract can emit a Transfer log, so a log with that
signature that is not a canonical ERC-20 transfer (three topics, zero-padded
addresses, and a 32-byte value), such as an ERC-721 transfer, is skipped
rather than failing the block; the block’s mapped delta counts the skipped
logs.
A transfer the ledger cannot apply proves it incomplete for that token and
holder: an outgoing transfer above the derived balance means transfers before
the start block or outside the ERC-20 model, such as a rebasing or forged
token, and an incoming one can overflow it. The pair is then marked
incomplete: incompleteFrom holds that block, complete is false,
balance keeps its last derived value, and later transfers for the pair are
ignored. Undoing that block restores the derived pair. Only a token in the
tokens allowlist with complete_from_start = true fails the processor
instead, because the configuration declares that ledger complete.
[[processors]]id = "erc20-balances"instance = "erc20-watchlist"version = "1.1.0"start_block = 1234567
[processors.state]mode = "durable"
[processors.output]mode = "latest"
[processors.delivery]mode = "window"
[processors.checkpoint]mode = "automatic"keep = 3
[processors.undo]mode = "unfinalized"safety_blocks = 256
[processors.coverage]verification_segment_blocks = 8192
[processors.settings]addresses = ["0x1111111111111111111111111111111111111111"]tokens = ["0x2222222222222222222222222222222222222222"]complete_from_start = trueblock-summary 1.2.0
A receipt-free, block-local view of every Ethereum execution block. It declares
the Header and Body field groups, then publishes timestamp, parent/hash
identity, gas limit and usage, decimal base fee, blob gas fields, transaction
count, exact execution-block size when available, and finality. Blocks come
from verified material or, under the trusted-dataset policy, from Xatu’s
dataset-declared header and transaction projections, whose body indices and
payload transaction count must agree. Dataset rows carry no execution-block
size, so sizeBytes is null for them. A block a dataset summarized first
matches the same block from verified material, as when the live lane overlaps a
Xatu backfill, and keeps null. The reverse, remapping a block that verified
material summarized from a dataset, as a recompute may, is refused as a
conflicting apply. Summaries are retained by block hash with a canonical
block-number index; the query extension exposes /latest and
/blocks/{number}. It never executes the EVM and does not request receipts.
The compact [blocks] preset supplies the standard checkpointed lifecycle,
full query output, 64 MiB/24 hour delivery window, and undo records kept until
256 blocks past finality. Advanced configurations can override those policies
explicitly.
uniswap-observations 2.1.0 / uniswap-latest 2.0.0
Two configured-pool contracts share the same exact event decoder:
- Uniswap V2
Sync(uint112,uint112)reserve observations; - Uniswap V3
Initialize(uint160,int24)andSwap(address,address,int256,int256,uint160,uint128,int24)square-root-price observations.
They store exact reserves and sqrtPriceX96, never floating-point prices. V3
swap observations also retain amount0 and amount1 as signed decimal int256
values, preserving the exact pool deltas needed to derive volume. Applications
combine these values with verified token metadata.
uniswap-observations is block-local and identifies immutable observations by
pool, block hash, and log index; it supports independent history/live delivery
and full or windowed SQLite retention. Version 2.1 also maintains a query-only
latest-observation pointer per pool so new clients can bootstrap immediately
before following the immutable stream. Transaction hashes are deliberately not
part of this processor’s output, allowing archive and P2P sources to omit
transaction bodies when acquiring filtered pool logs. Both processors declare
the Logs field group.
uniswap-latest is ordered/canonical and retains only the latest observation
per pool for slim price queries.
[[processors]]id = "uniswap-observations" # or "uniswap-latest"instance = "usdc-weth-observations"version = "2.1.0" # use 2.0.0 with uniswap-lateststart_block = 12369621history_control = "node_owned"history_mode = "on_demand"
[processors.state]mode = "checkpointed"
[processors.artifacts]mode = "none"
[processors.output]mode = "full"
[processors.delivery]mode = "none"
[processors.checkpoint]mode = "automatic"keep = 3
[processors.undo]mode = "unfinalized"safety_blocks = 256
[processors.settings]
[[processors.settings.pools]]address = "0x3333333333333333333333333333333333333333"kind = "v3"Pool factory discovery is intentionally separate from event reduction. A catalog adapter can discover pools and render these immutable processor configs; each processor identity then commits to the exact pool set.
Built by Smart-Byte · Legal notice (Impressum) · Privacy