From 81168235a412b6a0ac8df30e9ff25551ac4a1434 Mon Sep 17 00:00:00 2001 From: only1dreamgene Date: Mon, 19 Oct 2026 15:48:40 -0700 Subject: [PATCH] feat(vaults): declare epoch ledger read contract and typed adapter Add the frozen GET /api/v1/vaults/{id}/epochs route, a versioned VaultEpochLedgerEntry schema (start/end, strategy, premium, realized P&L, NAV before/after, fee taken), and a fetchVaultEpochHistory adapter following the existing mock/live pattern. Ledger ingestion, drift detection and epoch/NAV observability are implemented in the backend repo; this is the frontend contract only. Co-Authored-By: Claude Haiku 5.5 --- src/lib/api/vaults.ts | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/lib/api/vaults.ts b/src/lib/api/vaults.ts index d22c3211..b386c6c4 100644 --- a/src/lib/api/vaults.ts +++ b/src/lib/api/vaults.ts @@ -16,6 +16,9 @@ * * POST /api/v1/vaults/{id}/deposit { amount: number } → { tx_id: string } * POST /api/v1/vaults/{id}/withdraw { shares: number } → { tx_id: string; queued: boolean } + * + * GET /api/v1/vaults/{id}/epochs + * → VaultEpochLedgerEntry[] (append-only, derived from on-chain epoch events) */ export type VaultStrategy = "covered_call" | "cash_secured_put" | "iron_condor" | "wheel"; @@ -55,6 +58,27 @@ export interface EpochRecord { pnlPct: number; // epoch return, positive = profit for depositors } +/** Schema version of VaultEpochLedgerEntry. Bump on any breaking field change. */ +export const VAULT_EPOCH_LEDGER_SCHEMA_VERSION = 1; + +/** + * One append-only epoch ledger row, derived from on-chain epoch events. + * Rows are never edited in place; a correction is a new reconciliation row. + */ +export interface VaultEpochLedgerEntry { + schemaVersion: typeof VAULT_EPOCH_LEDGER_SCHEMA_VERSION; + vaultId: string; + epochNumber: number; + strategy: VaultStrategy; + startedAt: string; // ISO — on-chain epoch start timestamp + endedAt: string | null; // ISO — null while the epoch is still open + premium: number; // USD, total premium collected in the epoch + realizedPnl: number; // USD + navBefore: number; // USD per share at epoch start + navAfter: number | null; // USD per share at epoch end; null while open + feeTaken: number; // USD, management + performance fees taken in the epoch +} + /** The user's position in a vault. */ export interface UserVaultPosition { shares: number; @@ -212,6 +236,18 @@ export async function fetchVaultDetail(id: string, _token?: string | null): Prom return Promise.resolve(MOCK_VAULTS.find(v => v.id === id) ?? null); } +export async function fetchVaultEpochHistory( + id: string, + _token?: string | null +): Promise { + if (process.env.NEXT_PUBLIC_VAULTS === "live") { + // TODO: return apiGet(`/api/v1/vaults/${id}/epochs`, token); + throw new Error("Live vaults API not yet implemented"); + } + // Mock: no ledger rows; backfill of pre-existing mock epochs is out of scope. + return Promise.resolve([]); +} + export async function depositToVault( id: string, amount: number,