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
118 changes: 114 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,115 @@
.qodo
# ============================================================
# StellarHunts — root .gitignore
# Covers: Node.js / NestJS, Next.js, Rust / Cargo, and IDEs
# ============================================================

# ── Environment & secrets ───────────────────────────────────
.env
.env.*
.env.local
.env.*.local
.env.development
.env.development.local
.env.test
.env.test.local
.env.production
.env.production.local
*.pem
*.key
*.p12
secrets/

# ── Node / npm / yarn / pnpm ────────────────────────────────
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
.pnp
.pnp.js
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz

# ── Build artefacts ─────────────────────────────────────────
dist/
build/
out/
.next/
.nuxt/
.output/
.cache/
*.tsbuildinfo

# ── Rust / Cargo ────────────────────────────────────────────
target/
**/*.rs.bk
.cargo/registry/
.cargo/git/

# ── Test & coverage ─────────────────────────────────────────
coverage/
.nyc_output/
test_snapshots/
*.lcov
*.snap

# ── Logs & diagnostics ──────────────────────────────────────
logs/
*.log
pids/
*.pid
*.pid.lock
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# ── OS-generated ────────────────────────────────────────────
.DS_Store
node_modules
dist
.env
.DS_Store?
._*
.Spotlight-V100
.Trashes
Thumbs.db
ehthumbs.db
Desktop.ini

# ── IDE — JetBrains (IntelliJ / WebStorm / CLion / …) ───────
.idea/
*.iml
*.iws
*.ipr
.project
.classpath
.settings/
*.sublime-workspace
*.sublime-project

# ── IDE — VS Code ───────────────────────────────────────────
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

# ── IDE — Eclipse / generic ─────────────────────────────────
.c9/
*.launch

# ── Temporary files ─────────────────────────────────────────
.temp/
.tmp/
tmp/
*.tmp
*.bak
*.swp
*~

# ── Tooling ─────────────────────────────────────────────────
.qodo
.tool-versions
.turbo/
.parcel-cache/

# ── Docker (local overrides only — Dockerfiles stay tracked) ─
docker-compose.override.yml
66 changes: 66 additions & 0 deletions docs/adr/0001-record-architecture-decisions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# ADR-0001: Record Architecture Decisions

**Date:** 2025-07-24
**Status:** Accepted
**Deciders:** StellarHunts core team

---

## Context

Architecture decisions in StellarHunts have historically been made in PR
descriptions, Discord threads, and informal team discussions. When a new
contributor joins, there is no single place to find *why* certain choices
were made — only *what* the current code does. This creates ramp-up
friction and leads to decisions being revisited unnecessarily.

## Decision

We will use Architecture Decision Records (ADRs) to capture significant
architectural and design decisions. Each ADR is a short Markdown document
stored in `docs/adr/` with a sequential four-digit prefix and a
kebab-case title.

ADRs should be created when:
- A technology or library is selected over alternatives
- A structural pattern is established (e.g., module layout, naming)
- An existing decision is reversed or superseded
- A design has meaningful trade-offs worth documenting

ADRs are **immutable once accepted**. Superseding an old decision means
creating a new ADR and updating the old one's status field.

### Template

```
# ADR-NNNN: <Title>

**Date:** YYYY-MM-DD
**Status:** Proposed | Accepted | Deprecated | Superseded by ADR-XXXX
**Deciders:** <team or individuals>

---

## Context
<What situation or problem prompted this decision?>

## Decision
<What was decided?>

## Consequences
### Positive
<Benefits of this choice.>
### Negative / Trade-offs
<Costs, risks, or constraints introduced.>
```

## Consequences

### Positive
- New contributors can understand system rationale without reading PRs
- Decisions are revisited deliberately rather than accidentally
- Lightweight process — one Markdown file per decision

### Negative / Trade-offs
- Requires discipline to write an ADR *before* merging significant changes
- ADRs can go stale if not kept up to date with status changes
62 changes: 62 additions & 0 deletions docs/adr/0002-zustand-alongside-redux-toolkit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# ADR-0002: Use Zustand as the Primary State Manager Alongside Redux Toolkit

**Date:** 2025-07-24
**Status:** Accepted
**Deciders:** StellarHunts frontend team

---

## Context

The StellarHunts frontend needs client-side state management for:

1. **Game state** — current puzzle, difficulty level, completed puzzles,
score, NFT collection. This state must survive page refreshes
(localStorage persistence) and is mutated frequently during gameplay.
2. **Auth state** — current user, JWT token, wallet address.
3. **Server state** — leaderboard data, puzzle content, referral stats.
This is cached, stale-while-revalidate data fetched from the NestJS API.

Two popular choices were on the table: **Zustand** and
**Redux Toolkit (RTK)**.

| Criterion | Zustand | Redux Toolkit |
|-----------|---------|---------------|
| Bundle size | ~3 kB | ~20 kB |
| Boilerplate | Minimal (no actions/reducers) | Moderate (slice files) |
| Middleware / devtools | Optional, plugin-based | First-class |
| Persistence | `zustand/middleware` `persist` | `redux-persist` |
| Learning curve | Low | Medium |
| Ecosystem maturity | Stable, wide adoption | Very mature, huge ecosystem |

`@reduxjs/toolkit` is already listed as a production dependency (version
`^2.5.1`) because it was planned for a more complex slice-based state
model. In practice the team converged on Zustand stores for all current
state needs.

## Decision

- **Zustand** is the **primary** state management library for game state,
auth state, and reward state (see `frontend/store/`).
- **`@reduxjs/toolkit`** remains in `package.json` and should be used if
future requirements call for complex middleware chains, time-travel
debugging, or shared state slices that benefit from RTK's code
generation patterns (e.g., `createEntityAdapter`).
- **TanStack Query** handles all *server state* — API responses, caching,
background refetching — and is not replaced by either of the above.

## Consequences

### Positive
- Simple, readable store definitions — a store is just a `create()` call
- `persist` middleware handles localStorage serialization out of the box
- Low bundle size contribution
- Devtools integration available via `zustand/middleware` `devtools`

### Negative / Trade-offs
- RTK's advanced features (immer-backed reducers, RTK Query, entity
adapters) are unavailable unless RTK is wired up in the future
- Two state libraries in `package.json` can confuse new contributors —
this ADR resolves that ambiguity
- No centralized dispatcher pattern; state mutations are co-located in
store files, which can scatter business logic
68 changes: 68 additions & 0 deletions docs/adr/0003-nestjs-modular-monolith.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# ADR-0003: NestJS Modular Monolith Over Microservices

**Date:** 2025-07-24
**Status:** Accepted
**Deciders:** StellarHunts backend team

---

## Context

StellarHunts requires a backend that handles authentication, puzzle
management, NFT claim orchestration, real-time multiplayer matchmaking,
notifications, referrals, analytics, and more. These are distinct
domains, so the question arose: should the backend be structured as a
**microservices** cluster or a **monolith**?

The team evaluated three architectural patterns:

| Pattern | Deployment | Scaling | Complexity | Team size fit |
|---------|-----------|---------|------------|---------------|
| Unstructured monolith | Single process | Vertical only | Low initially | Small |
| Modular monolith | Single process | Vertical + horizontal replicas | Medium | Small–medium |
| Microservices | Many processes | Per-service horizontal | High | Large |

Key constraints at the time of decision:
- Team of fewer than 10 engineers
- Early-stage product — domain boundaries still evolving
- Single PostgreSQL instance; cross-service transactions would be complex
- Redis already required for Socket.IO adapter and rate limiting
- Need to ship quickly and iterate

## Decision

The backend is a **NestJS modular monolith**.

- All domains live under `backend/src/` as NestJS feature modules
(`@Module()` decorated classes).
- Each module owns its controller, service, entities, and DTOs.
- Cross-domain calls happen through NestJS dependency injection (imported
modules), **not** via HTTP or a message bus.
- The monolith is deployed as a single Docker container / process;
horizontal scaling is achieved by running multiple replicas behind a
load balancer with Redis as the shared session/socket adapter.

If a specific domain needs independent scaling in the future (e.g.,
the multiplayer matchmaking gateway), it can be extracted into a
standalone NestJS microservice using the built-in `@nestjs/microservices`
transport layer with minimal refactoring because the module boundary
already exists.

## Consequences

### Positive
- Single deployment unit — simpler CI/CD and local development
- No distributed-transaction complexity; TypeORM transactions work across
all domains
- NestJS DI container enforces explicit module boundaries without the
operational overhead of separate services
- Straightforward to extract a module into a microservice later

### Negative / Trade-offs
- A poorly written module can import anything, eroding boundaries over
time — code review must enforce the module contract
- A crash in one domain crashes the whole process (mitigated by process
managers and health checks)
- Vertical scaling limits apply; the team must monitor whether any single
domain (e.g., real-time sockets) becomes a bottleneck before extracting
it
71 changes: 71 additions & 0 deletions docs/adr/0004-soroban-over-evm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# ADR-0004: Use Soroban (Stellar) Over EVM-Compatible Chains

**Date:** 2025-07-24
**Status:** Accepted
**Deciders:** StellarHunts core team

---

## Context

StellarHunts awards on-chain NFT badges when players complete puzzle
levels. The team needed to select a smart-contract platform to host:

1. **Game contract** — question lifecycle, answer validation (SHA-256),
player level progression.
2. **NFT badge contract** — per-level badge minting with role-gated
authorization.

The two primary candidates were **Soroban on Stellar** and an
**EVM-compatible chain** (Ethereum mainnet, Polygon, or Base).

| Criterion | Soroban / Stellar | EVM (Ethereum / Polygon) |
|-----------|-------------------|--------------------------|
| Transaction fees | Sub-cent on Stellar | Variable (gwei spikes on mainnet; low on L2s) |
| Finality | ~5 s (Stellar consensus) | ~12 s ETH / ~2 s Polygon |
| Smart contract language | Rust (soroban-sdk) | Solidity / Vyper |
| Tooling maturity | Growing (Stellar CLI, soroban-cli) | Very mature (Hardhat, Foundry, OpenZeppelin) |
| NFT standards | Custom (no ERC-721 equivalent yet) | ERC-721 / ERC-1155 well established |
| Wallet ecosystem | Freighter, Lobstr, Albedo | MetaMask, WalletConnect (broad) |
| Developer community | Smaller, niche | Large, extensive resources |
| Educational alignment | Matches project's Stellar-learning theme | Generic blockchain knowledge |

The project's **educational mission** is to teach players about
blockchain technology — specifically the Stellar ecosystem. Using Soroban
keeps the on-chain layer consistent with the subject matter being taught.

## Decision

StellarHunts uses **Soroban smart contracts on the Stellar network** for
all on-chain game logic and NFT badge minting.

- Contracts are written in **Rust** using `soroban-sdk 22.x`.
- The workspace lives in `onchain/` with a Cargo workspace manifest.
- Two production contracts exist:
- `stellar_hunts` — game logic
- `stellar_hunts_nft` — badge minting
- Local development and CI use Stellar Testnet;
`STELLAR_MODE=mock` allows the backend to run without a live network.
- Answer privacy is preserved on-chain via `env.crypto().sha256()` —
no plaintext answers are stored in contract state.

## Consequences

### Positive
- Aligns with the project's educational goal of teaching Stellar/Soroban
- Very low and predictable transaction fees
- Fast finality reduces wait time after puzzle completion
- Rust's type system and Soroban's sandboxed WASM runtime provide strong
safety guarantees
- `STELLAR_MODE=mock` lets the backend be developed and tested without
a live network dependency

### Negative / Trade-offs
- Smaller developer community means fewer tutorials and third-party
tooling compared to EVM
- No standardized NFT interface (ERC-721) — badge ownership queries use
a custom `has_level_badge` function
- Freighter wallet has less browser/mobile coverage than MetaMask's
ecosystem
- Team members with an EVM background need to learn Rust and the Soroban
execution model
Loading
Loading