Files
pyre/packages/db/README.md
RogueWave b98b904896 feat(fee+burn+essence): 5% transparent fee, burn→close, Essence ledger + dashboard
Monetization (design Rev 4, §3.1) — transparent in-tx fee, non-custodial:
- @pyre/core: computeFeeBreakdown (single source of truth, BigInt) + FeeBreakdown
  threaded through close/burn previews; fee tests.
- @pyre/config: PYRE_TREASURY_WALLET / PYRE_FEE_BPS (500) / swap fee / max contribution.
- @pyre/solana: close-empty + burn→close now append ONE System transfer of exactly
  the disclosed fee to the treasury; rent/authority/feePayer pinned to wallet.
  buildBurnTx re-validates EVERY account on-chain and value-gates via the classifier
  (classic SPL + Token-2022) — never burns protected/valuable/NFT/unsupported;
  ignores client amount (burns real balance); whole-build rejection.
- @pyre/api: close-empty/burn endpoints carry the fee + bounded optional contribution;
  /api/receipt persists (cleanup_receipts) and records the on-chain treasury fee as
  Essence; GET /api/essence; startup migrate(). Best-effort DB (never fails receipts).
- @pyre/db: Postgres Essence ledger (rounds, cleanup_receipts, essence_contributions),
  idempotent migrations, parameterized + u64-safe.
- @pyre/web: fee preview ("reclaim · feeds the PYRE · you net" + treasury) + optional
  "feed more" slider; burn flow w/ destructive confirm; decode+match verifies the fee
  transfer (treasury + exact lamports) before signing; public "🔥 fed the PYRE" panel.

Built by agents (2 waves) + 2 audits. Security audit found a HIGH — buildBurnTx
didn't value-gate CLASSIC spl tokens (a direct API caller could burn USDC/an NFT);
FIXED (classify classic accounts too) + 2 regression tests. Integration: SHIP.
typecheck 8/8, core 91, solana 30, web build green. Live: burn preview on the dust
token shows 5% → treasury; non-empty/non-owned/valuable rejected. Nightly DB backup
cron enabled.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 06:11:00 +00:00

5.3 KiB

@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

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<void> — 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<void> — 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<EssenceSummary> — open-round { roundId, totalLamports, contributionCount, recent }, where recent is the last ~10 contributions (newest first).
  • closePool(): Promise<void> — close and clear the pool for shutdown / test teardown.

All lamport amounts are strings in and out.

Usage

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.