# @pyre/db Postgres-backed **Essence ledger** for PYRE. A small typed data layer over `pg` (no ORM): a lazily-created connection pool, an idempotent migration runner, and the round / receipt / contribution query surface. ## Trust rules - **Connection details come from the environment** (`DATABASE_URL`) or an explicit argument — credentials are **never** hardcoded. The localhost dev URL is only a last-resort fallback. - **Recovered ATA rent is not Essence.** `cleanup_receipts` records rent returned to the user; it never touches a round total. Only `essence_contributions` (the protocol fee and explicit opt-in contributions) feed `rounds.essence_lamports`. - **Parameterized queries only** (`$1`, `$2`, …) — no string interpolation. - Lamport amounts cross the API boundary as **decimal strings** (u64-safe) and are cast to `::bigint` in SQL. - **No network/DB access at import time.** The pool is created lazily; `migrate()` is safe to call repeatedly. ## Tables Defined in `migrations/001_init.sql` (idempotent, `CREATE TABLE IF NOT EXISTS`). ### `rounds` | column | type | notes | | ------------------ | ------------- | --------------------------------------- | | `id` | `BIGSERIAL` | primary key | | `status` | `TEXT` | `'open' \| 'closed'`, default `'open'` | | `essence_lamports` | `BIGINT` | running round total, default `0` | | `started_at` | `TIMESTAMPTZ` | default `now()` | | `closed_at` | `TIMESTAMPTZ` | nullable | ### `cleanup_receipts` | column | type | notes | | ------------------------ | ------------- | -------------------------------------- | | `id` | `BIGSERIAL` | primary key | | `wallet` | `TEXT` | | | `tx_signature` | `TEXT` | **unique** (idempotency key) | | `kind` | `TEXT` | `'close' \| 'burn'` | | `rent_returned_lamports` | `BIGINT` | rent returned to the user | | `fee_lamports` | `BIGINT` | protocol fee, default `0` | | `closed_accounts` | `JSONB` | array of addresses, default `'[]'` | | `created_at` | `TIMESTAMPTZ` | default `now()` | Index: `cleanup_receipts(wallet)`. ### `essence_contributions` | column | type | notes | | -------------- | ------------- | -------------------------------------- | | `id` | `BIGSERIAL` | primary key | | `round_id` | `BIGINT` | FK → `rounds(id)` | | `wallet` | `TEXT` | | | `tx_signature` | `TEXT` | **unique** (idempotency key) | | `lamports` | `BIGINT` | amount fed to the PYRE | | `kind` | `TEXT` | `'fee' \| 'contribution'` | | `created_at` | `TIMESTAMPTZ` | default `now()` | Index: `essence_contributions(round_id)`. ## API ```ts import { getPool, migrate, ensureOpenRound, recordReceipt, recordEssence, getEssenceSummary, closePool, } from "@pyre/db"; ``` - `getPool(databaseUrl?): Pool` — lazily create and cache the singleton `pg.Pool`. Connection string resolves to the explicit argument, then `DATABASE_URL`, then the localhost dev default. No connection is opened until first query. - `migrate(): Promise` — apply every `migrations/*.sql` in name order, each in its own transaction. Idempotent; safe to call repeatedly. - `ensureOpenRound(): Promise<{ id: string }>` — return the current open round, creating one if none exists. - `recordReceipt(r): Promise` — insert a cleanup receipt (`{ wallet, txSignature, kind: 'close'|'burn', rentReturnedLamports, feeLamports, closedAccounts }`). Idempotent on `txSignature`. Does **not** affect any round total. - `recordEssence(e): Promise<{ recorded, roundId }>` — record a contribution (`{ wallet, txSignature, lamports, kind: 'fee'|'contribution' }`) against the open round. In one transaction: ensures a round, inserts (idempotent on `txSignature`), and increments `rounds.essence_lamports` **only** when a new row is inserted. Returns `recorded: false` for duplicate signatures. - `getEssenceSummary(): Promise` — open-round `{ roundId, totalLamports, contributionCount, recent }`, where `recent` is the last ~10 contributions (newest first). - `closePool(): Promise` — close and clear the pool for shutdown / test teardown. All lamport amounts are **strings** in and out. ## Usage ```ts await migrate(); await recordEssence({ wallet: "Wallet…", txSignature: "Sig…", lamports: "1000000", kind: "fee", }); const summary = await getEssenceSummary(); ``` ## Migrations SQL lives in `migrations/`, one forward migration per file in lexical order (`001_init.sql`, `002_…sql`, …). Each file must be idempotent. The runner (`migrate()`) applies them in name order against the resolved connection.