---
title: "Architecture"
canonical: "https://pharos.watch/docs/architecture/"
description: "How Pharos is built: static Next.js export, Cloudflare Pages Functions, Worker API lanes, D1 storage, route ownership, and SEO metadata rules."
---

# Architecture — Curated File Tree & Routing Model

## API Endpoints

Curated architecture-significant routes. Start with the Documentation Index for the full docs map, or go straight to the [API Reference](https://pharos.watch/docs/api-reference/) for the exhaustive HTTP contract.

## Route Definition Model

Static route metadata is declared once in the folderized `shared/lib/api-endpoints/` module surface (`@shared/lib/api-endpoints`). That shared descriptor list carries path, method, admin/cache/probe/status-action metadata, shared dynamic-admin path matching, plus the worker dependency-hydration hints needed for static routes. Worker route primitives now live in `worker/src/routes/shared.ts`, domain route arrays are split under `worker/src/routes/`, and `worker/src/routes/registry.ts` composes them into the dispatch map that `worker/src/router.ts` consumes for method validation and generic dispatch. Dependency hydration lives in `worker/src/routes/dependency-hydrators.ts` and stays exhaustive/keyed by `EndpointDependency`, so adding a new dependency without wiring hydration still fails at compile time instead of silently defaulting.

Cron trigger metadata follows the same single-source pattern. `shared/lib/cron-jobs.ts` remains the schedule authority, while `shared/lib/scheduled-runner-registry.ts` binds each cron expression to a symbolic scheduled-runner key that both the worker scheduler and `scripts/ci/check-cron-schedule-sync.ts` consume. That keeps `worker/wrangler.toml`, shared cron metadata, and scheduled-runner dispatch in lockstep.

The architecture doc no longer carries a hand-maintained endpoint inventory. Use the generated OpenAPI artifact (`public/openapi.json`), the generated quick-reference block in [API Reference](https://pharos.watch/docs/api-reference/), and the source route registries (`shared/lib/api-endpoints/` plus `worker/src/routes/`) for current route membership. The architecture contract is the routing model above, not the full route list.

## Telegram Subsystem Tables

| Table                             | Description                                                                 |
| --------------------------------- | --------------------------------------------------------------------------- |
| `telegram_subscribers`            | Bot subscriber preferences (`chat_id`, alert type flags)                    |
| `telegram_subscriptions`          | Per-user coin subscriptions (`chat_id`, `stablecoin_id`)                    |
| `telegram_pending_disambiguation` | Ephemeral mid-conversation state for ticker disambiguation                  |
| `telegram_pending_alerts`         | Overflow subscriber-alert delivery queue drained by the 5-minute alert cron |

The Telegram subscriber, disambiguation, and overflow-queue tables are part of the squashed worker baseline in `worker/migrations/0000_baseline.sql`; see `worker/migrations/MANIFEST.md` for the pre-squash lineage and current post-baseline files. For the full bot flow, see PharosWatchBot and Telegram Alerts.

## Telegram Alert Cron Job

| Job                        | Description                                                                                                                                      |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `dispatch-telegram-alerts` | Detects DEWS/depeg/safety/launch changes and fans out alerts to subscribers on the dedicated `2,7,12,17,22,27,32,37,42,47,52,57 * * * *` trigger |

## File Tree Guide

This section is intentionally high-level. For the exhaustive current source inventory, use Agent Code Map or run:

```bash
rg --files src shared worker scripts data functions
```

| Area                       | Primary paths                                                                                                                  | Notes                                                                                                                                                                                                                                                                                                                                                                                                                    |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Frontend routes            | `src/app/**/page.tsx`, route `client.tsx`, route `layout.tsx` / `error.tsx` files                                              | Static Next.js export surfaces. Route-specific contracts live in the route docs linked from Documentation Index.                                                                                                                                                                                                                                                                                          |
| Shared UI components       | `src/components/**`, excluding shadcn primitives in `src/components/ui/**`                                                     | Product components, charts, page sections, status surfaces, and stablecoin-detail modules. Preserve local design patterns before introducing new abstractions.                                                                                                                                                                                                                                                           |
| Frontend hooks and helpers | `src/hooks/**`, `src/lib/**`                                                                                                   | TanStack Query wrappers, stale/refetch policy, view-model builders, route metadata, API helpers, and pure UI derivations.                                                                                                                                                                                                                                                                                                |
| Shared runtime contracts   | `shared/lib/**`, `shared/types/**`, `shared/data/stablecoins/**`                                                               | Runtime-neutral scoring, classification, endpoint metadata, cron metadata, stablecoin data, schemas, and types imported by both frontend and worker. Stablecoin metadata is authored in `shared/data/stablecoins/coins/*.json`; `shared/data/stablecoins/coins.generated.json` is the generated runtime aggregate. Legacy category shards are read-only compatibility shells guarded by `npm run check:stablecoin-data`. |
| API endpoint registry      | `shared/lib/api-endpoints/**`, `worker/src/routes/**`, `worker/src/router.ts`                                                  | Shared endpoint definitions drive method/auth/cache metadata; worker route arrays bind those definitions to handlers.                                                                                                                                                                                                                                                                                                    |
| Worker API handlers        | `worker/src/api/**`                                                                                                            | Public, admin, messaging, and dynamic OG/API handlers. Exact HTTP contracts are canonical in [API Reference](https://pharos.watch/docs/api-reference/).                                                                                                                                                                                                                                                                                        |
| Worker scheduled runtime   | `shared/lib/cron-jobs.ts`, `shared/lib/scheduled-runner-registry.ts`, `worker/src/handlers/scheduled/**`, `worker/src/cron/**` | Cron schedules, slot dispatch, leases, progress, domain ingestion/scoring jobs, freshness watchdogs, and reserve adapters (57 adapters). Connection budgets for the 18 job-bearing schedule keys in `CRON_JOB_DEFINITIONS` are enforced by `npm run check:cron-connections`, alongside budget-only scheduled surfaces such as Telegram registration reconciliation and the manual digest-trigger poll slot.                                         |
| Worker support libraries   | `worker/src/lib/**`                                                                                                            | D1 helpers, auth, rate limits, circuit breakers, fetch/RPC helpers, stores, scoring support, request attribution, and runtime credentials.                                                                                                                                                                                                                                                                               |
| Pages Functions            | `functions/**`                                                                                                                 | Same-origin site-data and ops proxy surfaces for Cloudflare Pages. Host/origin behavior is documented in Worker Infrastructure and Operator Origin Access.                                                                                                                                                                                                  |
| Static/generated data      | `data/**`, `public/**`, `src/generated/**`                                                                                     | Build-time digest data, logos, redirects, public assets, generated docs metadata/sitemap dates, homepage bootstrap snapshots, `/llms.txt`, public cemetery dataset exports, OpenAPI/Postman artifacts, and markdown exports. See Scripts for the generator/check commands.                                                                                                                                                               |
| Operational scripts        | `scripts/**`, `worker/scripts/**`                                                                                              | CI guardrails, smoke tests, static export serving, data refresh helpers, and worker-bound maintenance tools. See Scripts.                                                                                                                                                                                                                                                                                |
| D1 migrations              | `worker/migrations/**`                                                                                                         | Backward-compatible migration tree plus baseline lineage in `worker/migrations/MANIFEST.md`. Standard deploy applies migrations before worker promotion.                                                                                                                                                                                                                                                                 |

Shared runtime host/origin defaults live in `shared/lib/runtime-origins.json` and `shared/lib/runtime-origins.ts`. Frontend API-base inference, `/_site-data/*` Pages Functions, ops-host Pages Functions, worker self/probe URLs, and local static-export tooling should consume that shared source instead of embedding production origins ad hoc.

The Stablecoin Cemetery public dataset export is static Pages data, not a Worker API route. `scripts/maintenance/generate-cemetery-dataset.ts` consumes the merged cemetery entry registry from `shared/lib/cemetery-merged.ts`, backed by curated dead rows in `shared/data/dead-stablecoins.json` and frozen tracked rows from `shared/data/stablecoins/coins.generated.json`. It writes `public/datasets/stablecoin-cemetery.json` plus `public/datasets/stablecoin-cemetery.csv` during `prebuild`, with per-source checksums recorded in the JSON metadata; `npm run check:cemetery-dataset` guards drift in CI.

The API integration artifacts follow the same static-export pattern. `scripts/maintenance/generate-postman-collection.ts` writes `public/postman/pharos-api.postman_collection.json` plus `public/postman/pharos-api.postman_environment.json`, and `scripts/maintenance/generate-openapi-spec.ts` writes `public/openapi.json` during `prebuild`; `npm run check:postman` and `npm run check:openapi` guard drift.

The homepage bootstrap payload follows the same generated-data pattern. `scripts/maintenance/generate-homepage-bootstrap.ts` writes `src/generated/homepage-bootstrap.json` during `prebuild` when an API base is configured, and `src/app/page.tsx` embeds it only on `/` as a non-executed JSON script before the client providers mount. `src/components/providers.tsx` validates the inline payload against the same endpoint schemas used by the hooks and seeds TanStack Query so the static homepage shell can start with market data while normal browser reads still go through `/_site-data/*` or the configured public API base. Runtime seeding drops entries older than the endpoint freshness budget. The generator also enforces a hard inline byte budget and omits oversized live responses instead of expanding `out/index.html`; a configured build still fails rather than preserving a stale populated payload when every bootstrap fetch fails.

Worker cron refactors should place reusable stage contracts under `worker/src/cron/shared/`. The seed contract layer in `worker/src/cron/shared/stage-contracts.ts` defines the shared vocabulary for stage progress, abort results, and handoff context so large cron decompositions do not each invent incompatible result shapes.

## Frontend Runtime And SEO Surface

- Indexable route membership is owned by `src/app/sitemap.ts`, route metadata modules, and the route-specific docs linked from Documentation Index. Do not mirror the full route inventory here; use the sitemap source and `npm run seo:check` for current crawlability coverage.
- Legacy aliases are maintained through `public/_redirects` and are not sitemap entries: `/telegram` and `/telegram/*` redirect to `/pharoswatchbot/`; `/mica` and `/mica/*` redirect to `/compliance/`; `/blacklist` and `/blacklist/*` redirect to `/freezewatch/`; `/report-cards` and `/risk-lab` redirect to `/safety-scores/`; `/peg-tracker` redirects to `/`; `/stability-index-alt` redirects to `/stability-index/`.
- Tool roots intentionally marked `noindex,follow`:
  - `/compare/`
  - `/portfolio/`
  - `/screener/picker/` (profile-driven shortlist; Pages-only; KV-backed snapshot pinning at same-origin `/selector-snapshot/`; see Screener Picker Page)
- Public noindex utility route:
  - `/pharoswatchbot/app/` is the Telegram Mini App control panel and is marked `noindex,nofollow`.
- Tracked-variant browse ownership stays on the homepage query state (`/?variant=...`). The repo does not ship a dedicated `/stablecoins/variants/*` family.
- Legacy numeric stablecoin URLs from the pre-canonical-ID era (`/stablecoin/<DefiLlama id>/`) redirect to the matching canonical `/stablecoin/[id]/` route through `functions/stablecoin/[[path]].ts`.
- Private operator routes marked `noindex,nofollow`:
  - `/admin/`
  - `/admin-api/`
  - `/api/admin/`
- Crawlable server-rendered link hubs now live on the digest archive, depeg event archive, safety scores, liquidity, taxonomy landing pages, and stablecoin detail pages. These hubs are part of the static export and are what `npm run seo:check` validates for orphan routes, sitemap coverage, and click depth.
- `/llms.txt` is generated during `prebuild` from checked-in route/data sources as a curated LLM-facing index. It is a community proposal/inference aid, not a robots or sitemap replacement.
- Markdown content negotiation for agents is handled by `functions/_middleware.ts` for `/methodology/`, `/stablecoin/<id>/`, `/changelog/`, `/digest/<date>/`, and `/docs/*`. The `.md` variants are generated by `scripts/maintenance/generate-markdown-exports.ts` during `postbuild` and are written as `out/<route>/index.md`. Responses include `Vary: Accept` plus CDN no-store headers because Cloudflare's default CDN cache does not key on arbitrary `Vary: Accept`.
- The same Pages middleware also nonce-authorizes inline scripts on HTML responses and overwrites the CSP to remove script `unsafe-inline`. `shared/lib/site-csp.ts` is the shared CSP builder for middleware, local static-export smoke, and managed `public/_headers` fallback lines; `npm run check:site-csp-sync` guards those static headers against drift. `public/_routes.json` uses a single broad `/*` include so exported document routes pass through this middleware, while static asset prefixes such as `/_next/*`, `/logos/*`, `/dexes/*`, and `/featured/*` stay excluded from function routing. Nonced HTML responses set `Cloudflare-CDN-Cache-Control: no-store` / `CDN-Cache-Control: no-store` so a random nonce is not shared from CDN cache. Cloudflare Pages static headers live in `public/_headers`; the broad fallback CSP also omits script `unsafe-inline`, the broad fallback `Cache-Control` includes `no-transform` so CDN features do not rewrite checked-in HTML examples into crawlable helper URLs, and static assets with their own cache policy detach the broad `Cache-Control` rule with `! Cache-Control` so Pages does not comma-join duplicate values.

### Runtime host and env rules

- `src/lib/api.ts` is the frontend runtime source of truth for API origin selection.
- `NEXT_PUBLIC_API_BASE` is an optional explicit override, mainly for local `next dev` against `wrangler dev`.
- When `NEXT_PUBLIC_API_BASE` is unset, `buildRequestUrl()` maps public browser reads on `pharos.watch`, `ops.pharos.watch`, `stablecoin-dashboard.pages.dev`, and `*.stablecoin-dashboard.pages.dev` to same-origin `/_site-data/*`, while `buildApiUrl()` still points explicit public-API callsites (for example feedback, API-key self-serve, and OG fetches) at `https://api.pharos.watch`.
- `functions/_site-data/[[path]].ts` is the browser-facing proxy contract for the website data lane. It accepts only `GET`, allowlists public-read routes through `shared/lib/site-data-lane.ts`, and requires `SITE_API_ORIGIN` on every Pages host (production and preview); when that binding is missing the proxy returns `500`. The lane also gates on the caller's `Origin` header (or `Referer` as a fallback) — only `pharos.watch`, `ops.pharos.watch`, `stablecoin-dashboard.pages.dev`, and subdomains of `stablecoin-dashboard.pages.dev` are accepted. All site-data upstream requests use `SITE_API_SHARED_SECRET` against `site-api.pharos.watch` or a Worker preview URL.
- `site-api.pharos.watch` is an internal Worker host, not a browser surface. `worker/src/handlers/http/gates.ts` allows only `GET` allowlisted site-data paths plus the shared-secret header on that lane (or on Worker preview URLs during CI rehearsal).
- `NEXT_PUBLIC_GA_ID` gates GA4 script injection in `src/app/layout.tsx`. When it is unset, the site still renders normally and no browser analytics events are emitted from `src/lib/analytics.ts`.

### Metadata and crawl ownership

- `src/lib/page-metadata.ts` is the shared helper for per-route canonical metadata, Open Graph images, Twitter cards, indexable robots preview directives, and sentence-aware description trimming.
- `src/app/layout.tsx` owns the sitewide metadata baseline, icons, `api.pharos.watch` preconnect, and root JSON-LD (`WebSite`, `Organization`, `Person`, `WebApplication`) with stable `#website`, `#organization`, `#person-tokenbrice`, and `#webapp` anchors. It intentionally does not emit `SearchAction` until the site has a real query handler.
- Dataset JSON-LD nodes must remain crawlable in isolation for Google Search Console: emit explicit Pharos `Organization` objects for `creator` and `publisher`, a URL-valued license, a Pharos URN `identifier`, and `sameAs` where the dataset has a canonical page or public export. Dataset `distribution.contentUrl` values must point only at public crawlable API/static-export URLs, never same-origin `/_site-data/*`.
- `src/app/sitemap.ts` owns sitemap output for indexable routes. `/compare/`, `/portfolio/`, `/admin/`, `/admin-api/`, and `/pharoswatchbot/app/` are omitted; `/compare/[slug]/` static comparison pages are included. `/funding/` uses the latest of route edit time and checked-in funding data timestamps for `lastModified`. `LAST_EDITED` dates are auto-generated from git history during prebuild (`scripts/maintenance/generate-sitemap-dates.ts`) and written to a generated JSON file (gitignored). Public docs use `scripts/maintenance/generate-docs-metadata.ts` for git-derived first/last modified dates.
- `src/app/robots.ts` publishes an allow-all crawl policy and the sitemap location. Operator surfaces (`/admin/`, `/admin-api/`, and `/api/admin/*`) rely on route metadata, Pages host gates, and `X-Robots-Tag: noindex, nofollow` headers for deindexing so crawlers can observe the noindex/404 or Access-gated response instead of reporting a robots.txt block.

### Standalone PharosVille

PharosVille now lives in the separate `TokenBrice/pharosville` repository and
is deployed through its own Cloudflare Pages project at
`https://pharosville.pharos.watch/`. The Pharos.watch host keeps only temporary
redirects from `/pharosville/` and `/lighthouse/` plus the shared API contract
schemas that the standalone app validates against.

The standalone app reads Pharos data through its own same-origin Pages Function
proxy. That proxy owns the PharosVille API key server-side and calls only the
allowlisted public read endpoints on `https://api.pharos.watch`, so the host
Worker does not need a CORS allowlist change for the split.

### Pages Function endpoints (not Worker API)

These are same-origin Pages Functions backed by Pages-only bindings (KV, D1). They do not appear in the Worker API catalogue and are not part of the `api.pharos.watch` surface.

| Endpoint | Description |
| --- | --- |
| `POST /selector-snapshot` | Pages Function (`functions/selector-snapshot/[[path]].ts`): stores a semantically validated `SelectorOutput` JSON under a server-recomputed content-addressed `sid` in the `SELECTOR_SNAPSHOTS` KV namespace. Origin-gated, 100 KB defensive size cap, debug-stripped, 5-year TTL. See Screener Picker Page. |
| `GET /selector-snapshot/:sid` | Pages Function: returns the previously stored frozen `SelectorOutput` or `404`. It recomputes the canonical sid before replay, returns `502` for corrupt/mismatched stored values, and uses `private, no-store` so public shared caches cannot bypass the origin gate. |

### Static feed route handlers

The App Router feed handlers under `src/app/feed/**/route.ts` emit static JSON and XML feeds from checked-in or generated build inputs. Treat that source tree, the sitemap, and `npm run seo:check` as the current feed-route inventory.

---

## CSS Build Pipeline

Styling runs through **PostCSS** with the `@tailwindcss/postcss` plugin (configured in `postcss.config.mjs`). This is the Tailwind CSS v4 integration path -- there is no standalone `tailwind.config` file; Tailwind v4 reads design tokens and `@theme` directives directly from `src/app/globals.css`. The `cn()` utility in `src/lib/utils.ts` uses `tailwind-merge` for safe class deduplication at runtime.

Reminder: Tailwind classes must be static strings -- never construct class names dynamically, as the CSS purge pass cannot detect them.

---

## Coverage Subsystem

The `/coverage` page model lives under `src/lib/coverage/` as one module per feature (`price`, `safety`, `dex`, `reserves`, `redemption`, `yield`, `flows`, `blacklist`, `dependency`, `mint-authority`), plus a `shared.ts` with primitives (`createStatus`, `createPresetStatus`, `createDataUnavailableStatus`, `resolveBooleanCoverageStatus`). Each per-feature module owns:

- The feature's preset table (when applicable).
- The `resolve<Feature>Coverage(...)` function that maps a `StablecoinMeta` (plus auxiliary inputs) to a `CoverageStatus`.
- A `format<Feature>Breakdown(rows, breakdownMap)` callback returning `CoverageBreakdownItem[]`.
- The `<FEATURE>_STATUS_KINDS` array enumerating every `kind` the resolver can produce, used by the legend invariant.
- A `<FEATURE>_LEGEND_ITEMS` list of `{ kind, label, description }` entries aggregated into the global legend.

`src/lib/coverage.ts` is a thin orchestrator + barrel — it re-exports every public symbol so consumers can keep importing from `@/lib/coverage`, and it owns the cross-feature helpers (`buildCoverageRow`, `buildCoverageFeatureSummary`, `countAvailableFeatures`, `countHeadlineFeatures`, `isHeadlineFeatureCovered`). `buildCoverageBreakdown` is now a dispatcher that calls `feature.formatBreakdown(rows, breakdownMap)` — adding a new feature requires providing the callback, otherwise TypeScript fails the build.

`src/lib/coverage-features.ts` wires each feature key to its per-feature module exports (resolver, formatter, status kinds, legend items). `src/lib/coverage-page-config.ts` derives `LEGEND_ITEMS` from those per-feature exports plus a small fixed set of general entries (NR / Data n/a / —). A runtime invariant test in `src/lib/__tests__/coverage.test.ts` asserts every `kind` any resolver can produce has a matching legend entry, and a second exhaustiveness test invokes each resolver against a synthetic fixture matrix to confirm `*_STATUS_KINDS` doesn't drift from actual resolver output.

---

## Worker Coding Conventions

### Loose-equality null guard (`!= null`)

The worker codebase deliberately uses `!= null` (loose equality) as the standard null/undefined guard for D1 query results. D1 can return either `null` or `undefined` for absent column values depending on the query path and column type, and `value != null` catches both in a single check. This is intentional -- do not "fix" these to `!== null` or `!== undefined`.

### Worker import boundary waiver

`npm run check:worker-boundary` enforces the worker/frontend/shared import boundary. The only named non-test waiver is `frozen-invariants-lifecycle-registry-check` for `scripts/ci/check-frozen-invariants.ts`, which imports worker and frontend registries to prove frozen stablecoin IDs were removed from lifecycle surfaces. Keep that waiver documented in the script header and guarded by `scripts/__tests__/worker-boundary-waivers.test.ts`; new cross-layer checks should move runtime-neutral metadata into `shared/` instead of expanding the waiver set.

---

## TypeScript Target Constraints

Both the root tsconfig and worker tsconfig target ES2022. Shared modules in `shared/lib/` compile under both configs and may use ES2022 features (nullish assignment `??=`, logical assignment `||=`, `Array.at()`, top-level `await`, etc.) but must remain runtime-neutral — no DOM APIs, no Node-only APIs, no Cloudflare-only APIs.

---

## Stablecoin lifecycle phases

Every entry in `TRACKED_STABLECOINS` is in one of three lifecycle phases. The phase controls which write-side crons ingest the coin, which read-side endpoints serve it, and which UI surfaces list it. Phase transitions are a data-collection policy, not a scoring algorithm change — per-domain methodology version constants under `shared/lib/*-version*.ts` are unaffected.

| Phase      | `status` field                               | New data collected?   | Score recomputation? | Listed on                                                                                                      |
| ---------- | -------------------------------------------- | --------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------- |
| Active     | `"active"` (or omitted)                      | Yes                   | Yes                  | Homepage table, active taxonomy pages, portfolio picker, live aggregates, and `/stablecoin/<id>/` detail pages |
| Pre-launch | `"pre-launch"`                               | No (no live data yet) | No                   | `/upcoming/` cards and the pre-launch `/stablecoin/<id>/` detail variant                                       |
| Frozen     | `"frozen"` (requires `frozenAt`, `obituary`) | No (archive)          | No                   | `/cemetery/` (with archived-data link) and the preserved detail page at `/stablecoin/<id>/`                    |

The registry exposes five universes from `shared/lib/stablecoins/registry.ts`:

- `TRACKED_STABLECOINS` — every tracked coin (active + pre-launch + frozen). Used for canonical-ID lookups, registry validation, shared metadata reads, static stablecoin detail params, and stablecoin detail sitemap entries.
- `ACTIVE_STABLECOINS` — excludes pre-launch and frozen. Used by every write-side cron, live aggregator, PSI/DEWS/Bank-Run-Gauge inputs, and Telegram alert eligibility.
- `PRE_LAUNCH_STABLECOINS` — `status === "pre-launch"` only. Drives `/upcoming/` cards and the pre-launch detail-page variant.
- `READABLE_STABLECOINS` — active + frozen. Used by the compare picker and readback/archive surfaces that should preserve frozen assets while excluding pre-launch assets.
- `FROZEN_STABLECOINS` — `status === "frozen"` only. Drives the cemetery merge, the static cemetery dataset export, and the frozen detail-page banner/footer.

The freeze procedure is documented in `docs/freezing-stablecoins.md`.

## Funding page

The `/funding` route is a static page backed by two hand-maintained JSON files in `shared/data/funding/` (costs and donations). No cron, no D1, no API endpoint. Donations are appended to `donations.json` via the `funding-update` Claude skill on a weekly cadence — the skill researches inbound transfers to `pharos-watch.eth` across six chains (Ethereum/Base/Optimism/Arbitrum/Polygon via Alchemy `alchemy_getAssetTransfers`, Gnosis via Etherscan V2 with `chainid=100`), prices each donation in USD at receipt via CoinGecko `/coins/{id}/history`, forward-verifies ENS, and writes after explicit user approval. See `docs/funding-page.md` for the data model and the rationale for the intentionally-simple architecture.
