chore: scaffold PYRE MVP monorepo (structure + docs)

pnpm + TypeScript workspace per design doc §13:
- apps/{web,api,worker} skeletons (Next.js 16, Fastify 5, BullMQ)
- packages/{core,solana,prometheus,db,config} (core has real types/DTOs;
  solana/prometheus are stubs)
- programs/pyre-core placeholder (future Anchor, v1.0)
- docs/: PYRE_MVP_DESIGN (canonical), ARCHITECTURE, SECURITY, TOKEN_CLASSIFICATION
- CLAUDE.md, README, .env.example (no private-key var by design)

Skeleton + docs only — no Solana/business logic yet. All workspaces typecheck clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-31 02:20:55 +00:00
parent e86b57e00f
commit c20094ab56
65 changed files with 13834 additions and 1 deletions

489
docs/ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,489 @@
# PYRE — Engineering Architecture
> Companion to [`PYRE_MVP_DESIGN.md`](./PYRE_MVP_DESIGN.md). The design document is
> the canonical source of truth; this file describes *how the system is wired
> together*. Where they appear to disagree, the design document wins.
**Tagline:** *Burn the dead. Feed the PYRE. Claim the Spawn.*
---
## 1. System Overview
PYRE is a Solana wallet-cleanup and ritual meme-rebirth protocol. The MVP runs as
three runtime services backed by two stateful stores, and it depends on two
classes of external service: a Solana RPC provider and AI APIs.
Runtime services:
- **`apps/web`** (`pyre-web`) — Next.js frontend. Wallet connect, scanner UI,
cleanup preview, signing, receipt rendering, Prometheus review, admin pages.
- **`apps/api`** (`pyre-api`) — Node.js/Fastify (or Express) HTTP API. Scan
coordination, classification, **unsigned** transaction building, receipt
storage, AI orchestration, admin API. The only service the browser talks to.
- **`apps/worker`** (`pyre-worker`) — Node.js BullMQ worker. Slow/async jobs:
metadata enrichment, AI generation, image prompts, safety/collision checks,
confirmation tracking, receipt enrichment.
Stateful stores:
- **PostgreSQL** — durable record of scans, classifications, receipts,
generations, and Spawn records.
- **Redis** — BullMQ job queue, scan cache, rate limiting, quote cache,
generation status.
External dependencies (never self-hosted on the MVP VPS):
- **Solana RPC** — external provider only. No validator/RPC node on the VPS.
- **AI APIs** — text, image, and moderation APIs only. No local LLM/image models.
### Component / Data-Flow Diagram
```
┌──────────────┐
│ Wallet │ (Phantom / Solflare via Wallet Adapter)
│ (browser) │ signs locally — keys never leave the client
└──────┬───────┘
│ connect / sign / send
┌──────────────┐ static assets / SSR
│ apps/web │◀───────────────────────────┐
│ (Next.js) │ │
└──────┬───────┘ │
│ HTTPS (JSON) │
▼ │
┌───────────────────────────────────────────────┐ │
│ apps/api │ │
│ scan · classify · build (UNSIGNED) · receipt │ │
│ prometheus/generate · admin │ │
└───┬─────────────┬──────────────┬────────────────┘ │
│ │ │ │
│ SQL │ cache/queue │ getAccounts / simulate / │
▼ ▼ │ sendTx / confirm │
┌──────────┐ ┌──────────┐ ▼ │
│ Postgres │ │ Redis │ ┌──────────────┐ │
│ │ │ (BullMQ) │ │ Solana RPC │ (external) │
└──────────┘ └────┬─────┘ └──────────────┘ │
│ │
│ jobs pulled from queue │
▼ │
┌──────────────┐ ┌──────────────┐ │
│ apps/worker │───▶│ AI APIs │ (external) │
│ (BullMQ) │ │ text/image/ │ │
│ │ │ moderation │ │
└──────┬───────┘ └──────────────┘ │
│ writes results / status │
├───────────────▶ Postgres │
└───────────────▶ Redis ────────────────────┘
(generation status read back by web/api)
```
The browser only ever talks to `apps/web` and `apps/api`. The worker is not
network-exposed; it consumes jobs from Redis and writes results back to Postgres
and Redis. RPC and AI calls flow outbound only.
---
## 2. Component Responsibilities
### `apps/web` — User-facing frontend
Next.js + TypeScript + Tailwind + Solana Wallet Adapter, with React Query or
Zustand for state. Responsibilities (§11):
- Wallet connect via Solana Wallet Adapter.
- Token-account display and classification grouping (closeable / burnable /
transmutable / protected / unsupported).
- User confirmations and **transaction preview** UI.
- Transaction **signing** (client-side, via the connected wallet only).
- Receipt rendering and optional share image.
- Prometheus Spawn-package review and public round/Spawn pages.
- Admin / generation review UI.
### `apps/api` — Backend HTTP API
Node.js + Fastify/Express + TypeScript, fronting PostgreSQL, Redis, BullMQ.
Responsibilities (§11):
- Token-scan coordination (calls RPC, persists results).
- Classification helpers and route evaluation.
- **Builds UNSIGNED Solana transactions** — never signs, never holds keys.
- Metadata preparation and AI generation orchestration (enqueues worker jobs).
- Receipt storage and Spawn-record storage.
- Public API and protected admin API.
### `apps/worker` — Background worker
Node.js worker process driven by BullMQ over Redis, with AI API clients.
Responsibilities (§11):
- Slow token-metadata enrichment.
- AI generation jobs (Prometheus).
- Image-prompt generation.
- Safety checks and ticker/name collision checks.
- Background confirmation tracking.
- Receipt enrichment.
### `packages/core` — Shared types & business logic
The single home for cross-service domain definitions (§13):
- Classification **enums**: `EMPTY_CLOSE_ONLY`, `INCINERATE_ONLY`,
`TRANSMUTABLE`, `PROTECTED_SKIP`, `UNSUPPORTED`.
- Conservative **risk rules** (the §7 safety rules: classic SPL only, skip
Token-2022/NFTs/LP/frozen/delegated, USD threshold, price-impact and stale-quote
guards).
- Shared **DTOs** for the API request/response shapes.
- **Receipt schema**.
- **Prometheus input/output schema**.
### `packages/solana` — Solana transaction helpers
Everything that touches the chain at the instruction level (§13):
- Token-account **parsing** (decode raw account state into the scan model).
- **Close-account** transaction builder.
- **Burn** transaction builder.
- **Simulation** helpers (simulate before signing — §7).
- **Transaction decoder** (decode the built tx so it can be matched against the
preview — §8 step 5, §16).
### `packages/prometheus` — AI generation logic
The creative engine's code (§13), invoked by the worker:
- Prompt **templates**.
- The **meta mixer** (probabilistic influence blend — §9).
- Output **parser**.
- **Safety checks** (blocked terms, scam/impersonation/copyright filters).
- **Image-prompt generator**.
### `packages/db` — Database layer
Schema, migrations, and table definitions (§13/§15). Owns the canonical SQL and
migration history; `apps/api` and `apps/worker` depend on it for data access.
### `packages/config` — Shared config
Shared configuration and environment loading (§13). Centralizes reading
`.env`-supplied secrets (RPC URL, AI keys, DB/Redis connection strings) so no
secret is hard-coded.
### `programs/pyre-core` — Future Anchor program
NOT part of the first burner MVP. The future on-chain trust core (rounds, Essence
vault, claims, refunds) — built only at Phase 7 / v1.0.
---
## 3. Request / Data Flow — The Burner Journey
The core flow is the 8-step burner sequence from §8. The critical trust property:
**the server builds an UNSIGNED transaction, the client signs it, and the decoded
transaction is matched against the preview before signing.**
```
1. Connect ──▶ 2. Scan ──▶ 3. Classify ──▶ 4. Preview ──▶ 5. Build (UNSIGNED)
8. Receipt ◀── 7. Confirm ◀── 6. Sign (client) ◀── decode & match against preview
```
1. **Wallet Connect** — user connects via Solana Wallet Adapter (`apps/web`).
No keys leave the browser.
2. **Account Scan**`apps/web` calls `POST /api/scan`. `apps/api` queries token
accounts via the external RPC, capturing for each: owner, ATA address, mint,
token program, balance, decimals, rent estimate, metadata (if available),
token-program type, frozen/delegated state. Results persist to
`wallet_scans` / `token_accounts`; slow metadata enrichment may be deferred to
a worker job. Scan results are cached in Redis.
3. **Classification** — each account is classified server-side into the §6
categories and grouped for the UI. **Client-submitted classifications are not
trusted** — the server recomputes where needed (§16).
4. **Preview** — before any signing the user sees: accounts to close, tokens to
burn, accounts skipped, estimated rent returned, transaction fees, warnings,
and the destination wallet for recovered rent (which is the user's own wallet).
5. **Build Transaction**`apps/web` calls `POST /api/build/close-empty` (and/or
`/api/build/burn`). `apps/api` uses `packages/solana` to build an **unsigned**
transaction and returns it base64-encoded plus the preview. The client decodes
the transaction (`packages/solana` decoder) and **matches it against the
preview**; any mismatch aborts before signing.
6. **User Signs** — locally, with the connected wallet. PYRE never requests
private keys and never performs custodial signing.
7. **Confirmation** — the signed transaction is sent and confirmed via RPC. A
worker confirmation-tracking job may follow the signature to durable finality.
8. **Receipt**`apps/web` calls `POST /api/receipt`; `apps/api` records and
returns the receipt: tx signature, accounts closed, tokens burned, rent
returned, accounts skipped, warnings, timestamp, optional share image. A worker
receipt-enrichment job may backfill metadata.
All transactions are simulated before final signing (§7), and all build requests
are logged (§16).
---
## 4. API Surface
Reproduced from §14. All endpoints are served by `apps/api`. Admin endpoints are
authenticated and protected separately (§16).
### `POST /api/scan`
```jsonc
// in:
{ "wallet": "wallet_pubkey" }
// out:
{
"scanId": "uuid",
"wallet": "wallet_pubkey",
"summary": {
"totalAccounts": 0,
"emptyCloseOnly": 0,
"incinerateOnly": 0,
"transmutable": 0,
"protectedSkip": 0,
"unsupported": 0,
"estimatedRentLamports": 0
},
"accounts": []
}
```
### `POST /api/build/close-empty`
```jsonc
// in:
{ "wallet": "...", "accountAddresses": ["ata1", "ata2"] }
// out:
{
"transactionBase64": "...",
"preview": {
"accountsToClose": [],
"estimatedRentReturnedLamports": 0,
"rentDestination": "user_wallet_pubkey"
}
}
```
### `POST /api/build/burn`
```jsonc
// in:
{ "wallet": "...", "items": [{ "tokenAccount": "...", "mint": "...", "amount": "..." }] }
// out:
{
"transactionBase64": "...",
"preview": {
"tokensToBurn": [],
"accountsPotentiallyClosable": []
}
}
```
### `POST /api/receipt`
```jsonc
// in:
{ "wallet": "...", "txSignature": "...", "scanId": "uuid" }
// out:
{
"receiptId": "uuid",
"txSignature": "...",
"rentReturnedLamports": 0,
"closedAccounts": [],
"burnedTokens": [],
"skippedTokens": []
}
```
### `POST /api/prometheus/generate`
```jsonc
// in:
{ "receiptId": "uuid", "chaos": 0.25, "operatorSeed": "optional" }
// out:
{
"generationId": "uuid",
"spawnName": "...",
"ticker": "...",
"lore": "...",
"imagePrompt": "...",
"metadata": {},
"riskFlags": []
}
```
### Admin endpoints
Admin / generation-review endpoints (approve/reject generations, record Pump.fun
launches, view system events) are protected and not part of the public surface.
They back the admin review UI and the Spawn-record workflow (§10, §16).
---
## 5. Data Model
### Initial PostgreSQL tables (§15)
```
wallet_scans: id, wallet, status, created_at, completed_at, summary_json
token_accounts: id, scan_id, wallet, ata, mint, token_program,
raw_balance, ui_balance, decimals, symbol, name,
classification, warnings_json, estimated_rent_lamports,
created_at
cleanup_receipts: id, wallet, scan_id, tx_signature, rent_returned_lamports,
closed_accounts_count, burned_tokens_count, status,
created_at, receipt_json
prometheus_generations: id, receipt_id, input_json, output_json, status,
risk_flags_json, created_at, approved_at, rejected_at
spawn_records: id, generation_id, spawn_name, ticker, mint, metadata_uri,
pumpfun_url, launch_tx, status, created_at
```
Additional initial tables called out in §11/§15: `token_classifications`,
`burn_events`, `close_account_events`, `spawn_candidates`, and `system_events`
(audit/event log).
### Future tables (NOT in the burner MVP)
These arrive with the Essence ledger and PYRE Core program (Phases 67):
- `pyre_rounds` — round state machine.
- `essence_contributions` — recorded Essence per wallet per round.
- `claim_records` — Spawn claim accounting.
- `refund_records` — refunds for failed rounds.
- `influence_fields` — meta-influence inputs for Prometheus weighting.
> Per §5/§15: Essence may start in the database, but it must not be called a
> deposit until an on-chain custody model exists, and claims must not be promised
> until claim logic exists.
---
## 6. Redis Usage
Redis serves five roles (§11):
- **Job queue** — backs BullMQ; `apps/api` enqueues, `apps/worker` consumes.
- **Scan cache** — caches scan results so repeat views don't re-hit RPC.
- **Rate limiting** — throttles scan and build endpoints (§16).
- **Temporary quote cache** — short-lived swap/route quotes; stale quotes are
rejected by classification (§7).
- **Generation status** — live status of in-flight Prometheus jobs, read back by
`apps/web` / `apps/api`.
### BullMQ Worker Jobs
`apps/worker` processes these job types (§11):
- **Metadata enrichment** — slow token name/symbol/metadata lookups.
- **AI generation** — Prometheus Spawn generation.
- **Image prompts** — image-prompt generation for the Spawn.
- **Safety checks** — moderation of generated names/tickers/lore.
- **Collision checks** — ticker/name duplicate detection.
- **Confirmation tracking** — follow tx signatures to finality in the background.
- **Receipt enrichment** — backfill receipt detail after confirmation.
---
## 7. Solana RPC Requirements
PYRE uses an **external RPC provider only**. Per §11/§12, the MVP VPS must **not**
run a Solana validator or RPC node.
Required RPC capabilities (§11):
- Get token accounts by owner.
- Get account info.
- Simulate transactions.
- Send transactions.
- Confirm transactions.
- Parse token-account state.
The RPC endpoint URL is supplied via environment config (`packages/config`) and
treated as a secret. All chain access flows through `packages/solana`.
---
## 8. AI Services
AI is **API-based only** (§11/§12). The MVP server must **not** run local LLMs or
local image-generation models.
Service classes used:
- **Text generation** — Spawn name, ticker, lore, tagline, description, launch
copy (§9 outputs).
- **Image generation** — from the generated image prompt.
- **Moderation / safety** — filtering hate/explicit/extremist/copyrighted/
impersonation/scam-like output (§16).
All AI calls are made from `apps/worker` using clients configured via secrets in
`packages/config`. Prometheus generates a *candidate package for human review*
it never launches tokens or controls funds (§9).
---
## 9. Infrastructure & Deployment
A single **8GB VPS** is sufficient for the MVP (§12). It runs:
- `pyre-web`, `pyre-api`, `pyre-worker`
- PostgreSQL
- Redis
- **nginx** (reverse proxy / TLS termination in front of web + api)
- **PM2 or systemd** (process supervision for the three Node services)
- logs / log rotation
- admin dashboard
The VPS must **NOT** run (§12):
- a Solana validator,
- a Solana RPC node,
- a large local LLM,
- local image generation,
- heavy indexing at scale.
**Base setup already completed (§12/§19):** `pyre` user created, root login
disabled, SSH key auth, UFW firewall, Fail2ban, basic hardening.
**Next setup (§12/§19):** Node.js 22, pnpm, Git, nginx, PostgreSQL, Redis, PM2,
Claude Code, project repo, environment files, backup script, log rotation.
**External accounts required (§19):** Solana RPC provider, OpenAI/Anthropic API
key, image-generation provider, domain name, GitHub repo, Pump.fun creator
wallet, optional IPFS/Arweave metadata service.
---
## 10. Build-Order Note (Phased Roadmap)
Components map to the §18 development phases so a reader knows *what gets built
when*:
| Phase | Focus | Components touched |
|-------|-------|--------------------|
| **0 — Server & Repo Setup** | VPS, pnpm workspace, web/api/worker skeletons, Postgres + Redis, nginx, env templates | all apps (skeleton), `packages/config`, `infra/` |
| **1 — Wallet Scanner** | Wallet connect, scan endpoint, token-account fetch, basic classification, results + protected/skipped UI | `apps/web`, `apps/api`, `packages/solana` (parsing), `packages/core` (enums/rules), `packages/db` (`wallet_scans`, `token_accounts`) |
| **2 — Close Empty ATAs** | Build close-account tx, decode/preview, signing, confirmation tracking, receipt page | `packages/solana` (close builder + decoder + simulation), `apps/api` (`/build/close-empty`, `/receipt`), `apps/worker` (confirmation tracking), `packages/db` (`cleanup_receipts`, `close_account_events`) |
| **3 — Burn Junk** | `INCINERATE_ONLY` classification, burn builder, burn-then-close, stronger confirmations, receipt update | `packages/solana` (burn builder), `apps/api` (`/build/burn`), `packages/db` (`burn_events`) |
| **4 — Prometheus Generator** | Generation input from receipt, meta mixer, name/ticker/lore + image prompt, safety checks, admin approval UI | `packages/prometheus`, `apps/worker` (AI/safety/collision jobs), `apps/api` (`/prometheus/generate`, admin), `apps/web` (review UI), `packages/db` (`prometheus_generations`, `spawn_candidates`) |
| **5 — Manual Pump.fun Launch** | Approved package, metadata JSON, operator checklist, record mint/url/tx, public Spawn page | `apps/web` (public Spawn page), `apps/api` (admin record endpoints), `packages/db` (`spawn_records`) |
| **6 — Essence / Round Prototype** | Swap-candidate detection, route quote preview, net Essence estimate, round dashboard, DB-only contribution record (no claim promises) | `packages/solana` (route eval), `apps/api`, `packages/db` (future `pyre_rounds`, `essence_contributions`, `influence_fields`) |
| **7 — PYRE Core Program** | Anchor program: create round, contribute Essence, contribution-receipt PDA, lock round, register Spawn, claim, refund, tests | `programs/pyre-core`, future tables `claim_records`, `refund_records` |
The first three phases deliver the trust-building burner (v0.1). The Anchor
program in `programs/pyre-core` is explicitly **not** needed until Phase 7 / v1.0.
---
> **PYRE returns your rent. The scraps feed the fire.**

559
docs/PYRE_MVP_DESIGN.md Normal file
View File

@@ -0,0 +1,559 @@
# PYRE / Prometheus Protocol — MVP Design Document
> Canonical source of truth for the PYRE MVP. This document is reproduced from
> the original design brief. When in doubt, this file wins.
**Tagline:** *Burn the dead. Feed the PYRE. Claim the Spawn.*
---
## 1. Project Summary
PYRE is a Solana wallet-cleanup and ritual meme rebirth protocol.
Users connect a Solana wallet. PYRE scans SPL token accounts for empty
accounts, abandoned balances, dust tokens, scam tokens, illiquid remnants, and
unsupported assets. The system classifies each token account and helps the user
safely perform cleanup actions.
The initial MVP focuses on **wallet cleanup and trust**:
1. Scan the user wallet.
2. Identify token accounts.
3. Classify assets as safe-to-close, burnable, transmutable, or protected.
4. Let the user close empty associated token accounts.
5. Return recovered ATA rent to the user.
6. Generate a clear PYRE receipt.
The later ritual layer uses **Prometheus**, an AI firebringer engine, to
generate a new meme token identity (a **Spawn**) from burned/transmuted token
remnants. The first launch flow is semi-manual through Pump.fun before full
protocol automation is attempted.
> The MVP should **not** start as a fully automated on-chain launch protocol. It
> should start as a safe burner/cleaner with a beautiful receipt experience.
---
## 2. Core Product Positioning
PYRE is **not** an investment product, yield mechanism, trading bot,
guaranteed-profit system, or protected launch mechanism.
PYRE **is**: wallet cleanup, token scrap transmutation, recovered rent return,
transparent contribution accounting, AI-generated meme rebirth, and ritual
entertainment.
> PYRE cleans dead Solana remnants from your wallet, returns recovered rent to
> you, and optionally lets safely swapped scraps feed a ritual round that
> Prometheus uses to birth a new AI-generated Spawn.
---
## 3. Core Trust Rule
> **Recovered ATA rent returns to the user by default.**
Rent must not be silently taxed, redirected, pooled, or used as Essence unless a
future version creates an explicit opt-in donation mode.
For MVP:
- recovered rent goes to the user,
- burned junk does not count as Essence,
- swapped scraps may become Essence **only if the user explicitly approves**,
- optional SOL contribution must be separate and explicit,
- all actions require wallet approval,
- **PYRE never has custody of private keys.**
> **PYRE returns your rent. The scraps feed the fire.**
---
## 4. MVP Philosophy
Narrow, practical, and trust-building. Do **not** build the full ritual launch
protocol first.
Build this first:
```
Connect wallet
→ scan token accounts
→ classify accounts
→ close eligible empty ATAs
→ optionally burn obvious junk
→ return rent to user
→ show receipt
```
Then add:
```
Burned/transmuted token metadata
→ Prometheus AI generation
→ manual Pump.fun launch package
```
Then later add:
```
Essence ledger
→ contribution proofs
→ round state machine
→ Spawn distribution
→ claim logic
```
The first emotional win: *"PYRE cleaned my wallet and returned SOL I forgot was
trapped in token accounts."* That trust moment makes the later ritual layer
believable.
---
## 5. MVP Feature Scope
### MVP v0.1 — Burner / Cleaner
**Required:**
- Solana wallet connect
- Read token accounts
- Detect empty associated token accounts
- Estimate reclaimable rent
- Classify obvious protected assets
- Build close-account transactions
- User signs locally
- Send rent back to user wallet
- Display transaction result
- Generate PYRE receipt
**Optional but desirable:** detect burnable junk tokens; allow user to burn
selected junk to zero; close emptied accounts after burn; show
skipped/protected tokens; export/share receipt image.
**v0.1 must NOT include:** automatic Pump.fun launch, user-contributed Essence
vault, custom PYRE Solana program, Token-2022 support, NFT handling, automatic
valuable-token sacrifice, custodial signing, background wallet automation.
### MVP v0.2 — Prometheus Meta Mixer
AI generation from burned/cleaned token context.
**Input:** token names, symbols, metadata, categories, burned count,
transmutable count, source archetypes, optional theme seed, chaos factor.
**Output:** spawn name, ticker, lore, tagline, image prompt, metadata
description, Pump.fun launch description, risk/safety warnings, forbidden-term
check, duplicate ticker/name check.
Prometheus must **not** directly launch tokens — it generates a candidate
package for human review.
### MVP v0.3 — Manual Pump.fun Creator Workflow
```
Burner receipt → Prometheus generates Spawn package → human reviews package
→ human manually creates Pump.fun token → PYRE records mint, launch URL,
metadata, tx → public Spawn record page
```
Validates metadata/art/ticker quality, the Pump.fun creation process, user
understanding, and social reaction without dangerous automation.
### MVP v0.4 — Essence Ledger
```
safe scrap swap output → net SOL value → record as Essence
→ associate with wallet and round → show round progress
```
Can start in a database before moving on-chain. **Important:** do not call it a
deposit until an on-chain custody model exists; do not promise claims until
claim logic exists; keep it experimental if not on-chain.
### MVP v1.0 — PYRE Core Program
Custom Solana program for trust-critical accounting: create round, accept
Essence, hold Essence vault, record contribution receipts, lock round, register
Spawn mint, open claims, distribute Spawn pro rata, support refunds for failed
rounds, prevent double claims. This is where PYRE becomes a true protocol.
---
## 6. Token Classification
Token accounts are classified into conservative categories.
- **EMPTY_CLOSE_ONLY** — zero balance, can be closed. *Action:* close ATA, send
rent to user wallet.
- **INCINERATE_ONLY** — no safe swap route but may be burnable. *Action:* user
may burn balance to zero; if account becomes empty, close it; recovered rent
returns to user.
- **TRANSMUTABLE** — has a safe swap route and passes risk checks. *Action:* user
may swap token into SOL; net swapped SOL may become Essence **only if the user
opts in**.
- **PROTECTED_SKIP** — not touched by default. Examples: SOL/WSOL special cases,
USDC/USDT/major assets, valuable meme tokens, NFTs, LP tokens, receipt tokens,
staked tokens, suspicious tokens, frozen accounts, delegated accounts,
high-value balances.
- **UNSUPPORTED** — system cannot safely reason about it. Examples: Token-2022 in
MVP, unknown token program, bad metadata, unsupported account layout, accounts
with extension behavior not yet handled.
> **Default rule: Unknown means skip.**
---
## 7. Token Safety Rules
The classifier must be conservative. MVP rules:
- Classic SPL only.
- Skip Token-2022 by default.
- Skip NFTs.
- Skip compressed NFTs.
- Skip LP tokens.
- Skip frozen accounts.
- Skip delegated accounts.
- Skip known valuable assets.
- Skip tokens above a user-safe USD threshold.
- Skip routes with high price impact.
- Skip routes with stale quotes.
- Skip unsafe or weird liquidity paths.
- Simulate all transactions before final signing.
> The system should never say *"This token is safe."*
> It should say *"This token appears eligible based on current checks."*
---
## 8. Burner Transaction Flow
1. **Wallet Connect** — user connects via Solana Wallet Adapter.
2. **Account Scan** — query token accounts. For each: owner, ATA address, mint,
token program, balance, decimals, rent estimate, metadata if available, token
program type, frozen/delegated state if available.
3. **Classification** — each account classified; UI groups into: closeable empty
accounts, burnable junk, potentially transmutable scraps, protected/skipped,
unsupported.
4. **Preview** — before signing the user sees: accounts to close, tokens to burn,
accounts skipped, estimated rent returned, transaction fees, warnings,
destination wallet for recovered rent.
5. **Build Transaction** — backend/frontend builds an unsigned Solana
transaction. It must be decoded and compared against the preview.
6. **User Signs** — locally, with the wallet. PYRE must never ask for private keys.
7. **Confirmation** — system waits for transaction confirmation.
8. **Receipt** — shows tx signature, accounts closed, tokens burned, rent
returned, accounts skipped, warnings, timestamp, optional share image.
---
## 9. Prometheus AI Meta Mixer
Prometheus is the creative engine. It generates Spawn identity, **not** control
of funds.
**Input:** `burned_tokens[]`, `transmuted_tokens[]`, `token_symbols[]`,
`token_names[]`, `metadata_descriptions[]`, `dominant_archetypes[]`,
`chaos_factor`, `manual_theme_seed_optional`.
**Output:** `spawn_name`, `spawn_ticker`, `spawn_lore`, `spawn_tagline`,
`spawn_description`, `image_prompt`, `metadata_json`, `launch_copy`, `risk_flags`.
**Meta influence** (probabilistic, not deterministic) — example model:
```
40% burned token archetypes
25% Essence-weighted token themes
20% Prometheus mutation / chaos
15% manual operator seed
```
Do not allow users to force exact copyrighted or existing meme identities.
- Bad: *"Create a Pepe token."*
- Better: *"Frog archetype influence increased."*
Prometheus should create inspired mutations, not direct clones.
---
## 10. Pump.fun Creator Workflow
The first Pump.fun integration is manual / approval-gated.
**Manual MVP flow:** Prometheus generates token package → operator reviews →
operator creates token on Pump.fun → operator records mint and launch link →
PYRE shows Spawn record.
**Semi-automated future:** generates package → backend uploads metadata →
backend builds create transaction → creator/multisig signs → token launches →
PYRE records launch.
**Fully automated future** (NOT MVP): round locks → Prometheus generates Spawn →
metadata uploaded → launch tx built → Essence used for initial acquisition →
Spawn distributor receives tokens → contributors claim pro rata.
---
## 11. Architecture
**Frontend** — Next.js, TypeScript, Tailwind, Solana Wallet Adapter, React Query
or Zustand, transaction preview UI, receipt UI, admin/generation UI.
*Responsibilities:* wallet connect, token account display, classification
grouping, user confirmations, transaction signing, receipt rendering, Spawn
package review, public round/spawn pages.
**Backend API** — Node.js, Fastify or Express, TypeScript, PostgreSQL, Redis,
BullMQ. *Responsibilities:* token scan coordination, classification helpers,
route evaluation, AI generation orchestration, metadata preparation, receipt
storage, Spawn record storage, public API, admin API.
**Worker** — Node.js worker process, BullMQ queue, Redis, AI API clients.
*Responsibilities:* slow token metadata enrichment, AI generation jobs, image
prompt generation, safety checks, ticker/name collision checks, background
confirmation tracking, receipt enrichment.
**Database** — PostgreSQL.
*Initial tables:* `wallet_scans`, `token_accounts`, `token_classifications`,
`cleanup_receipts`, `burn_events`, `close_account_events`,
`prometheus_generations`, `spawn_candidates`, `spawn_records`, `system_events`.
*Future tables:* `pyre_rounds`, `essence_contributions`, `claim_records`,
`refund_records`, `influence_fields`.
**Redis** — job queue, scan cache, rate limiting, temporary quote cache,
generation status.
**Solana RPC** — external provider. Do **not** run a validator/RPC node on the
MVP VPS. Required: get token accounts by owner, get account info, simulate
transactions, send transactions, confirm transactions, parse token account state.
**AI Services** — API-based first (text generation, image generation,
moderation/safety, custom prompt templates). Do **not** run local LLMs or image
models on the MVP server.
---
## 12. Server / Infrastructure Setup
An 8GB VPS is enough for the MVP. Run: `pyre-web`, `pyre-api`, `pyre-worker`,
postgres, redis, nginx, pm2 or systemd, logs, admin dashboard.
Do **not** run: a Solana validator, a Solana RPC node, a large local LLM, local
image generation, or heavy indexing at scale.
**Server base setup — already completed:** new user `pyre`, root disabled, SSH
key auth, UFW configured, Fail2ban installed, basic security hardening.
**Next setup:** Node.js 22, pnpm, Git, nginx, PostgreSQL, Redis, PM2, Claude
Code, project repo, environment files, backup script, log rotation.
---
## 13. Recommended Repo Structure
```
pyre/
apps/
web/
api/
worker/
packages/
core/
solana/
prometheus/
db/
config/
programs/
pyre-core/
docs/
PYRE_MVP_DESIGN.md
ARCHITECTURE.md
SECURITY.md
TOKEN_CLASSIFICATION.md
scripts/
infra/
preview.html
CLAUDE.md
package.json
pnpm-workspace.yaml
```
- **apps/web** — user-facing app: landing, wallet connect, scanner UI, cleanup
preview, receipt page, Prometheus generation preview, admin review page.
- **apps/api** — backend HTTP API: scan, classify, build transaction, receipt,
generation, admin endpoints.
- **apps/worker** — background workers: async metadata lookup, AI generation,
safety checks, tx confirmation watcher, receipt enrichment.
- **packages/core** — shared types and business logic: classification enums,
risk rules, shared DTOs, receipt schema, Prometheus input/output schema.
- **packages/solana** — Solana transaction helpers: token account parsing,
close-account tx builder, burn tx builder, simulation helpers, tx decoder.
- **packages/prometheus** — AI generation logic: prompt templates, meta mixer,
output parser, safety checks, image prompt generator.
- **packages/db** — database schema, migrations, table definitions.
- **packages/config** — shared config and environment loading.
- **programs/pyre-core** — future Anchor program (NOT needed for the first burner
MVP).
---
## 14. API Design
```http
POST /api/scan
# in: { "wallet": "wallet_pubkey" }
# out: { scanId, wallet, summary: { totalAccounts, emptyCloseOnly,
# incinerateOnly, transmutable, protectedSkip, unsupported,
# estimatedRentLamports }, accounts: [] }
POST /api/build/close-empty
# in: { "wallet": "...", "accountAddresses": ["ata1","ata2"] }
# out: { transactionBase64, preview: { accountsToClose,
# estimatedRentReturnedLamports, rentDestination } }
POST /api/build/burn
# in: { "wallet": "...", "items": [{ tokenAccount, mint, amount }] }
# out: { transactionBase64, preview: { tokensToBurn, accountsPotentiallyClosable } }
POST /api/receipt
# in: { "wallet": "...", "txSignature": "...", "scanId": "uuid" }
# out: { receiptId, txSignature, rentReturnedLamports, closedAccounts,
# burnedTokens, skippedTokens }
POST /api/prometheus/generate
# in: { "receiptId": "uuid", "chaos": 0.25, "operatorSeed": "optional" }
# out: { generationId, spawnName, ticker, lore, imagePrompt, metadata, riskFlags }
```
---
## 15. MVP Database Schema
```
wallet_scans: id, wallet, status, created_at, completed_at, summary_json
token_accounts: id, scan_id, wallet, ata, mint, token_program,
raw_balance, ui_balance, decimals, symbol, name,
classification, warnings_json, estimated_rent_lamports,
created_at
cleanup_receipts: id, wallet, scan_id, tx_signature, rent_returned_lamports,
closed_accounts_count, burned_tokens_count, status,
created_at, receipt_json
prometheus_generations: id, receipt_id, input_json, output_json, status,
risk_flags_json, created_at, approved_at, rejected_at
spawn_records: id, generation_id, spawn_name, ticker, mint, metadata_uri,
pumpfun_url, launch_tx, status, created_at
```
(Also: `token_classifications`, `burn_events`, `close_account_events`,
`spawn_candidates`, `system_events`.)
---
## 16. Security Requirements
**Wallet Security:** Never request private keys. Never run custodial signing.
Never auto-execute without user signing. Always show transaction preview. Decode
transaction before signing. Match decoded transaction against preview.
**Token Safety:** Unknown assets default to skip. Token-2022 default skip for
MVP. NFTs default skip. Valuable assets default skip. User must manually select
anything risky. High-value actions require stronger confirmation.
**Backend Security:** Rate-limit scan endpoints. Validate wallet pubkeys.
Validate token account ownership. Do not trust client-submitted classifications;
recompute server-side where needed. Log all transaction build requests. Protect
admin endpoints. Use environment secrets only. Rotate API keys if leaked.
**AI Safety:** Filter generated names, tickers, lore. Avoid hate, explicit,
extremist, copyrighted, impersonation, and scam-like outputs. Require human
approval before first launches.
---
## 17. Abuse Scenarios
- **Misclassification of valuable asset** → user loses valuable token. *Defense:*
protected token registry, USD value threshold, major-token skip list, manual
advanced override only.
- **Fake dust farming** → user creates many fake tokens to influence status.
*Defense:* no allocation by token count, no reward by account count, Essence
weighting only in future versions.
- **Rent confusion** → people claim PYRE steals rent. *Defense:* receipt clearly
shows rent returned, transaction preview clearly shows destination, do not pool
rent by default.
- **AI output abuse** → Prometheus generates offensive/copyrighted token.
*Defense:* safety filters, blocked terms, ticker collision checks, human
approval in MVP.
- **Pump.fun launch abuse** → Spawn launches look like team-rug launches.
*Defense:* manual review, public launch record, no profit promises, clear
entertainment framing, disclose launch wallet / creator.
---
## 18. MVP Development Phases
- **Phase 0 — Server and Repo Setup:** VPS configured, Claude Code installed,
repo initialized, pnpm workspace created, web/api/worker skeleton, Postgres +
Redis running, nginx configured, environment templates.
- **Phase 1 — Wallet Scanner:** wallet connect frontend, scan endpoint, token
account fetch, basic classification, scan results UI, protected/skipped UI.
- **Phase 2 — Close Empty ATAs:** identify empty token accounts, build
close-account tx, decode tx preview, wallet signing, confirmation tracking,
receipt page.
- **Phase 3 — Burn Junk:** incinerate-only classification, burn transaction
builder, burn-then-close flow, stronger confirmations, receipt update.
- **Phase 4 — Prometheus Generator:** generation input from receipt, meta mixer,
Spawn name/ticker/lore generation, image prompt generation, safety checks,
admin approval UI.
- **Phase 5 — Manual Pump.fun Launch Workflow:** approved Spawn package, metadata
JSON, operator launch checklist, mint/url/tx record input, public Spawn record
page.
- **Phase 6 — Essence / Round Prototype:** safe swap candidate detection, route
quote preview, net Essence estimate, round dashboard, contribution database
record, no claim promises until on-chain logic exists.
- **Phase 7 — PYRE Core Program:** Anchor program — create round, contribute
Essence, contribution receipt PDA, lock round, register Spawn, claim Spawn,
refund failed round, tests.
---
## 19. What We Need Set Up Now
**Server — already done:** pyre user, SSH auth key, root disabled, UFW, Fail2ban.
**Server — next:** Node.js 22, pnpm, Git, nginx, PostgreSQL, Redis, PM2, Claude
Code, repo directory, .env files, backup script, logrotate config.
**External accounts / services:** Solana RPC provider, OpenAI/Anthropic API key,
image generation provider, domain name, GitHub repo, Pump.fun creator wallet,
optional IPFS/Arweave metadata service.
**Local / dev tools:** VS Code or SSH workflow, Claude Code, Git, Node.js, pnpm,
Postgres client, Redis CLI, Solana CLI (later), Anchor (later).
**Project files to create:** `CLAUDE.md`, `README.md`, `docs/PYRE_MVP_DESIGN.md`,
`docs/SECURITY.md`, `docs/TOKEN_CLASSIFICATION.md`, `.env.example`,
`pnpm-workspace.yaml`.
---
## 20. First Claude Code Prompts
After repo + CLAUDE.md exist, **plan first (no code):**
> Read CLAUDE.md and docs/PYRE_MVP_DESIGN.md. Do not write code yet. Produce an
> implementation plan for PYRE MVP v0.1 focused only on wallet scanning, token
> account classification, close-empty-ATA transaction building, transaction
> preview, and receipt generation. Identify the exact packages, APIs, database
> tables, and test cases needed.
Then, after plan review (**skeleton only**):
> Create the monorepo skeleton with pnpm workspaces. Add apps/web, apps/api,
> apps/worker, packages/core, packages/solana, packages/prometheus, packages/db,
> and docs. Add TypeScript configs, package.json files, README files, and
> .env.example files. Do not implement Solana transaction logic yet.
---
## 21. MVP Definition of Done
The MVP is complete when a user can: connect wallet, scan token accounts, see
clear classifications, select empty ATAs, preview the close-account transaction,
sign it, recover rent to their wallet, see a receipt, generate a Prometheus Spawn
concept from the receipt, manually record a Pump.fun launch, and view a public
Spawn record page.
> The MVP is **not** complete if it requires users to trust hidden backend logic.
The MVP succeeds if users say: *"This cleaned my wallet, returned SOL, and the
receipt/generation was cool."* That is the foundation for the full PYRE protocol.

128
docs/SECURITY.md Normal file
View File

@@ -0,0 +1,128 @@
# Security
> Security model and threat documentation for PYRE. Aligned with
> [`PYRE_MVP_DESIGN.md`](PYRE_MVP_DESIGN.md) §3, §16, and §17 — when in doubt, the
> design doc wins.
PYRE is a wallet-cleanup tool. Its entire value proposition rests on trust:
users connect a wallet and let PYRE help them destroy or close token accounts.
That only works if PYRE can never harm the user, never take custody of funds, and
never hide what it is doing.
---
## Core trust rule
> **Recovered ATA rent returns to the user by default.**
Rent must **never** be silently taxed, redirected, pooled, or counted as Essence
unless a future version creates an explicit opt-in donation mode.
For the MVP:
- Recovered rent goes to the user.
- Burned junk does **not** count as Essence.
- Swapped scraps may become Essence **only if the user explicitly approves**.
- Any optional SOL contribution must be separate and explicit.
- All actions require wallet approval.
- **PYRE never has custody of private keys.**
> **PYRE returns your rent. The scraps feed the fire.**
---
## Wallet security
- **Never request private keys.**
- **Never run custodial signing.**
- **Never auto-execute without the user signing** locally in their own wallet.
- **Always show a transaction preview** before any signature is requested.
- **Decode the transaction before signing.**
- **Match the decoded transaction against the preview** (accounts to close,
tokens to burn, rent amount, rent destination, fees, warnings). If the decoded
transaction does not match the preview, do not sign.
---
## Token safety
- Unknown assets default to **skip**.
- Token-2022 defaults to **skip** for the MVP.
- NFTs default to **skip**.
- Valuable assets default to **skip**.
- The user must **manually select** anything risky.
- High-value actions require **stronger confirmation**.
For the full classifier specification — categories, allowed actions, the
"Unknown means skip" rule, and the complete safety checklist — see
[`TOKEN_CLASSIFICATION.md`](TOKEN_CLASSIFICATION.md).
---
## Backend security
- **Rate-limit scan endpoints.**
- **Validate wallet pubkeys.**
- **Validate token-account ownership** (the account must belong to the wallet).
- **Do not trust client-submitted classifications** — recompute classification
server-side before building any transaction.
- **Log all transaction build requests.**
- **Protect admin endpoints.**
- **Use environment secrets only.**
- **Rotate API keys if leaked.**
---
## AI safety
- **Filter** generated names, tickers, and lore.
- **Avoid** hate, explicit, extremist, copyrighted, impersonation, and scam-like
outputs.
- **Require human approval before first launches.** Prometheus generates a
candidate package for human review; it never launches tokens directly.
---
## Abuse scenarios
Reproduced from design doc §17. Each is stated as **Risk → Defense**.
### Misclassification of valuable asset
- **Risk:** The classifier mislabels a valuable token and the user loses it.
- **Defense:** Protected token registry, USD value threshold, major-token skip
list, and manual advanced override only.
### Fake dust farming
- **Risk:** A user creates many fake tokens to inflate their status.
- **Defense:** No allocation by token count, no reward by account count; Essence
weighting only in future versions.
### Rent confusion
- **Risk:** People claim PYRE steals rent.
- **Defense:** The receipt clearly shows rent returned, the transaction preview
clearly shows the destination, and rent is not pooled by default.
### AI output abuse
- **Risk:** Prometheus generates an offensive or copyrighted token.
- **Defense:** Safety filters, blocked terms, ticker collision checks, and human
approval in the MVP.
### Pump.fun launch abuse
- **Risk:** Spawn launches look like team-rug launches.
- **Defense:** Manual review, public launch record, no profit promises, clear
entertainment framing, and disclosure of the launch wallet / creator.
---
## No private keys, anywhere
PYRE is non-custodial by design. There is intentionally **no private-key or
mnemonic environment variable anywhere in the project**, and there never will be.
All signing happens client-side in the user's wallet. Any change that introduces
key custody, mnemonic handling, or custodial signing violates the core trust
rule and must be rejected.

View File

@@ -0,0 +1,186 @@
# Token Classification
> Spec for the PYRE token-account classifier. Aligned with
> [`PYRE_MVP_DESIGN.md`](PYRE_MVP_DESIGN.md) §6 and §7 — when in doubt, the design
> doc wins.
PYRE scans a wallet's SPL token accounts and assigns each one a single
conservative category. The category determines what action (if any) the user is
allowed to take and how recovered rent / Essence are handled.
---
## Philosophy: conservative by default
The classifier exists to protect the user from accidentally destroying value. It
is intentionally cautious. It never optimizes for "more accounts cleaned" at the
cost of safety. When two readings of an account are possible, it picks the safer
(less destructive) one.
> **Default rule: Unknown means skip.**
Anything the system cannot fully and safely reason about — an unknown token
program, bad or missing metadata, an unsupported account layout, unfamiliar
extension behavior — is classified into a non-destructive category
(`PROTECTED_SKIP` or `UNSUPPORTED`) and is **never acted on by default**. The
user must manually opt in to anything risky.
The classifier must never say *"this token is safe."* It may only say
*"this token appears eligible based on current checks."* See
[Wording rule](#wording-rule).
---
## Classification categories
Each token account is assigned exactly one of the following categories. The
canonical enum lives in `packages/core` (see [Canonical enum](#canonical-enum)).
### `EMPTY_CLOSE_ONLY`
- **Enum identifier:** `EMPTY_CLOSE_ONLY`
- **Meaning:** A zero-balance token account that can be closed.
- **Allowed action(s):** Close the associated token account (ATA).
- **Rent / Essence rules:** Recovered ATA rent is sent to the **user's wallet**.
Nothing is counted as Essence.
### `INCINERATE_ONLY`
- **Enum identifier:** `INCINERATE_ONLY`
- **Meaning:** An account with a balance that has no safe swap route but may be
burnable junk.
- **Allowed action(s):** The user may burn the balance to zero; if the account
becomes empty after the burn, it may then be closed.
- **Rent / Essence rules:** Recovered rent (from closing the emptied account)
returns to the **user's wallet**. Burned junk does **not** count as Essence.
### `TRANSMUTABLE`
- **Enum identifier:** `TRANSMUTABLE`
- **Meaning:** An account holding a token that has a safe swap route and passes
the risk checks.
- **Allowed action(s):** The user may swap the token into SOL.
- **Rent / Essence rules:** The net swapped SOL may become Essence **only if the
user explicitly opts in**. Without opt-in, proceeds go to the user. Recovered
rent from any subsequently closed account returns to the user.
### `PROTECTED_SKIP`
- **Enum identifier:** `PROTECTED_SKIP`
- **Meaning:** An account PYRE recognizes but will **not touch by default**
because acting on it could destroy or mishandle value.
- **Allowed action(s):** None by default. Acting on these requires an explicit,
manual advanced override by the user.
- **Rent / Essence rules:** No rent recovery and no Essence by default.
- **Example asset types:**
- SOL / WSOL special cases
- USDC / USDT / other major assets
- Valuable meme tokens
- NFTs
- LP tokens
- Receipt tokens
- Staked tokens
- Suspicious tokens
- Frozen accounts
- Delegated accounts
- High-value balances
### `UNSUPPORTED`
- **Enum identifier:** `UNSUPPORTED`
- **Meaning:** An account the system **cannot safely reason about** in the MVP.
- **Allowed action(s):** None. These are skipped.
- **Rent / Essence rules:** No rent recovery and no Essence.
- **Example asset types:**
- Token-2022 accounts (in the MVP)
- Unknown token program
- Bad / missing metadata
- Unsupported account layout
- Accounts with extension behavior not yet handled
---
## Safety rules
Reproduced from design doc §7 as a checklist. The classifier and any action
builder must enforce **all** of these. MVP rules:
- [ ] Classic SPL only.
- [ ] Skip Token-2022 by default.
- [ ] Skip NFTs.
- [ ] Skip compressed NFTs.
- [ ] Skip LP tokens.
- [ ] Skip frozen accounts.
- [ ] Skip delegated accounts.
- [ ] Skip known valuable assets.
- [ ] Skip tokens above a user-safe USD threshold.
- [ ] Skip routes with high price impact.
- [ ] Skip routes with stale quotes.
- [ ] Skip unsafe or weird liquidity paths.
- [ ] Simulate all transactions before final signing.
---
## Wording rule
The system must **never** state that a token is safe.
- Forbidden: *"This token is safe."*
- Required: *"This token appears eligible based on current checks."*
This wording is load-bearing: it communicates that eligibility is a snapshot of
current automated checks, not a guarantee about the asset.
---
## Decision flow
How a single account flows from scan to a final category:
1. **Scan** — fetch the token account: owner, ATA address, mint, token program,
raw/UI balance, decimals, metadata if available, token program type, and
frozen/delegated state if available.
2. **Token program check** — if not classic SPL (e.g. Token-2022, unknown
program), classify as `UNSUPPORTED`. Stop.
3. **Account integrity check** — if metadata is bad/missing, the account layout
is unsupported, or extension behavior is unhandled, classify as
`UNSUPPORTED`. Stop.
4. **Protected check** — if the asset is an NFT, compressed NFT, LP token,
receipt token, staked token, major/known-valuable asset, SOL/WSOL special
case, suspicious token, frozen account, delegated account, or a high-value /
over-threshold balance, classify as `PROTECTED_SKIP`. Stop.
5. **Empty check** — if the balance is zero, classify as `EMPTY_CLOSE_ONLY`.
Stop.
6. **Route check** — evaluate a swap route. If a safe route exists and passes all
risk checks (no high price impact, no stale quote, no weird liquidity path,
simulation passes), classify as `TRANSMUTABLE`. Stop.
7. **Fallback** — otherwise the account has a balance with no safe route but may
be burnable junk: classify as `INCINERATE_ONLY`.
At any step where the system cannot safely decide, it falls back to a
non-destructive category — **Unknown means skip.**
```
scan account
├─ not classic SPL? ───────────────────────────────► UNSUPPORTED
├─ bad metadata / layout / unhandled extension? ───► UNSUPPORTED
├─ NFT / cNFT / LP / receipt / staked / major /
│ valuable / SOL-WSOL / suspicious / frozen /
│ delegated / over-threshold? ────────────────────► PROTECTED_SKIP
├─ balance == 0? ──────────────────────────────────► EMPTY_CLOSE_ONLY
├─ safe swap route + passes risk checks? ──────────► TRANSMUTABLE
└─ otherwise (burnable junk, no safe route) ───────► INCINERATE_ONLY
```
---
## Canonical enum
The canonical classification enum and the risk rules live in **`packages/core`**
(shared types and business logic). Apps and the worker import the enum from
there; they must not redefine it.
**The server must recompute classification.** Client-submitted classifications
are never trusted — the backend recomputes classification server-side before
building any transaction (design doc §16). A category arriving from the client is
treated as a hint at most, never as authority for a destructive action.