Skip to content
Open
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
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ TREASURY_INSTANCE=
# Sync mainnet start point (slot and block hash)
# The indexer will start syncing from this point on first run

# 160964954 and 560c7537831007f9670d287b15a69ba18a322b1edc39c0c23ccab3c12ad77b9f are good for Intersect 2025 budget instance
# Must be BEFORE the publish tx (slot 160963893) to capture the full event history.
# 160963800 and 65233bb713c15c4bb427ccbf0e7e5c1c6a6a9c5c04b5edfa1e0e8e72f1285c9c are good for Intersect 2025 budget instance

STORE_CARDANO_SYNC_START_SLOT=
STORE_CARDANO_SYNC_START_BLOCKHASH=
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
.env
.DS_Store
api/logs/
indexer/logs/
scripts/diverging_events.csv
187 changes: 187 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
# CLAUDE.md — Administration Data

## Project Overview

Indexes Cardano treasury governance data from the blockchain and exposes it via a REST API. Three components work together:

1. **YACI Store indexer** — Java-based blockchain indexer (black-box dependency) that reads from a Cardano node and writes raw data to PostgreSQL
2. **PostgreSQL** — stores both raw blockchain data (`yaci_store` schema) and normalized app data (`treasury` schema)
3. **Rust API** — syncs from YACI Store tables into treasury tables, then serves REST endpoints

Swagger docs are at `/docs` when the API is running.

## Architecture & Data Flow

```
Cardano Node → YACI Store indexer → PostgreSQL (yaci_store schema)
Rust API sync service
PostgreSQL (treasury schema)
REST API
```

- **`yaci_store` schema**: raw blockchain data, managed by YACI Store's Flyway migrations — never modify manually
- **`treasury` schema**: normalized app data, managed by `database/schema/treasury.sql` and init scripts
- The YACI Store plugin filter (`indexer/plugins/scripts/treasury-filter.mvel`) reduces stored data by ~95%

## Domain Context (TOM / Cardano Treasury)

This project implements the **Treasury Oversight Metadata (TOM)** standard, using CIP-100 metadata label **1694**.

### Contract Hierarchy
- **Treasury Contract (TRSC)** → at a unique script address, holds treasury reserve funds. Stored in `treasury.treasury_contracts`.
- **Vendor Contract (PSSC)** → **ONE shared script address for ALL projects** (not one per project). Stored in `treasury.vendor_contracts` (singleton row: `address`, `stake_credential`).
- Each `fund` tx creates UTXOs at the shared PSSC address
- UTXOs belong to specific projects, distinguished by inline datum, NOT by address
- UTXO chain tracking (`find_project_from_inputs`) links events to projects by tracing spent inputs
- **Project** → one row per `fund` event (e.g. `EC-0008-25`). Stored in `treasury.projects`. Foreign-keyed from milestones, events, and UTXO history via `project_db_id`.
- **Milestones** → belong to a project

### Vendor Naming
- `vendor.name` does **not exist** in the TOM spec — code extracts it but always gets null
- `vendor.label` in the spec is the vendor's display name; in practice, real metadata puts the payment address here
- Vendor identity is typically embedded in the top-level `body.label` by convention (e.g., "Tastenkunst GmbH - Eternl Maintenance")

### Event Types
publish, initialize, fund, complete, disburse, withdraw, pause, resume, modify, cancel, sweep, reorganize

See [`docs/event-processing.md`](docs/event-processing.md) for detailed per-event field mappings, code extraction paths, DB writes, and known bugs.

### Financial Model
- All amounts are in **lovelace** (1 ADA = 1,000,000 lovelace)

### Milestone Lifecycle
Milestones use 4 independent boolean flags (not a linear status):
- **evidence_provided** — vendor submitted completion evidence via a `complete` transaction
- **withdrawn** — vendor withdrew payment via a `withdraw` transaction
- **paused** — oversight committee paused this milestone (from inline datum constructor 0→1)
- **archived** — milestone replaced by a `modify` event (old row preserved, new row created)

Additionally, each milestone has a **time_limit** (POSIXTime ms) from the inline UTXO datum.
Claimability is derived: time_limit < current time AND NOT withdrawn.

Archive model: on modify, existing row → archived=true, new row inserted. superseded_by FK links old → new.

**Disburse vs Withdraw**: Disburse is treasury-level (moves funds from treasury contract to any address).
Withdraw is milestone-level (vendor claims matured milestone funds from vendor contract). These are completely separate.

### Treasury Instance
The `TREASURY_INSTANCE` env var filters to a specific on-chain treasury. Changing it tracks a different treasury entirely.

## Development Setup

### Prerequisites
- Docker and docker-compose
- Rust toolchain (for native API development)

### Quick Start
```bash
./dev.sh start # starts PostgreSQL + indexer + API
```

### Dev Script Commands
```bash
./dev.sh start # start all services (API runs natively if Rust is installed)
./dev.sh stop # stop all Docker services
./dev.sh restart # restart Docker services
./dev.sh logs # tail all logs (or: ./dev.sh logs indexer)
./dev.sh status # show service status
./dev.sh build # build Docker images
./dev.sh clean # stop and remove all containers + volumes
```

### Native API Development
```bash
# With Docker DB already running:
cd api
DATABASE_URL="postgresql://postgres:postgres@localhost:5433/administration_data" cargo run
```

### Build & Test
```bash
cd api
cargo build --release
cargo check # fast type-checking
cargo test # run tests
```

### Environment Setup
Copy `.env.example` to `.env` and configure:
- `TREASURY_INSTANCE` — the on-chain treasury to track
- `STORE_CARDANO_SYNC_START_SLOT` / `STORE_CARDANO_SYNC_START_BLOCKHASH` — where to start syncing

## Port Mappings

| Service | Host Port | Container Port |
|------------|-----------|----------------|
| PostgreSQL | 5433 | 5432 |
| YACI Store | 8081 | 8080 |
| API | 8080 | 8080 |

PostgreSQL uses **5433** on the host to avoid conflicts with local PostgreSQL installations.

Database connection string: `postgresql://postgres:postgres@localhost:5433/administration_data`

## Code Conventions

- Rust 2021 edition, **Axum 0.7** web framework
- **SQLx** for database queries (compile-time checked)
- **utoipa** OpenAPI decorators on all endpoints and models
- Consistent API response envelope: `{ data, pagination?, meta.timestamp }`
- Follow existing patterns in the codebase
- Add tests for new code

## Database

- Schema source of truth: `database/schema/treasury.sql`
- Init scripts: `database/init/` — run on first Docker PostgreSQL start
- For schema changes: create incremental migration files, don't edit `treasury.sql` directly for running systems
- YACI Store schema is auto-managed by Flyway — **never modify manually**

## Indexer

YACI Store is a **black-box dependency**. Only modify configuration and plugins:

- `indexer/application.properties` — indexer config
- `indexer/config/application-plugins.yml` — plugin configuration
- `indexer/plugins/scripts/treasury-filter.mvel` — MVEL filter script

Never modify `yaci-store.jar` or YACI Store internals. Primary network: Mainnet (`backbone.cardano.iog.io:3001`).

## CI/CD

- **`ci.yml`**: runs `cargo build --release && cargo test` on push/PR to main/develop
- **`push-to-ecr.yaml`**: builds Docker image and pushes to AWS ECR on push to main (or manual dispatch)
- Deployment: Helm chart bump in a separate repo

## Gotchas

- **Startup ordering**: YACI Store must be running and synced before the API sync service can process events. The sync service (`api/src/services/sync.rs`) waits for YACI Store tables to exist.
- **Port 5433**: PostgreSQL is on host port 5433, not 5432.
- **`.env` not committed**: copy `.env.example` and configure before first run.
- **UTXO pruning**: YACI Store prunes spent UTXOs — historical UTXO data may not be available.
- **Cold replay vs continuous operation**: The milestone-event chain trace (`find_project_from_inputs`) needs UTXO history to link withdraw/complete/pause/resume to a project. The Postgres triggers installed by `install_utxo_history_triggers` (in `api/src/services/sync.rs`) capture every script-address UTXO into `treasury.utxo_history` synchronously with YACI Store's INSERT, so pruning no longer drops chain-trace inputs. Triggers only protect from the moment they're armed — to recover pre-existing pruned data, wipe the database volume and re-sync with the API running so the triggers arm before YACI Store ingests. See [`docs/known-issues.md`](docs/known-issues.md) `KI-CR-01` and `KI-UTX-01`.
- **Large JAR**: `indexer/yaci-store.jar` is ~108MB and committed to the repo. Don't regenerate unnecessarily.
- **Inline datums**: `store.script.enabled=true` in YACI Store config enables milestone datum data (amounts, time limits, pause flags). Requires full re-sync after enabling.
- **Milestone archiving**: Filter `WHERE NOT archived` for current milestones. Archived rows are historical versions.

## Key File Locations

| Purpose | Path |
|--------------------|---------------------------------------|
| API entry point | `api/src/main.rs` |
| API routes | `api/src/routes/v1/` |
| API models | `api/src/models/v1.rs` |
| Event processing | `api/src/services/event_processor.rs` |
| Sync service | `api/src/services/sync.rs` |
| DB schema | `database/schema/treasury.sql` |
| DB init scripts | `database/init/` |
| Docker setup | `docker-compose.yml` |
| Dev script | `dev.sh` |
| Indexer config | `indexer/application.properties` |
| Plugin config | `indexer/config/application-plugins.yml` |
| Treasury filter | `indexer/plugins/scripts/treasury-filter.mvel` |
| CI | `.github/workflows/ci.yml` |
| ECR push | `.github/workflows/push-to-ecr.yaml` |
48 changes: 32 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,22 +124,29 @@ Interactive documentation available at `/docs` (Swagger UI).
| `GET /api/v1/treasury/utxos` | Treasury UTXOs |
| `GET /api/v1/treasury/events` | Treasury-level events |

### Vendor Contracts (Projects)
### Vendor Contract (singleton PSSC)

| Endpoint | Description |
|----------|-------------|
| `GET /api/v1/vendor-contracts` | List all vendor contracts (with pagination, filtering, search) |
| `GET /api/v1/vendor-contracts/:project_id` | Get vendor contract details |
| `GET /api/v1/vendor-contracts/:project_id/milestones` | Get project milestones |
| `GET /api/v1/vendor-contracts/:project_id/events` | Get project event history |
| `GET /api/v1/vendor-contracts/:project_id/utxos` | Get project UTXOs |
| `GET /api/v1/vendor-contract` | Shared PSSC script address + project rollup by status |

### Projects

| Endpoint | Description |
|----------|-------------|
| `GET /api/v1/projects` | List all projects (with pagination, filtering, search) |
| `GET /api/v1/projects/:project_id` | Get project details |
| `GET /api/v1/projects/:project_id/milestones` | Get project milestones |
| `GET /api/v1/projects/:project_id/events` | Get project event history |
| `GET /api/v1/projects/:project_id/utxos` | Get project UTXOs |

### Milestones

| Endpoint | Description |
|----------|-------------|
| `GET /api/v1/milestones` | List all milestones (with pagination, filtering) |
| `GET /api/v1/milestones/:id` | Get milestone details |
| `GET /api/v1/milestones/:project_id` | List milestones for a project (paginated) |
| `GET /api/v1/milestones/by-id/:id` | Get milestone by integer database ID |

### Events

Expand Down Expand Up @@ -183,8 +190,8 @@ Limitation: this is only configured for Mainnet currently
The sync start point is configured via environment variables in `.env`:

```bash
STORE_CARDANO_SYNC_START_SLOT=160964954
STORE_CARDANO_SYNC_START_BLOCKHASH=560c7537831007f9670d287b15a69ba18a322b1edc39c0c23ccab3c12ad77b9f
STORE_CARDANO_SYNC_START_SLOT=160963800
STORE_CARDANO_SYNC_START_BLOCKHASH=65233bb713c15c4bb427ccbf0e7e5c1c6a6a9c5c04b5edfa1e0e8e72f1285c9c
```

Network settings (host, port, protocol magic) are in `indexer/application.properties`.
Expand Down Expand Up @@ -222,10 +229,12 @@ The system uses two schemas:
| Table | Description |
|-------|-------------|
| `treasury.treasury_contracts` | Treasury reserve contracts (TRSC) |
| `treasury.vendor_contracts` | Vendor/project contracts (PSSC) |
| `treasury.milestones` | Project milestones |
| `treasury.events` | All TOM event audit log |
| `treasury.utxos` | UTXO tracking for event linking |
| `treasury.vendor_contracts` | Singleton row for the shared PSSC script address (one per deployment) |
| `treasury.projects` | One row per `fund` event (e.g. `EC-0008-25`); holds project metadata |
| `treasury.milestones` | Project milestones (4 independent boolean flags + archive model). FKs to `projects` via `project_db_id` |
| `treasury.events` | All TOM event audit log. FKs to `projects` via `project_db_id`; `destination` is JSONB |
| `treasury.utxo_history` | Persistent UTXO history (populated by Postgres triggers on `yaci_store.address_utxo`) for chain trace + datum cache |
| `treasury.sync_status` | Heartbeat: per-stream `last_slot` / `last_block` / `updated_at` |

### Connecting to Database

Expand All @@ -246,11 +255,11 @@ SELECT * FROM yaci_store.block ORDER BY number DESC LIMIT 5;
-- Treasury summary
SELECT * FROM treasury.v_treasury_summary;

-- Vendor contracts with financials
-- Projects with financials
SELECT project_id, project_name, status,
initial_amount_lovelace / 1000000 as allocated_ada,
total_disbursed_lovelace / 1000000 as disbursed_ada
FROM treasury.v_vendor_contracts_summary;
total_withdrawn_lovelace / 1000000 as withdrawn_ada
FROM treasury.v_projects_summary;

-- Recent events
SELECT * FROM treasury.v_events_with_context
Expand All @@ -268,10 +277,17 @@ This reduces database size by ~95% while keeping all treasury data.
## Component Documentation

- [Architecture & Data Flow](docs/architecture.md) - System architecture and data flow diagrams
- [Event Processing](docs/event-processing.md) - Per-event-type field mappings and write paths
- [Known Issues](docs/known-issues.md) - Indexed catalog of NULL-field cases, on-chain data quirks, and sync-loop gotchas
- [API Documentation](api/README.md) - Full API reference
- [Indexer Setup](indexer/README.md) - YACI Store configuration
- [Database Schema](database/schema/) - Treasury schema definitions

## Gotchas

- **Cold replay vs continuous operation**: a fresh local sync from an old `STORE_CARDANO_SYNC_START_SLOT` cannot reconstruct UTXO chains whose inputs were pruned *before* the `treasury.utxo_history` triggers were installed. With the triggers armed, every script-address UTXO YACI Store inserts is captured before pruning runs. To recover pre-existing pruned data, wipe the volume and re-sync with the API running so the triggers arm before YACI Store ingests. See [`docs/known-issues.md`](docs/known-issues.md) `KI-CR-01` / `KI-UTX-01`.
- **Stale-looking sync timestamp**: `treasury.sync_status.updated_at` only bumps when new events arrive. A long delta does not mean the sync loop is dead. See `KI-SY-01`.

## License

See [LICENSE](./LICENSE).
6 changes: 6 additions & 0 deletions api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,9 @@ chrono = { version = "0.4", features = ["serde"] }

# UUID
uuid = { version = "1.0", features = ["v4", "serde"] }

# Cardano datum parsing (CBOR/Plutus)
pallas-primitives = "0.30"
pallas-codec = "0.30"
pallas-addresses = "0.30"
hex = "0.4"
Loading
Loading