Work2025A more human blockchain explorer

Terrain

A Solana block explorer stack built from the messy parts up.

Built from the messy parts up

Most people building in crypto slap a UI on top of someone else's API and call it a day. Fair enough. We went the other way and built the machinery underneath it.

Terrain turns raw Solana and Raydium-style market activity into something humans can actually use: price charts, wallet history, transaction feeds, trader rankings, live token updates, the lot. Under the hood that meant parsing ugly blockchain data, figuring out what actually happened, storing the important bits, and pushing it back out in realtime.

Three parts, one system: a Rust parser engine, a TypeScript backend, and a frontend that makes all that chain chaos readable.

Gallery

Terrain Parser

Blockchains don't hand you neat little labels like "swap", "transfer", or "this wallet dumped the token and left." They hand you a pile of low-level state changes and basically wish you good luck.

So we built a Rust parser that takes raw Solana transactions and turns them into stuff you can actually use: swaps, prices, balances, live updates.

It does more than just index chain data. It figures out what happened, who likely did it, and feeds the rest of the system from there.

The part where raw blockchain data stops being gibberish.

This was proper systems work: Rust crates, worker pools, batching, retries, caches, Docker, AWS, Kafka, WebSockets. Less "look I made an app," more "we built the engine room too."

Pipeline walkthrough

Data pipeline from chain transaction to live client update.

Playback

1000 tx / 2.3s

One pipeline pass from raw transaction intake to live client output.

3s1000 tx

Each wave shows one pass moving through the pipeline.

batch1000 transactionssynced burst

Batch completion

1000 tx completed

done in 2.3s
  1. 01processing

    Raw txstream

    Solana RPC and Kafka payloads arrive as noisy transaction data.

    Encoded tx + status meta
    InputKafka raw topic
    decode payload
    Outputtransaction + meta
    slotblockTimemeta.err
  2. 02queued

    CtTransactionparser

    A shared Rust engine normalizes signer, UBO, fees, token deltas, and inner instructions.

    block_parser
    Inputtransaction + meta
    normalize semantics
    OutputCtTransaction
    signerubotoken deltas
  3. 03queued

    Actionreconstruction

    Low-level value changes resolve into swaps, transfers, and wallet-aware events.

    CtAction
    InputValueChange[]
    group deltas
    Outputswap / transfer
    combineinferlabel
  4. 04queued

    Pool-awarepricing

    Raydium pool context, SOL reference price, and pool metadata produce market prices.

    PriceItem
    Inputpool id + SOL/USD
    derive market price
    OutputPriceItem
    PoolMetaMoka cacheprice_usd
  5. 05queued

    Enrichedevent

    Structured token updates leave the ingest worker with token and pool headers.

    TokenUpdate
    InputPriceItem
    batch + publish
    OutputTokenUpdate
    headersroundinglz4
  6. 06queued

    Productoutputs

    Postgres receives analytics while subscribed clients receive live price updates.

    PRICE_UPDATE
    InputTokenUpdate
    fan out
    OutputDB + token room
    token_prices_newJOIN_ROOMPRICE_UPDATE

Terrain Backend

The API nobody sees first, but everything depends on.

Once the parser did its job, this service turned the data into actual product features. Token charts. Transaction history. Wallet views. Trader leaderboards. The sort of stuff users click around in without thinking too much about how annoying it is to make.

The interesting part here is that it doesn't just dump chain data back at the frontend and walk away. It calculates things like realized P/L, ROI, bought vs sold volume, unrealized exposure, trader rankings, filtered history, and wallet-level views. Basically: raw blockchain activity in, readable trading intelligence out.

Also included: versioned APIs, request tracing, Sentry, mock endpoints for frontend work, Docker, Fly, AWS. The boring grown-up stuff that keeps things alive.

Built in Node, TypeScript and Postgres, the backend combines precomputed analytics, cached transaction records, live Solana RPC enrichment, and parser-service fallbacks. Which is a fancy way of saying: it does whatever it needs to do to return something useful, fast.

The part where raw blockchain data stops being gibberish.

superhuge

Terrain note

Terrain Frontend

Because raw chain data looks like nonsense unless you do the work.

Most blockchain explorers feel like they were designed by and for people who enjoy suffering. Huge tables, mystery labels, JSON vibes, good luck out there.

No big deal.

Terrain took a more useful route. Search-first, fast to navigate, and built around the idea that users want to jump straight to a wallet, transaction, token or protocol without getting lost in some overcomplicated menu.

Once there, the UI translates blockchain movement into something readable: who sent what, who received what, what got swapped, what changed, and why it matters.

It also pulls in protocol views, wallet filters, transaction drill-downs, live token pricing, websocket updates, and client-side chart transformations.

Domain model

A few stable models make the product readable.

Terrain does not push raw chain payloads straight into the UI. It shapes them into stable transaction, token, and balance models that the rest of the product can actually build on.

That modeling layer is what makes live pricing, holder changes, wallet views, and feed updates feel coherent instead of improvised.

One stack, not a facade

Terrain is a Solana block explorer stack built from the messy parts up: parser, backend, frontend, and the operational glue that makes the whole thing feel fast instead of fragile.

What it actually took

So in short, Rust workspace, multi-crate parser core, CtTransaction, Solana transaction/meta payloads, instruction-vector walks, inner-instruction traversal, signer and UBO heuristics, fee surfaces, owner-level token balance deltas, token-account-level diffs, Raydium AMM pool extraction, pool-id resolution, quote/base price math, SOL/USD anchoring, Kafka consumers, Kafka republish, async channels, worker pools, batched processing, Postgres persistence, price-update listeners, holder-ingest signature pagination, historical replay, parser-debugger flows, Moka cache hits, connection pools, exponential backoff, Axum listeners, WebSocket fan-out, Socket.IO token rooms, multi-stage Docker, env-driven config, AWS CDK, ECS services, Fargate tasks, ALB wiring, snapshot diffing, and just enough distributed-systems trauma to keep it honest.

Architecture X-Ray

Separate services, one event-processing platform.

kafka-ingest

Consumes raw transactions, parses pricing, republishes structured events.

Kafka in -> TokenUpdate out

block_parser

Central domain engine for transaction semantics, actions, pool lookup, and pricing.

RPC/meta -> CtTransaction

db-saver

Persists price and balance updates into analytics tables.

Kafka event -> Postgres

axum-webserver

Fans out live token prices to Socket.IO rooms by token address.

Kafka event -> WebSocket

holder-ingest

Replays historical signatures for holder and balance analysis.

RPC history -> BalanceUpdate

infra

Ships separate Rust binaries through Docker and AWS ECS/Fargate.

Docker -> ECS services