Titandocs

Indexer

The only writer to Postgres — two input streams, one schema, and a migration ordering rule that breaks deploys.

server/indexer — Go. Consumes the Canton ledger stream and the engine's signed WAL, and projects both into Postgres.

This is the deployed tree. The standalone titan/indexer at the repository root is stale.

Two inputs, one schema

Canton ledger (gRPC stream) ──┐
                              ├──► Postgres
Engine WAL (firehose)      ───┘
SourceTables
Ledgervaults, deposit_receipts, withdrawal_requests, withdrawal_counters, batch_manifests, user_batches
Engine WALengine_orders, engine_fills, engine_triggers, engine_positions, engine_events

The engine_* tables are the mirror: an off-chain projection that lets the BFF answer history queries without asking the engine.

Migrations

Numbered and applied in order. Two matter for the BFF contract:

MigrationAdds
0014_tpsl_v2engine_orders.reduce_only, tp_*, sl_*; engine_triggers.size, limit_price
0015_fired_limitengine_triggers.fired_size, fired_limit

Migrate the indexer before deploying the api. The BFF's Drizzle queries emit an explicit column list, so an api deployed ahead of these migrations fails on the first request with column "size" does not exist — not gradually, immediately.

Setup

Database

createdb titan

The server DB user is titanadmin on port 5433.

gRPC stubs

Generated once from proto/ into internal/ledger/pb/. Commit them on your branch if you vendor them.

make proto

Configure

config.yaml plus .env. See .env.example.

Run

make run

Its own REST + WebSocket

The indexer exposes read endpoints and a WebSocket of its own. The interface does not use them — it goes through the BFF, which aggregates and adds auth. The indexer's API is for tooling, debugging, and operational queries.

Two WebSocket endpoints in one stack is a recurring source of confusion. The one browsers connect to is the BFF's /v1/stream. The indexer's is not exposed publicly.

Build tags

The indexer uses Go build tags to select ledger backends and optional features. make targets wrap the right combinations — prefer them over hand-rolled go build invocations, which is also where the multi-package -o trap from the engine page applies.

End-to-end verification

The repository ships an E2E check that drives activity through the engine and asserts the projection matches. It is the fastest way to confirm a schema change did not silently drop a field — a nullable column that never gets written looks identical to a healthy one at the type level.

Lag

The BFF exposes bff_engine_max_seq. Comparing it against the indexer's high-water mark gives indexer lag directly.

Live WebSocket frames bypass the indexer entirely (they come from SSE), so during lag live data is correct while REST reads are stale. A fresh order book beside an empty position list is this, not a UI bug.

On this page