/** Shapes returned by the API. The web app builds against these. */ /** * Statuses a listed thing can be in. * * `announced` is the Fedimint addition, and it is deliberately not a synonym for * `unknown`. `unknown` means "we have not checked yet"; `announced` means "there is no * check we can run" — the federation exists on Nostr, nothing has confirmed it since, * and nothing here will claim otherwise. A Cashu mint never carries it. */ export type MintStatus = 'online' | 'degraded' | 'offline' | 'unknown' | 'announced'; /** * Which ecosystem a listing belongs to. * * Deliberately open rather than a closed union: `mints.type` is a stored TEXT column * with no CHECK constraint, so an older build reading a database written by a newer one * has to be able to hold a value it does not have a page for. Compare against * `ANNOUNCEMENT_KINDS` rather than switching exhaustively on this. */ export type MintType = 'cashu' | 'fedimint' | (string & {}); /** One item of `GET /api/mints`. */ export interface MintListItem { url: string; host: string; name: string | null; icon: string | null; /** 'cashu' or 'fedimint'. Rows written before the column existed read as 'cashu'. */ type: MintType; status: MintStatus; last_online: number | null; review_count: number; rating_avg: number | null; score: number; last_review_at: number | null; version: string | null; } export interface ProbeSample { ts: number; ok: 0 | 1; latency_ms: number | null; } export type RatingDistribution = Record<'1' | '2' | '3' | '4' | '5', number>; /** * `GET /api/mints/:host`. * * The Fedimint keys are optional and absent on a Cashu mint, which is what keeps the * Cashu payload unchanged. Read them through `FedimintDetail` after checking `type`. */ export interface MintDetail extends MintListItem, Partial { description: string | null; pubkey: string | null; info: MintInfo | null; nuts: string[]; first_seen: number; last_probe: number | null; updated_at: number; rating_distribution: RatingDistribution; /** Reviews in the last 90 days, for the verdict strip's activity cell. */ reviews_90d: number; uptime_30d: number | null; probes_recent: ProbeSample[]; } /** * What `ecosystem_json` holds for a Fedimint row, and what `GET /api/mints/:host` * spreads across the detail payload for one. * * Flattened rather than nested so a Cashu payload is byte for byte what it always was: * these keys are simply absent on one. A third ecosystem adds its own interface here * and its own optional keys below; nothing existing has to move. */ export interface FedimintFields { /** The `d` tag of the announcement: 64 hex characters. `host` is derived from it. */ federation_id: string; /** Every `u` tag that was a usable invite code, in announcement order. */ invite_codes: string[]; /** The `modules` tag, split: `["ln", "mint", "wallet", "lnv2", "meta"]`. */ modules: string[]; /** The `n` tag, normalized — `bitcoin` and `mainnet` both arrive as `mainnet`. */ network: string | null; /** `created_at` of the newest announcement seen for this federation. */ announced_at: number | null; /** Who published that announcement. The `a` tag of a review points back at them. */ announcer_pubkey: string | null; /** * Who confirmed the status, when anything did: `"fedimint.observer"` today. * * null means nothing has, and then `status` is `announced` and never `online`. The * page prints this next to the status, because "someone else says it is up" is a * different claim from "we checked". */ status_source: string | null; } /** `GET /api/mints/:host` for a Fedimint federation: the detail plus its own fields. */ export type FedimintDetail = MintDetail & FedimintFields; /** * `GET /api/stats`. * * The four `mints_*` fields count Cashu mints and only Cashu mints, exactly as they did * before federations were indexed: they are read by the pulse ticker, the /mints * description and the home page, and a number that silently changed meaning would be * worse than a new field. `cashu_total` is the same number under a name that says so, * and every ecosystem added later gets its own `*_total` beside `fedimint_total`. */ export interface Stats { mints_total: number; mints_online: number; mints_offline: number; mints_degraded: number; reviews_total: number; last_review_at: number | null; updated_at: number; /** Same value as `mints_total`, named for the ecosystem it counts. */ cashu_total: number; fedimint_total: number; /** Federations a real check confirmed were up. Never inferred from an announcement. */ fedimint_online: number; fedimint_offline: number; /** Federations announced on Nostr that no check has ever confirmed. */ fedimint_announced: number; /** Reviews of federations (`k` = 38173), included in `reviews_total`. */ fedimint_reviews: number; } /** `GET /api/health`. */ export interface Health { status: 'ok' | 'degraded'; uptime_s: number; last_probe_at: number | null; last_discovery_at: number | null; mints_tracked: number; updated_at: number; } /** * A mint's `/v1/info` response (NUT-06). Every field is optional: mints in the wild * omit most of it, and the site must render whatever is there. */ export interface MintInfo { name?: string; pubkey?: string; version?: string; description?: string; description_long?: string; contact?: MintContact[]; motd?: string; icon_url?: string; urls?: string[]; time?: number; tos_url?: string; nuts?: Record; } export interface MintContact { method?: string; info?: string; } /** A review as rendered in the browser, parsed from a Nostr event. */ export interface Review { id: string; pubkey: string; created_at: number; mint_url: string; /** null when the event carries no parseable rating. Never defaulted to 5. */ rating: number | null; content: string; } /** Resolved kind-0 metadata for a reviewer. */ export interface Profile { pubkey: string; name?: string; display_name?: string; picture?: string; nip05?: string; /** false when the kind-0 lookup returned nothing, used for the provenance note. */ found: boolean; }