Service boundaries for a 13-service Ethereum mempool pipeline
Why decoding the mempool in real time is a distributed-systems architecture problem — a swappable message bus, a decode waterfall with graceful degradation, and service isolation enforced by convention.
Decoding Ethereum's mempool in real time looks, from the outside, like a blockchain problem: read pending transactions, figure out what they mean. In practice it's a distributed-systems architecture problem first — the hard part isn't decoding a single transaction, it's building a pipeline that keeps up with mempool throughput while staying correct, extensible and independently scalable across every stage of that decoding.
Thirteen services, one message bus, no direct calls
The platform splits into thirteen independently deployable services: a core service that subscribes to a node's pending-transaction feed, a decoder, an enricher, and a fan-out of domain-specific analytics services — gas prediction, DEX swap tracking, cross-chain bridge tracking, liquidation tracking, gas-auction tracking, sentiment aggregation — each consuming the decoded transaction stream independently. None of them call each other directly. Every cross-service interaction goes through a shared message-bus abstraction and a common internal library, enforced as a convention across the codebase rather than left to individual services to opt into.
The payoff of that discipline is scaling flexibility: gas-station and swap-service can each be scaled to their own load profile, restarted independently, or replaced entirely, without any other service in the pipeline needing to know it happened.
A transport layer that's swappable, not fixed
The message bus itself is RabbitMQ in production and an in-memory implementation for local development, selected by a single environment variable rather than two divergent code paths. That choice keeps the local development loop fast — the full 13-service stack runs without a broker to stand up first — while production gets the durability and cross-process delivery guarantees RabbitMQ provides. It's the same principle you'd apply to any infrastructure dependency that's expensive to run locally but necessary in production: abstract the interface, keep both implementations honest against the same contract.
Decoding as a pipeline stage, not a single function call
The decoder itself is architected as a waterfall rather than a single best-effort attempt: ABI-based decoding first, a Sourcify lookup next, then DEX-swap and liquidation-specific heuristics. Every stage that fails falls through to the next, and the pipeline always publishes a result — with an explicit failure flag when nothing matched — instead of silently dropping a transaction that didn't decode cleanly. Enrichment, the most expensive stage (fetching full calldata and trace data), only runs for the fraction of transactions the earlier stages couldn't resolve, which keeps the pipeline's average-case cost low without sacrificing coverage on the harder cases.
Closing the loop on decoder coverage
New contracts deploy faster than any team can manually maintain ABI coverage for them. Rather than treating unknown function selectors as a backlog for a human to work through, the platform runs a Model Context Protocol tool server and a periodic LLM-driven triage pass that can fetch missing ABIs, trigger traces, and propose new heuristic classifications on its own — gated behind dry-run and auto-apply flags, so the decoder's coverage compounds over time under human review rather than either stagnating or drifting unsupervised.
The architectural takeaway
None of the thirteen services is individually complex. What makes the platform work at mempool throughput is the boundary discipline between them — a message bus that doesn't leak implementation details into service code, a decode pipeline that degrades gracefully instead of failing hard, and an expensive path that's opt-in rather than the default. That's infrastructure design doing the work a cleverer single-service implementation couldn't.