Skip to content

feat: scaffold monorepo, implement Phase 1 indexer core, and wire CI#4

Merged
Depo-dev merged 16 commits into
mainfrom
dev
May 21, 2026
Merged

feat: scaffold monorepo, implement Phase 1 indexer core, and wire CI#4
Depo-dev merged 16 commits into
mainfrom
dev

Conversation

@Depo-dev
Copy link
Copy Markdown
Owner

@Depo-dev Depo-dev commented May 21, 2026

This is the foundational pull request for Trident. It takes the repository from an empty README to a fully scaffolded, CI-gated monorepo with a working Phase 1 indexer pipeline.


Changes

Monorepo structure

Full directory and workspace layout for all four layers: crates/ (Rust), services/api/ (Go), sdk/typescript/, database/, docker/, and docs/. Root Cargo.toml workspace, MIT licence, .gitignore, and .env.example with documented variables.

Database

Production-ready PostgreSQL schema — soroban_events with computed topic_0/topic_1 columns, composite and GIN indexes, system_state for cursor tracking, indexed_contracts for the contract registry, and ledger_metadata for gap detection. First migration mirrors the schema.

Rust — common crate

TridentError (4 variants via thiserror) and the canonical SorobanEvent struct shared across all Rust crates.

Rust — indexer crate (Phase 1 core)

Full event indexing pipeline:

  • RPC client — JSON-RPC 2.0 getEvents with cursor pagination
  • Parser — base64 XDR decode of ScVal topics and data into SorobanEvent; all primitive variants handled, Map/Vec recurse into JSON, addresses encode to strkey; events from failed contract calls are filtered out
  • DB modulesqlx inserts into soroban_events, ledger_metadata, and system_state cursor management
  • Redis moduleXADD to trident:events stream for real-time WebSocket delivery
  • Streamer loop — cursor recovery on restart, exponential backoff retry (5 attempts, 30 s cap), full-page pagination, per-event error isolation

Rust — gRPC API crate

proto/trident.proto defines EventsService with ListEvents (cursor-paginated, filterable by contract/topic/ledger), GetEvent (by UUID), and StreamEvents (server-side streaming). build.rs compiles protos via tonic-build with a vendored protoc — no system install required. EventsServiceImpl is wired into the Tonic server with clear stubs for each handler.

Go — front office API

Minimal stdlib HTTP server with graceful shutdown. Comment markers show exactly where the REST router, GraphQL handler, WebSocket handler, and Redis consumer wire in. Listens on PORT (default 3000).

TypeScript SDK

TridentClient with full type signatures for queryEvents (cursor-paginated), getEventById, and subscribeToContract (returns a Subscription handle). Zod schemas mirror SorobanEvent for runtime validation.

Docker

Dev compose runs postgres:15 and redis:7 only with health checks and schema auto-init. Production compose adds indexer and api services with depends_on health conditions and env-var-only secrets.

CI

Three required jobs on every push and PR to dev/main:

  • Rustcargo fmt --check, cargo clippy -- -D warnings, cargo test
  • Gogo vet, golangci-lint
  • TypeScriptnpm ci, tsc --noEmit

Closes

Closes #1, #2, #3

Depo-dev added 16 commits May 19, 2026 14:12
Add workspace Cargo.toml, MIT LICENSE, CONTRIBUTING.md, and .gitignore
covering Rust, Go, Node, environment files, and IDE artifacts.
Define soroban_events with all columns, computed topic columns, composite
and GIN indexes. Add system_state for cursor tracking, indexed_contracts
for the contract registry, and ledger_metadata for gap detection.
Migration 0001_init.sql mirrors schema.sql at this stage.
TridentError covers RpcError, ParseError, StorageError, and ConfigError
via thiserror. SorobanEvent is the canonical normalised event record shared
across the indexer, gRPC server, and any future crates.
Streamer owns the RPC polling loop, cursor management, and retry logic.
Parser owns XDR decoding, ScVal normalisation, and type coercion to the
canonical SorobanEvent shape. Both modules have detailed comment blocks
describing their full responsibilities for the next developer.
Minimal Tonic server entry point listening on GRPC_ADDR (default 0.0.0.0:50051).
Proto definitions and service implementations are deferred — the commented
layout shows exactly where they slot in once .proto files are defined.
Minimal stdlib HTTP server with graceful shutdown. Comment markers show
exactly where the REST router, GraphQL handler, WebSocket handler, and
Redis Streams consumer are wired in. Listens on PORT env var, default 3000.
TridentClient accepts apiUrl, apiKey, and network. Three public methods
with full type signatures: queryEvents (cursor-paginated), getEventById,
and subscribeToContract (returns a Subscription handle). Zod schemas
mirror the server-side SorobanEvent shape for runtime validation.
Dev compose runs postgres:15 and redis:7 only, with a postgres health check
and schema auto-init. Production compose adds indexer and api services with
depends_on health check conditions; all secrets are env var references.
Documents every required environment variable with a one-line explanation
of its purpose. Covers DATABASE_URL, REDIS_URL, STELLAR_RPC_URL, NETWORK,
POLL_INTERVAL_MS, INDEX_DIAGNOSTIC, LOG_LEVEL, PORT, API_KEY_SALT, and
PostgreSQL credentials for the production compose file.
SPECIFICATION.md reserves the location for the full technical spec covering
the data model, RPC protocol, XDR decoding, cursor semantics, API contracts,
and SDK design decisions. Content will be added before Phase 1 development begins.
Three required jobs on push/PR to dev and main: cargo fmt + clippy -D warnings
+ cargo test for Rust; go vet + golangci-lint for Go; tsc --noEmit for TypeScript.
Rust build cache via Swatinem/rust-cache keeps CI times reasonable.

Closes #1
Document the exact commands contributors must run locally before opening a
PR — cargo fmt/clippy/test, go vet/golangci-lint, and tsc --noEmit — so
they know what the pipeline enforces before they push. Update project status
to reflect that scaffolding and CI are now complete.
Full indexer pipeline:
- Config::from_env() reads all required environment variables
- RpcClient calls getEvents via JSON-RPC 2.0 with cursor pagination
- Parser decodes base64 XDR ScVal topics/data, normalises to SorobanEvent;
  all common ScVal variants handled with JSON-safe i128/u128 overflow to string
- db module: insert_event (ON CONFLICT DO NOTHING), get/set_cursor,
  insert_ledger_metadata via sqlx
- redis_stream module: XADD to trident:events stream
- Streamer loop: cursor recovery on restart, exponential backoff retry (5
  attempts, max 30s), full-page pagination, per-event error isolation so one
  bad event never stalls the stream
- SorobanEvent gains ledger_timestamp field in trident-common

Closes #2
proto/trident.proto defines EventsService with three RPCs: ListEvents
(cursor-paginated with contract_id/topic/ledger filters), GetEvent (by UUID),
and StreamEvents (server-side streaming for real-time delivery). All message
types are fully documented.

build.rs compiles the proto via tonic-build. EventsServiceImpl in
src/services/events.rs is wired into the Tonic server with TODO stubs for
each RPC showing exactly what query/stream logic goes in each handler.

Closes #3
Rust CI fixes:
- stellar-xdr 0.0.x no longer published; update to 26.0.1.
  Fix ScAddress::Contract pattern (ContractId wrapper, not Hash) and add
  exhaustive match arms for new MuxedAccount/ClaimableBalance/LiquidityPool
  variants added in 26.x
- stellar-strkey 0.0.8 → 0.0.16: to_string() now returns heapless::String;
  convert via .as_str().to_owned() at both call sites
- sqlx::query! requires DATABASE_URL or a prepared cache at compile time;
  replace all four usages with sqlx::query().bind() chains which compile
  without a live database
- Streamer: fix identical-branch clippy lint — start_ledger was always None;
  correct logic now uses Some(1) on first run and cursor-based pagination
  on all subsequent calls
- Wire insert_ledger_metadata into poll_once (called once per page after
  cursor advance) to resolve dead_code lint
- Use in_successful_contract_call in Parser to skip events from failed calls
  (resolves field dead_code lint and is the correct semantic behaviour)
- Use page.latest_ledger in streamer debug log (resolves field dead_code lint)
- Add protoc-bin-vendored as a build dependency so protoc is bundled and
  neither CI nor local dev need a system installation
@Depo-dev Depo-dev changed the title Dev feat: scaffold monorepo, implement Phase 1 indexer core, and wire CI May 21, 2026
@Depo-dev Depo-dev merged commit 2d7fc93 into main May 21, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ci: add GitHub Actions workflow for Rust, Go, and TypeScript

1 participant