Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Connection string for PostgreSQL. The indexer and API both read from this.
DATABASE_URL=postgresql://trident:password@localhost:5432/trident

# Connection string for Redis. Used by the indexer to publish events and by
# the API to consume them for WebSocket delivery.
REDIS_URL=redis://localhost:6379

# Soroban RPC endpoint. For testnet use https://soroban-testnet.stellar.org
# For mainnet use https://horizon.stellar.org/soroban/rpc or a private node.
STELLAR_RPC_URL=https://soroban-testnet.stellar.org

# Network identifier passed through to the indexer for validation and labelling.
# One of: mainnet | testnet | futurenet
NETWORK=testnet

# How often the indexer polls the RPC node for new ledgers, in milliseconds.
POLL_INTERVAL_MS=5000

# Set to true to store diagnostic events (emitted only in debug mode by Soroban).
# Leave false for production — diagnostic events are high-volume and rarely useful.
INDEX_DIAGNOSTIC=false

# Log verbosity level. One of: error | warn | info | debug | trace
LOG_LEVEL=info

# Port the Go API server listens on.
PORT=3000

# Random secret used to salt API key hashes. Must be changed before deployment.
# Generate with: openssl rand -hex 32
API_KEY_SALT=change-this-to-a-random-string

# PostgreSQL credentials (used by docker-compose.yml in production).
POSTGRES_USER=trident
POSTGRES_PASSWORD=password
POSTGRES_DB=trident
86 changes: 86 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
name: CI

on:
push:
branches: [dev, main]
pull_request:
branches: [dev, main]

jobs:
# ---------------------------------------------------------------------------
# Rust — format, lint, test
# ---------------------------------------------------------------------------
rust:
name: Rust
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy

- name: Cache Rust build artefacts
uses: Swatinem/rust-cache@v2

- name: Check formatting
run: cargo fmt --all -- --check

- name: Clippy (deny warnings)
run: cargo clippy --all-targets --all-features -- -D warnings

- name: Tests
run: cargo test --all

# ---------------------------------------------------------------------------
# Go — vet and lint
# ---------------------------------------------------------------------------
go:
name: Go
runs-on: ubuntu-latest
defaults:
run:
working-directory: services/api
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.22"
cache-dependency-path: services/api/go.sum

- name: go vet
run: go vet ./...

- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: latest
working-directory: services/api

# ---------------------------------------------------------------------------
# TypeScript — type check
# ---------------------------------------------------------------------------
typescript:
name: TypeScript
runs-on: ubuntu-latest
defaults:
run:
working-directory: sdk/typescript
steps:
- uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
cache-dependency-path: sdk/typescript/package-lock.json

- name: Install dependencies
run: npm ci

- name: Type check
run: npm run lint
33 changes: 33 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Rust
/target
**/target/
Cargo.lock
**/*.rs.bk

# Go
services/api/bin/
*.exe
*.out

# Node / TypeScript
node_modules/
sdk/typescript/dist/
sdk/typescript/.turbo/
*.tsbuildinfo

# Environment
.env
.env.local
.env.*.local

# IDE
.idea/
.vscode/
*.swp
*.swo
.DS_Store
Thumbs.db

# Misc
*.log
dist/
111 changes: 111 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Contributing to Trident

Trident is infrastructure — the kind of system other developers will build products on top of without thinking about it. That means the bar for what gets merged is higher than it would be for an application. Code here needs to be correct, readable, and maintainable by someone who didn't write it. Those three things, in that order.

---

## Before the Codebase Is Public

The most useful thing you can do right now is engage with the design while it can still change. Read the full specification at [`docs/SPECIFICATION.md`](./docs/SPECIFICATION.md) and open an issue if something looks wrong, underspecified, or like a decision that hasn't been thought through. Open a [Discussion](https://github.com/trident-build/trident/discussions) and describe what you're building on Stellar and what you'd need from an indexer. If you've built event pipelines or indexing infrastructure before, your architectural critique matters more right now than it will once the code exists.

---

## Getting Set Up

You'll need Rust via [rustup](https://rustup.rs), Node.js 20 LTS for the TypeScript SDK, and Docker with Compose v2. Once those are in place, getting a local environment running should take under ten minutes.

```bash
git clone https://github.com/trident-build/trident.git
cd trident
cp .env.example .env
docker compose -f docker/docker-compose.dev.yml up -d
cargo build
```

The dev Compose file starts PostgreSQL and Redis only. You run the indexer and API locally so changes reflect immediately without rebuilding containers. To work with real data, set `STELLAR_RPC_URL` and `NETWORK=testnet` in your `.env`. Full setup details will live in [`docs/development.md`](./docs/development.md) once the repo is scaffolded.

---

## How the Repo Is Structured

```
trident/
├── crates/
│ ├── indexer/ # Rust core — streamer, XDR parser, cursor management
│ ├── api/ # Rust gRPC server
│ └── common/ # Shared types, errors, config
├── services/
│ └── api/ # Go front office — REST, GraphQL, WebSocket, Redis consumer
├── sdk/
│ └── typescript/ # TypeScript SDK (@trident-indexer/sdk)
├── database/
│ ├── schema.sql # Canonical PostgreSQL schema
│ └── migrations/ # Versioned, append-only migration files
├── docker/
└── docs/
```

The Rust crates own everything from the chain to storage. The Go service owns everything from storage to the developer. The TypeScript SDK is a pure HTTP and WebSocket client with no knowledge of the database and no direct connection to any internal service.

---

## Workflow

Before writing code for anything non-trivial, open an issue first and comment to claim it. This prevents duplicated effort and makes sure your approach aligns with where the project is heading before you invest time in it.

All branches come off `dev` — `main` is for tagged releases only. Name branches as `type/issue-number-short-description`, for example `feature/42-graphql-subscriptions` or `fix/87-cursor-recovery`.

Commit messages follow [Conventional Commits](https://www.conventionalcommits.org) with type, scope, and an imperative description:

```
feat(indexer): add cursor recovery on restart
fix(api): return 404 when event id not found
docs(sdk): add subscribeToContract example
chore(deps): update tokio to 1.35
```

Valid types are `feat`, `fix`, `docs`, `test`, `refactor`, `perf`, and `chore`. Valid scopes are `indexer`, `api`, `sdk`, `db`, `docker`, and `docs`. Reference the issue in the commit footer with `Closes #NNN`.

---

## Pull Requests

A PR that moves through review quickly explains *why* the change exists, not just what it does. It has tests covering the new behaviour — for bug fixes specifically, a regression test that would have caught the original bug. CI is green before review is requested. It changes one thing.

A PR that comes back for revision is one that mixes concerns, doesn't explain the reasoning, or changes a public interface without prior discussion. One concern per PR is a firm rule regardless of how small the changes are.

---

## Code Standards

On the Rust side, `cargo clippy` must pass clean and `cargo fmt` must produce no diff — both enforced in CI with no exceptions. `.unwrap()` is not allowed in non-test code because a panic in the streaming loop means missed events, and missed events are the one thing Trident cannot tolerate. New error variants go in `crates/common`. Public functions and types get doc comments.

On the Go side, `golangci-lint` must pass. Errors are returned and never ignored. Every function doing I/O takes a `context.Context` as its first argument. Error messages returned to developers need to be genuinely useful — not "internal server error" but something that tells them exactly what was wrong and how to fix it.

On the TypeScript side, strict mode is on and `any` is not allowed. The SDK's public interface deserves particular care because renaming anything after v1 is a breaking change requiring a major version bump and migration docs. ESLint and Prettier must be clean.

SQL migrations are append-only — a migration committed to `main` is never edited, only superseded by a new numbered one. Index names follow `idx_<table>_<columns>`. Production queries always name columns explicitly.

---

## Good First Issues

Once development starts, we'll tag approachable issues with `good first issue`. These tend to be things like adding a missing filter parameter to an endpoint, writing tests for an existing parser function, improving an error message with more context, or documenting an undocumented function. They're scoped to help you understand the system without needing to know all of it upfront.

For more substantial work — changes to the indexer core, query performance improvements, new API capabilities — open a Discussion with a concrete use case before writing any code. The indexer's streaming and cursor logic in particular warrants a conversation before anyone touches it.

---

## Security

Security issues must not be filed as public GitHub issues. Send them to `security@trident.build` and expect a response within 48 hours.

---

## Getting Help

[GitHub Discussions](https://github.com/trident-build/trident/discussions) is the right place for questions, ideas, and design conversations before an issue is opened. [GitHub Issues](https://github.com/trident-build/trident/issues) is for confirmed bugs and concrete feature requests. For anything that shouldn't be public, reach out at `contributors@trident.build`.

---

*Trident is infrastructure for the whole Stellar ecosystem. Getting it right matters. Thanks for helping.*
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[workspace]
members = [
"crates/indexer",
"crates/api",
"crates/common",
]
resolver = "2"
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 Depo-dev

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,39 @@ Full historical event storage with no enforced retention limit, so a query again

- [x] Architecture defined
- [x] Full specification written — [`docs/SPECIFICATION.md`](./docs/SPECIFICATION.md)
- [ ] Repository scaffolding
- [x] Repository scaffolded
- [x] CI pipeline active
- [ ] Phase 1 development begins

---

## Contributing

All branches come off `dev`. Before opening a pull request, make sure all three CI checks pass locally — the pipeline will block merge if any of them fail.

**Rust**
```bash
cargo fmt --all # format — CI runs --check, so this must be clean
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all
```

**Go** (`services/api`)
```bash
go vet ./...
golangci-lint run # install: https://golangci-lint.run/usage/install/
```

**TypeScript** (`sdk/typescript`)
```bash
npm ci
npm run lint # runs tsc --noEmit in strict mode
```

Running these before pushing means CI passes on the first try. See [`CONTRIBUTING.md`](./CONTRIBUTING.md) for branching conventions, commit message format, and code standards.

---

<div align="center">

*Build on Stellar. Query everything.*
Expand Down
17 changes: 17 additions & 0 deletions crates/api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "trident-api"
version = "0.1.0"
edition = "2021"

[dependencies]
trident-common = { path = "../common" }
tokio = { version = "1", features = ["full"] }
tonic = "0.11"
prost = "0.12"
tokio-stream = "0.1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

[build-dependencies]
tonic-build = "0.11"
protoc-bin-vendored = "3"
12 changes: 12 additions & 0 deletions crates/api/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Use the vendored protoc binary so neither CI nor local dev need a system install.
let protoc_path = protoc_bin_vendored::protoc_bin_path()?;
std::env::set_var("PROTOC", protoc_path);

tonic_build::configure()
.build_server(true)
.build_client(false)
.compile(&["../../proto/trident.proto"], &["../../proto"])?;

Ok(())
}
Loading
Loading