# Architecture

## Overview

SolaceScan is a four-tier block explorer + access-control plane for
Chain 138. Every request is classified into one of four **tracks**;
higher tracks require stronger authentication and hit different
internal subsystems.

```mermaid
flowchart LR
    U[User / wallet / operator] -->|HTTPS| FE[Next.js frontend<br/>:3000]
    U -->|direct API<br/>or SDK| EDGE[Edge / nginx<br/>:443]
    FE --> EDGE
    EDGE --> API[Go REST API<br/>backend/api/rest :8080]

    API --> PG[(Postgres +<br/>TimescaleDB)]
    API --> ES[(Elasticsearch)]
    API --> RD[(Redis)]
    API --> RPC[(Chain 138 RPC<br/>core / alltra / thirdweb)]

    IDX[Indexer<br/>backend/indexer] --> PG
    IDX --> ES
    RPC --> IDX

    subgraph Access layer
      EDGE -->|auth_request| VK[validate-key<br/>/api/v1/access/internal/validate-key]
      VK --> API
    end
```

## Tracks

```mermaid
flowchart TB
    subgraph Track1[Track 1 — public, no auth]
      T1A[/blocks]
      T1B[/transactions]
      T1C[/search]
      T1D[/api/v1/track1/*]
    end

    subgraph Track2[Track 2 — wallet-verified]
      T2A[Subscriptions]
      T2B[API key lifecycle]
      T2C[Usage + audit self-view]
    end

    subgraph Track3[Track 3 — analytics]
      T3A[Advanced analytics]
      T3B[Admin audit]
      T3C[Admin subscription review]
    end

    subgraph Track4[Track 4 — operator]
      T4A[/api/v1/track4/operator/run-script]
      T4B[Mission-control SSE]
      T4C[Ops tooling]
    end

    Track1 --> Track2 --> Track3 --> Track4
```

Authentication for tracks 2–4 is SIWE-style: client hits
`/api/v1/auth/nonce`, signs the nonce with its wallet, posts the
signature to `/api/v1/auth/wallet`, gets a JWT back. JWTs carry the
resolved `track` claim and a `jti` for server-side revocation (see
`backend/auth/wallet_auth.go`).

### Per-track token TTLs

| Track | TTL | Rationale |
|------|-----|-----------|
| 1 | 12h | Public / long-lived session OK |
| 2 | 8h | Business day |
| 3 | 4h | Analytics session |
| 4 | **60 min** | Operator tokens are the most dangerous; short TTL + `POST /api/v1/auth/refresh` |

Revocation lives in `jwt_revocations` (migration `0016`). Logging out
(`POST /api/v1/auth/logout`) inserts the token's `jti` so subsequent
validation rejects it.

## Sign-in flow (wallet)

```mermaid
sequenceDiagram
    autonumber
    actor W as Wallet
    participant FE as Frontend
    participant API as REST API
    participant DB as Postgres

    W->>FE: connect / sign-in
    FE->>API: POST /api/v1/auth/nonce {address}
    API->>DB: insert wallet_nonces(address, nonce, expires_at)
    API-->>FE: {nonce}
    FE->>W: signTypedData/personal_sign(nonce)
    W-->>FE: signature
    FE->>API: POST /api/v1/auth/wallet {address, nonce, signature}
    API->>API: ecrecover → verify address
    API->>DB: consume nonce; resolve user track
    API-->>FE: {token, expiresAt, track, permissions}
    FE-->>W: session active
```

## Data flow (indexer ↔ API)

```mermaid
flowchart LR
    RPC[(Chain 138 RPC)] -->|new blocks| IDX[Indexer]
    IDX -->|INSERT blocks, txs, logs| PG[(Postgres)]
    IDX -->|bulk index| ES[(Elasticsearch)]
    IDX -->|invalidate| RD[(Redis)]

    API[REST API] -->|SELECT| PG
    API -->|search, facets| ES
    API -->|cached RPC proxy| RD
    API -->|passthrough for deep reads| RPC
```

## Subsystems

- **`backend/api/rest`** — HTTP API. One package; every handler lives
  under `backend/api/rest/*.go`. AI endpoints were split into
  `ai.go` + `ai_context.go` + `ai_routes.go` + `ai_docs.go` +
  `ai_xai.go` + `ai_helpers.go` by PR #6 to keep file size
  manageable.
- **`backend/auth`** — wallet auth (nonce issue, signature verify,
  JWT issuance / validation / revocation / refresh).
- **`backend/indexer`** — Chain 138 block/tx/log indexer, writes
  Postgres + Elasticsearch, invalidates Redis.
- **`backend/analytics`** — longer-running queries: token distribution,
  holder concentration, liquidity-pool aggregates.
- **`backend/api/track4`** — operator-scoped endpoints
  (`run-script`, mission-control).
- **`frontend`** — Next.js 14 pages-router app. Router decision
  (PR #9) is final: no `src/app/`.

## Runtime dependencies

| Service | Why |
|---------|-----|
| Postgres (+ TimescaleDB) | Chain data, users, subscriptions, `jwt_revocations` |
| Elasticsearch | Full-text search, facets |
| Redis | Response cache, rate-limit counters, SSE fan-out |
| Chain 138 RPC | Upstream source of truth; three lanes — core / alltra / thirdweb — catalogued in `backend/config/rpc_products.yaml` |

## Deployment

See [deployment/README.md](../deployment/README.md) for compose and
production deploy details. The `deployment/docker-compose.yml` file
is the reference local stack and is what `make e2e-full` drives.

## Security posture

- `JWT_SECRET` and `CSP_HEADER` are **fail-fast** — a production
  binary refuses to start without them (PR #3).
- Secrets never live in-repo; `.gitleaks.toml` blocks known-bad
  patterns at commit time.
- Rotation checklist: [docs/SECURITY.md](SECURITY.md).
- Track-4 token TTL capped at 60 min; every issued token is
  revocable by `jti`.
