163 lines
5.0 KiB
Markdown
163 lines
5.0 KiB
Markdown
|
|
# 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`.
|