Project

A more human blockchain explorer

Terrain

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

blockchaindata productinterface

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.

Move through the transaction

Raw chain data is not a product.

Terrain turns low-level state changes into an explanation a person can act on. Drag the signal to move between both views.

Raw transactionParser trace / RPC meta
Slot281,991,842Block246,889,104Compute109,773 / 200k CUFee0.0000175 SOL
Instruction tracedepth · 2
  1. 001ComputeBudget1111set_compute_unit_limit200,000 CU
  2. 011ComputeBudget1111set_compute_unit_price12,500 μ-lamports
  3. 021ATokenGPvbdGVxr1b2hvcreate_idempotentsuccess
  4. 031675kPX9MHTjS2zt1qfrraydium_amm::swap_base_ininvoke
  5. 03.12TokenkegQfeZyiNwAJbtransfer_checked100.387 SOL
  6. 03.2211111111111111111111transfer0.000005 SOL
  7. 03.32TokenkegQfeZyiNwAJbtransfer_checked270.90 USDC
  8. 03.42TokenkegQfeZyiNwAJbsync_nativesuccess
  9. 041JUP6LkbZbjS1jKKwapdrouteexact_in
Program logs8 / 31
  1. 01Program 675kPX9M invoke [1]
  2. 02Program log: Instruction: SwapBaseIn
  3. 03Program TokenkegQ invoke [2]
  4. 04Program log: amount_in=100387000000
  5. 05Program log: minimum_out=269440000
  6. 06Program TokenkegQ consumed 4645 of 174128 CU
  7. 07Program 675kPX9M consumed 92118 of 190400 CU
  8. 08Program 675kPX9M success
Account diffs4 / 17
#03 signer / writable7xKXt…9QmB
−100.387 SOL
#07 pool vault A6UeJg…vL2d
+100.387 SOL
#08 pool vault B8FqWj…3HnA
−270.90 USDC
#12 owner ATA4nPzV…wK7c
+270.90 USDC
signature 5Kp9aAV7…Ym4rpreTokenBalances[7] → postTokenBalances[7]err: nullfinalized
Terrain recapSuccess
What happened?

This wallet added liquidity, received two assets and redeemed rewards about five minutes ago.

Added liquidity100.387 SOLLido pool
Redeemed rewards2 assets$270.90 USD

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.

Fair warning: this bit gets technical

The engine room. We’ll walk you through it.

Terrain was not just a nice interface on top of someone else’s API. We built the machinery underneath it: raw Solana data, a Rust parser, event streams, analytics and the product surface. You do not need to know every term. Follow one transaction down and watch each layer make it more useful.
Your way through the machineryStart at Solana. Every stop adds one useful thing: evidence, meaning, continuity, context, then clarity.
  1. SolanaAdds Evidence

    The raw chain

    RPC payloads, inner instructions, account changes and noisy metadata arrive first. In plain English: plenty of evidence, no useful answer yet.

  2. Rust parserAdds Meaning

    Transaction semantics

    The Rust parser identifies signers, token deltas, swaps, transfers, pools and prices. This is where the system works out what actually happened.

  3. Event streamAdds Continuity

    One enriched signal

    Kafka joins parsed activity with persistence and live enrichment, then publishes one dependable event downstream. This keeps the answer moving without falling apart.

  4. API + analyticsAdds Context

    Product-ready intelligence

    Cached records, history and live prices become profit and loss (P/L), return on investment (ROI), rankings and wallet-level intelligence. This is where chain activity becomes product intelligence.

  5. Terrain product surface with live token, balance and transaction intelligence.
    InterfaceAdds Clarity

    One readable product surface

    The complexity finally resolves into search, wallet intelligence, transaction explanations and live markets. The user gets the useful bit, not the plumbing.

Raw evidenceReadable decision

The useful bit

All that machinery, so the product can feel simple.

That is Terrain in one line: serious infrastructure, sharp product thinking, and an interface that never asks the user to care how hard any of it was.
Got something equally complicated?Good. Those are usually the interesting ones.
Bring us the messy part