The problem
By the time a transaction is mined, the interesting question about it — was this a sandwich attack, is this liquidation about to be profitable, is the gas market about to spike — is already answered and no longer actionable. That information lives in the mempool, for the seconds or minutes a transaction sits pending, and it arrives as raw, often truncated calldata that has to be decoded before it means anything.
The approach
Thirteen independently deployable services, communicating over a shared message bus (RabbitMQ in production, in-memory for local development, selected by a single environment variable), built on a common internal library so no service calls another directly. A core service subscribes to a node's pending- transaction feed and runs fast sandwich and EIP-1559 heuristics; a decoder turns calldata into structured intent; an enricher fills in what the decoder couldn't from truncated data; and a fan-out of analytics services — gas-station, swap-service, bridge-tracker, liquidation-tracker, auction-tracker, sentiment-service — each consume the decoded stream for their own domain. An API gateway aggregates all of it behind REST and server-sent events for the dashboard.
Decoding as a waterfall, not a gate
A transaction that can't be decoded is still useful — knowing that something
unrecognised is happening is itself a signal. The decoder tries ABI-based
decoding first, falls back to a Sourcify lookup, then DEX/swap and liquidation
heuristics, and publishes a decoded event either way, with success: false
when nothing worked, rather than dropping the transaction. Calldata is
truncated at 8KB by default to keep the hot path cheap; only the transactions
that actually need more get a background enrichment round-trip — fetching full
calldata and, where the node supports it, trace data — which is what moves
decode coverage from a baseline to meaningfully higher without paying the
enrichment cost on every transaction.
A self-improving decoder
Unknown function selectors don't sit in a dead-letter queue waiting for someone to notice. A Model Context Protocol tool server exposes ABI-fetch, trace and reclassification tools, and a periodic LLM-driven triage run summarises decoder failures and calls those tools to resolve them — gated behind dry-run and auto-apply flags, so decoder coverage improves without unreviewed changes reaching production silently.
Hardened by auditing the running system, not its docs
The platform's reliability comes from treating its own operational behaviour, not its architecture diagram, as the thing worth auditing. That practice directly shaped the liquidation-tracker's math — profitability now runs through an on-chain price oracle and protocol-specific collateralisation formulas instead of a single constant standing in for every protocol — and the gas-station's next-block prediction, which follows EIP-1559's actual single-preceding-block formula rather than a smoothed multi-block average, so it reacts correctly during congestion spikes rather than lagging behind them. It's the same discipline applied to the message bus itself: retry and dead-letter behaviour is verified against what the broker's acknowledgement API actually supports, not assumed from the client library's interface.
Design decisions worth noting
- Numeric precision is a rule, not a convention. All monetary and gas
values are required to travel as strings or
BigInt, never native JS numbers, specifically to avoid float precision loss on values where a rounding error changes a liquidation's profitability. - No service-to-service imports. Cross-service interaction is required to go through the shared library and the message bus, enforced by convention and audited for, so services stay independently deployable.
- The message bus is swappable by design, not by accident — running the whole 13-service stack against an in-memory bus for local development, and RabbitMQ in production, off a single environment variable, keeps the local dev loop fast without a second code path to maintain.
Case study
See this in production: Architecting a 13-service pipeline to decode the Ethereum mempool in real time.
Availability
Private. Built and operated in-house; not distributed under an open licence.