From 75f2981203c85c499befe18b3e5a19b1ed371efa Mon Sep 17 00:00:00 2001 From: only1dreamgene Date: Mon, 19 Oct 2026 15:37:42 -0700 Subject: [PATCH] Wire /vaults to live vault REST reads behind the vaults flag Remove MOCK_VAULTS and the mock-data disclaimer. The adapter now calls GET /api/v1/vaults and GET /api/v1/vaults/{id} (404 -> null). Both pages gate on useFlag("vaults") and show explicit unavailable, loading, error, and empty states. Fields the backend does not serve render as an em dash instead of zero. Deposit and withdraw return a not-yet-available error rather than fabricated transaction IDs. Co-Authored-By: Claude Haiku 5.5 --- src/app/[locale]/vaults/[id]/page.tsx | 85 +++++++---- src/app/[locale]/vaults/page.tsx | 62 ++++---- src/lib/api/vaults.ts | 199 +++++--------------------- 3 files changed, 134 insertions(+), 212 deletions(-) diff --git a/src/app/[locale]/vaults/[id]/page.tsx b/src/app/[locale]/vaults/[id]/page.tsx index be22a66f..23e66005 100644 --- a/src/app/[locale]/vaults/[id]/page.tsx +++ b/src/app/[locale]/vaults/[id]/page.tsx @@ -6,6 +6,7 @@ import { use } from "react"; import { AppHeader } from "../../../../components/AppHeader"; import { WalletConnect } from "../../../../components/WalletConnect"; import { useWalletStore } from "../../../../lib/store/wallet"; +import { useFlag } from "../../../../lib/flags"; import { fetchVaultDetail, depositToVault, @@ -14,6 +15,8 @@ import { type EpochPhase, } from "../../../../lib/api/vaults"; +const ABSENT = "—"; + const PHASE_LABELS: Record = { deposit: "Deposit Window", active: "Epoch Active", @@ -43,6 +46,7 @@ function fmtDateShort(iso: string) { export default function VaultDetailPage({ params }: { params: Promise<{ id: string }> }) { const { id } = use(params); const token = useWalletStore(s => s.session); + const vaultsEnabled = useFlag("vaults"); const [vault, setVault] = useState(null); const [loading, setLoading] = useState(true); @@ -55,10 +59,15 @@ export default function VaultDetailPage({ params }: { params: Promise<{ id: stri const [activeFlow, setActiveFlow] = useState<"deposit" | "withdraw" | null>(null); useEffect(() => { + if (!vaultsEnabled) return; + setLoading(true); + setError(null); fetchVaultDetail(id, token) .then(v => { setVault(v); setLoading(false); }) .catch(e => { setError(e.message); setLoading(false); }); - }, [id, token]); + }, [id, token, vaultsEnabled]); + + const nav = vault?.sharePrice && vault.sharePrice > 0 ? vault.sharePrice : null; const handleDeposit = async () => { if (!vault || !token) return; @@ -116,20 +125,26 @@ export default function VaultDetailPage({ params }: { params: Promise<{ id: stri {vault?.name ?? id} - {loading && ( + {!vaultsEnabled && ( +
+ Vaults are not available on this deployment. +
+ )} + + {vaultsEnabled && loading && (
)} - {error && ( + {vaultsEnabled && error && (
- {error} + Failed to load vault: {error}
)} - {!loading && !error && !vault && ( + {vaultsEnabled && !loading && !error && !vault && (
Vault not found.
)} @@ -143,11 +158,11 @@ export default function VaultDetailPage({ params }: { params: Promise<{ id: stri

{vault.name}

-
{vault.description}
+
{vault.description}
- {(vault.apy * 100).toFixed(1)}% + {vault.apy != null ? `${(vault.apy * 100).toFixed(1)}%` : ABSENT}
trailing APY
@@ -156,9 +171,9 @@ export default function VaultDetailPage({ params }: { params: Promise<{ id: stri {/* Stats row */}
{[ - { label: "TVL", value: fmtUsd(vault.tvl) }, - { label: "Share Price", value: `$${vault.sharePrice.toFixed(4)}` }, - { label: "Epoch", value: `#${vault.currentEpoch}` }, + { label: "TVL", value: vault.tvl != null ? fmtUsd(vault.tvl) : ABSENT }, + { label: "Share Price", value: nav != null ? `$${nav.toFixed(4)}` : ABSENT }, + { label: "Epoch", value: vault.currentEpoch != null ? `#${vault.currentEpoch}` : ABSENT }, { label: "Mgmt Fee", value: vault.feeStructure.managementPct > 0 ? `${vault.feeStructure.managementPct}%` : "None" }, { label: "Perf Fee", value: `${vault.feeStructure.performancePct}%` }, ].map((s, i) => ( @@ -176,10 +191,13 @@ export default function VaultDetailPage({ params }: { params: Promise<{ id: stri {/* Epoch timeline */}
Epoch Timeline
+ {!vault.currentPhase && ( +
No epoch has started yet.
+ )}
{(["deposit", "active", "settlement"] as EpochPhase[]).map((phase, i) => { const isCurrent = phase === vault.currentPhase; - const isPast = (["deposit", "active", "settlement"] as EpochPhase[]).indexOf(vault.currentPhase) > i; + const isPast = vault.currentPhase != null && (["deposit", "active", "settlement"] as EpochPhase[]).indexOf(vault.currentPhase) > i; return (
{PHASE_LABELS[phase]}
- {isCurrent && ( + {isCurrent && vault.phaseEndsAt && (
Ends {fmtDate(vault.phaseEndsAt)}
@@ -203,19 +221,21 @@ export default function VaultDetailPage({ params }: { params: Promise<{ id: stri ); })}
-
- - {vault.currentPhase === "deposit" && "Deposits open · Withdrawals processed at epoch start"} - {vault.currentPhase === "active" && "Epoch in progress · Withdrawals queued until settlement"} - {vault.currentPhase === "settlement" && "Settling position · Final P&L being calculated"} - -
+ {vault.currentPhase && ( +
+ + {vault.currentPhase === "deposit" && "Deposits open · Withdrawals processed at epoch start"} + {vault.currentPhase === "active" && "Epoch in progress · Withdrawals queued until settlement"} + {vault.currentPhase === "settlement" && "Settling position · Final P&L being calculated"} + +
+ )}
{/* Payoff explanation */}
How This Vault Works
-
{vault.payoffExplanation}
+
{vault.payoffExplanation}
{/* Historical epochs */} @@ -235,6 +255,13 @@ export default function VaultDetailPage({ params }: { params: Promise<{ id: stri + {vault.historicalEpochs.length === 0 && ( + + + No completed epochs yet. + + + )} {vault.historicalEpochs.map(ep => ( #{ep.epochNumber} @@ -333,8 +360,14 @@ export default function VaultDetailPage({ params }: { params: Promise<{ id: stri {activeFlow === "deposit" && (
- Deposit USDC into the vault. Shares are issued at the current NAV of{" "} - ${vault.sharePrice.toFixed(4)}/share. + {nav != null ? ( + <> + Deposit USDC into the vault. Shares are issued at the current NAV of{" "} + ${nav.toFixed(4)}/share. + + ) : ( + "Deposit USDC into the vault. NAV per share is not available yet." + )}
@@ -350,9 +383,9 @@ export default function VaultDetailPage({ params }: { params: Promise<{ id: stri color: "var(--text-hi)", fontFamily: "var(--font-mono)", fontSize: 13, }} /> - {depositAmount && !isNaN(parseFloat(depositAmount)) && parseFloat(depositAmount) > 0 && ( + {nav != null && depositAmount && !isNaN(parseFloat(depositAmount)) && parseFloat(depositAmount) > 0 && (
- ≈ {(parseFloat(depositAmount) / vault.sharePrice).toFixed(4)} shares + ≈ {(parseFloat(depositAmount) / nav).toFixed(4)} shares
)}
@@ -397,9 +430,9 @@ export default function VaultDetailPage({ params }: { params: Promise<{ id: stri color: "var(--text-hi)", fontFamily: "var(--font-mono)", fontSize: 13, }} /> - {withdrawShares && !isNaN(parseFloat(withdrawShares)) && parseFloat(withdrawShares) > 0 && ( + {nav != null && withdrawShares && !isNaN(parseFloat(withdrawShares)) && parseFloat(withdrawShares) > 0 && (
- ≈ ${(parseFloat(withdrawShares) * vault.sharePrice).toFixed(2)} USDC at current NAV + ≈ ${(parseFloat(withdrawShares) * nav).toFixed(2)} USDC at current NAV
)}
diff --git a/src/app/[locale]/vaults/page.tsx b/src/app/[locale]/vaults/page.tsx index efe5f483..dbbec8ea 100644 --- a/src/app/[locale]/vaults/page.tsx +++ b/src/app/[locale]/vaults/page.tsx @@ -5,6 +5,9 @@ import Link from "next/link"; import { AppHeader } from "../../../components/AppHeader"; import { WalletConnect } from "../../../components/WalletConnect"; import { fetchVaults, type VaultSummary, type VaultStrategy, type RiskRating } from "../../../lib/api/vaults"; +import { useFlag } from "../../../lib/flags"; + +const ABSENT = "—"; const STRATEGY_LABELS: Record = { covered_call: "Covered Call", @@ -29,10 +32,6 @@ function capacityPct(tvl: number, capacity: number) { return Math.min(100, (tvl / capacity) * 100); } -function fmt(n: number, d = 2) { - return n.toLocaleString("en-US", { minimumFractionDigits: d, maximumFractionDigits: d }); -} - function fmtUsd(n: number) { if (n >= 1_000_000) return `$${(n / 1_000_000).toFixed(2)}M`; if (n >= 1_000) return `$${(n / 1_000).toFixed(1)}K`; @@ -40,16 +39,20 @@ function fmtUsd(n: number) { } export default function VaultsPage() { + const vaultsEnabled = useFlag("vaults"); const [vaults, setVaults] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [apyTooltip, setApyTooltip] = useState(null); useEffect(() => { + if (!vaultsEnabled) return; + setLoading(true); + setError(null); fetchVaults() .then(v => { setVaults(v); setLoading(false); }) .catch(e => { setError(e.message); setLoading(false); }); - }, []); + }, [vaultsEnabled]); return (
@@ -71,15 +74,16 @@ export default function VaultsPage() {

Earn passive yield by providing liquidity to automated option-writing strategies. Premiums are collected each epoch and compounded into your share price. - {process.env.NEXT_PUBLIC_VAULTS !== "live" && ( - - (mock data — contracts not yet deployed) - - )}

- {loading && ( + {!vaultsEnabled && ( +
+ Vaults are not available on this deployment. +
+ )} + + {vaultsEnabled && loading && (
{[1, 2, 3].map(i => (
@@ -87,12 +91,16 @@ export default function VaultsPage() {
)} - {error && ( + {vaultsEnabled && error && (
Failed to load vaults: {error}
)} + {vaultsEnabled && !loading && !error && vaults.length === 0 && ( +
No vaults are live yet.
+ )} + {/* APY tooltip overlay */} {apyTooltip && (
0 && (
{vaults.map(vault => { - const cap = capacityPct(vault.tvl, vault.capacity); - const capColor = cap >= 90 ? "var(--put)" : cap >= 70 ? "var(--atm)" : "var(--call)"; + const cap = vault.tvl != null && vault.capacity ? capacityPct(vault.tvl, vault.capacity) : null; + const capColor = cap === null ? "var(--text-lo)" : cap >= 90 ? "var(--put)" : cap >= 70 ? "var(--atm)" : "var(--call)"; return (
{STRATEGY_LABELS[vault.strategy]}
-
{vault.description}
+
{vault.description}
{/* Risk rating */}
ⓘ
- {(vault.apy * 100).toFixed(1)}% + {vault.apy != null ? `${(vault.apy * 100).toFixed(1)}%` : ABSENT}
{/* TVL */}
TVL
-
{fmtUsd(vault.tvl)}
+
{vault.tvl != null ? fmtUsd(vault.tvl) : ABSENT}
{/* Capacity */}
@@ -186,26 +194,30 @@ export default function VaultsPage() { Capacity
- {fmtUsd(vault.tvl)} / {fmtUsd(vault.capacity)} + {vault.tvl != null && vault.capacity != null ? `${fmtUsd(vault.tvl)} / ${fmtUsd(vault.capacity)}` : ABSENT}
-
+
{/* Current epoch strike */}
Current Strike
- {vault.currentStrike >= 1000 - ? `$${vault.currentStrike.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` - : `$${vault.currentStrike.toFixed(4)}`} + {vault.currentStrike == null + ? ABSENT + : vault.currentStrike >= 1000 + ? `$${vault.currentStrike.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` + : `$${vault.currentStrike.toFixed(4)}`}
{/* Expiry */}
Expiry
- {new Date(vault.currentExpiry).toLocaleDateString("en-US", { month: "short", day: "numeric" })} + {vault.currentExpiry + ? new Date(vault.currentExpiry).toLocaleDateString("en-US", { month: "short", day: "numeric" }) + : ABSENT}
{/* Underlying */} @@ -224,7 +236,7 @@ export default function VaultsPage() { }}> View Vault → - {cap >= 100 && ( + {cap !== null && cap >= 100 && (
Vault full
)}
diff --git a/src/lib/api/vaults.ts b/src/lib/api/vaults.ts index d22c3211..f7116f57 100644 --- a/src/lib/api/vaults.ts +++ b/src/lib/api/vaults.ts @@ -1,43 +1,48 @@ /** - * Typed data adapter interface for the Vaults feature. + * Typed data adapter for the Vaults feature. * - * The vault smart contracts and backend API are not yet available. - * When NEXT_PUBLIC_VAULTS=mock (or the env var is absent), the mock - * implementation below is used. Swap in the real implementation by - * setting NEXT_PUBLIC_VAULTS=live and implementing `fetchVaultsLive`. - * - * Backend contract (for when the real API lands): + * Reads go through the BFF against the frozen vault REST contract: * * GET /api/v1/vaults * → VaultSummary[] * * GET /api/v1/vaults/{id} - * → VaultDetail + * → VaultDetail (404 → null) + * + * Gating lives in the page layer (`useFlag("vaults")`); this module never + * returns placeholder data. + * + * Deposit/withdraw are not implemented yet (out of scope for the read path): * * POST /api/v1/vaults/{id}/deposit { amount: number } → { tx_id: string } * POST /api/v1/vaults/{id}/withdraw { shares: number } → { tx_id: string; queued: boolean } */ +import { apiGet, ApiError } from "./client"; + export type VaultStrategy = "covered_call" | "cash_secured_put" | "iron_condor" | "wheel"; export type RiskRating = "low" | "medium" | "high"; export type EpochPhase = "deposit" | "active" | "settlement"; -/** Summary shown on the /vaults listing page. */ +/** + * Summary shown on the /vaults listing page. Fields the backend does not + * serve yet are `null` and must render as absent, never as zero. + */ export interface VaultSummary { id: string; name: string; underlying: string; strategy: VaultStrategy; /** Trailing 12-month APY estimate (0–1, e.g. 0.18 = 18%). */ - apy: number; + apy: number | null; /** APY methodology note shown in tooltip. */ apyMethodology: string; /** Total value locked in USD. */ - tvl: number; + tvl: number | null; /** Maximum TVL before the vault is full. */ - capacity: number; - currentStrike: number; - currentExpiry: string; // ISO date + capacity: number | null; + currentStrike: number | null; + currentExpiry: string | null; // ISO date riskRating: RiskRating; description: string; } @@ -66,10 +71,11 @@ export interface UserVaultPosition { /** Full detail for a single vault. */ export interface VaultDetail extends VaultSummary { - currentEpoch: number; - currentPhase: EpochPhase; - phaseEndsAt: string; // ISO — when the current phase ends - sharePrice: number; // current NAV per share in USD + /** Null until the first epoch has started. */ + currentEpoch: number | null; + currentPhase: EpochPhase | null; + phaseEndsAt: string | null; // ISO — when the current phase ends + sharePrice: number | null; // current NAV per share in USD; null or 0 = no NAV totalShares: number; feeStructure: { managementPct: number; performancePct: number }; payoffExplanation: string; @@ -77,164 +83,35 @@ export interface VaultDetail extends VaultSummary { userPosition: UserVaultPosition | null; } -// --------------------------------------------------------------------------- -// Mock data -// --------------------------------------------------------------------------- - -const MOCK_VAULTS: VaultDetail[] = [ - { - id: "xlm-cc-v1", - name: "XLM Covered Call", - underlying: "XLM", - strategy: "covered_call", - apy: 0.22, - apyMethodology: "Trailing 12-month annualized premium income ÷ average TVL. Does not include spot price change of the underlying.", - tvl: 284000, - capacity: 500000, - currentStrike: 0.13, - currentExpiry: "2026-10-03", - riskRating: "medium", - description: "Writes weekly out-of-the-money covered calls on XLM. Collects premium each epoch; the vault holds XLM as collateral.", - currentEpoch: 47, - currentPhase: "active", - phaseEndsAt: "2026-10-03T16:00:00Z", - sharePrice: 1.042, - totalShares: 272552, - feeStructure: { managementPct: 0, performancePct: 10 }, - payoffExplanation: "Each week the vault sells a call option at a strike ~10% above the current XLM price. If XLM stays below the strike, depositors keep the full premium. If XLM rallies above the strike, the position is exercised and depositors participate in the upside up to the strike, giving up gains beyond it.", - historicalEpochs: [ - { epochNumber: 46, depositStart: "2026-09-19", activeStart: "2026-09-20", settlementDate: "2026-09-27", strike: 0.125, expiryDays: 7, premiumCollected: 0.0018, outcome: "expired_worthless", pnlPct: 0.021 }, - { epochNumber: 45, depositStart: "2026-09-12", activeStart: "2026-09-13", settlementDate: "2026-09-20", strike: 0.122, expiryDays: 7, premiumCollected: 0.0015, outcome: "expired_worthless", pnlPct: 0.018 }, - { epochNumber: 44, depositStart: "2026-09-05", activeStart: "2026-09-06", settlementDate: "2026-09-13", strike: 0.118, expiryDays: 7, premiumCollected: 0.0021, outcome: "exercised", pnlPct: 0.009 }, - { epochNumber: 43, depositStart: "2026-08-29", activeStart: "2026-08-30", settlementDate: "2026-09-06", strike: 0.115, expiryDays: 7, premiumCollected: 0.0019, outcome: "expired_worthless", pnlPct: 0.022 }, - { epochNumber: 42, depositStart: "2026-08-22", activeStart: "2026-08-23", settlementDate: "2026-08-30", strike: 0.120, expiryDays: 7, premiumCollected: 0.0016, outcome: "expired_worthless", pnlPct: 0.019 }, - ], - userPosition: { shares: 950, shareValue: 1.042, totalValue: 989.9, accruedPremium: 18.46, pendingWithdrawal: 0 }, - }, - { - id: "btc-csp-v1", - name: "BTC Cash-Secured Put", - underlying: "BTC", - strategy: "cash_secured_put", - apy: 0.15, - apyMethodology: "Trailing 12-month annualized premium income ÷ average USDC TVL. Excludes BTC spot risk — depositors are exposed to assignment at the strike price.", - tvl: 1850000, - capacity: 3000000, - currentStrike: 60000, - currentExpiry: "2026-10-17", - riskRating: "high", - description: "Writes monthly cash-secured puts on BTC. The vault holds USDC as collateral; assignment means acquiring BTC at the strike.", - currentEpoch: 18, - currentPhase: "active", - phaseEndsAt: "2026-10-17T20:00:00Z", - sharePrice: 1.088, - totalShares: 1700368, - feeStructure: { managementPct: 0.5, performancePct: 15 }, - payoffExplanation: "Each month the vault sells a put option at a strike ~10% below the current BTC price, backed by USDC. If BTC stays above the strike, depositors keep the premium. If BTC falls below the strike, the vault is assigned and acquires BTC at the strike — a risk equivalent to a limit buy order.", - historicalEpochs: [ - { epochNumber: 17, depositStart: "2026-08-15", activeStart: "2026-08-19", settlementDate: "2026-09-20", strike: 59000, expiryDays: 30, premiumCollected: 420, outcome: "expired_worthless", pnlPct: 0.013 }, - { epochNumber: 16, depositStart: "2026-07-18", activeStart: "2026-07-22", settlementDate: "2026-08-16", strike: 55000, expiryDays: 30, premiumCollected: 380, outcome: "expired_worthless", pnlPct: 0.011 }, - { epochNumber: 15, depositStart: "2026-06-21", activeStart: "2026-06-24", settlementDate: "2026-07-19", strike: 57000, expiryDays: 30, premiumCollected: 440, outcome: "expired_worthless", pnlPct: 0.014 }, - ], - userPosition: null, - }, - { - id: "eth-ic-v1", - name: "ETH Iron Condor", - underlying: "ETH", - strategy: "iron_condor", - apy: 0.18, - apyMethodology: "Trailing 12-month net premium income (both spreads) ÷ average USDC collateral deployed.", - tvl: 620000, - capacity: 1000000, - currentStrike: 3200, - currentExpiry: "2026-10-10", - riskRating: "low", - description: "Sells 30-day iron condors on ETH, targeting a range-bound market. Limited loss profile; wins when ETH stays between the two breakevens.", - currentEpoch: 11, - currentPhase: "deposit", - phaseEndsAt: "2026-09-28T12:00:00Z", - sharePrice: 1.021, - totalShares: 607247, - feeStructure: { managementPct: 0, performancePct: 10 }, - payoffExplanation: "The vault simultaneously sells an out-of-the-money call spread and a put spread on ETH. Maximum profit is the combined net premium, achieved if ETH expires between the two short strikes. Maximum loss is limited to the spread width minus the premium.", - historicalEpochs: [ - { epochNumber: 10, depositStart: "2026-08-25", activeStart: "2026-08-29", settlementDate: "2026-09-28", strike: 3100, expiryDays: 30, premiumCollected: 28, outcome: "expired_worthless", pnlPct: 0.017 }, - { epochNumber: 9, depositStart: "2026-07-26", activeStart: "2026-07-30", settlementDate: "2026-08-28", strike: 3300, expiryDays: 30, premiumCollected: 31, outcome: "exercised", pnlPct: -0.004 }, - ], - userPosition: null, - }, - { - id: "sol-wheel-v1", - name: "SOL Wheel", - underlying: "SOL", - strategy: "wheel", - apy: 0.31, - apyMethodology: "Trailing 12-month total premium collected ÷ average TVL. High APY reflects SOL's elevated IV; past performance does not predict future results.", - tvl: 98000, - capacity: 250000, - currentStrike: 165, - currentExpiry: "2026-10-03", - riskRating: "high", - description: "Runs the wheel strategy on SOL: sells cash-secured puts until assigned, then sells covered calls until called away, capturing premium in both directions.", - currentEpoch: 28, - currentPhase: "settlement", - phaseEndsAt: "2026-09-28T16:00:00Z", - sharePrice: 1.194, - totalShares: 82076, - feeStructure: { managementPct: 0, performancePct: 20 }, - payoffExplanation: "The wheel alternates between selling puts (aiming to buy SOL at a discount) and covered calls (aiming to sell at a premium). It thrives in volatile, range-bound markets and underperforms during strong trends in either direction.", - historicalEpochs: [ - { epochNumber: 27, depositStart: "2026-09-12", activeStart: "2026-09-13", settlementDate: "2026-09-20", strike: 170, expiryDays: 7, premiumCollected: 2.8, outcome: "expired_worthless", pnlPct: 0.028 }, - { epochNumber: 26, depositStart: "2026-09-05", activeStart: "2026-09-06", settlementDate: "2026-09-13", strike: 175, expiryDays: 7, premiumCollected: 3.2, outcome: "exercised", pnlPct: 0.011 }, - ], - userPosition: null, - }, -]; - // --------------------------------------------------------------------------- // Public adapter // --------------------------------------------------------------------------- -export async function fetchVaults(): Promise { - if (process.env.NEXT_PUBLIC_VAULTS === "live") { - // TODO: return apiGet("/api/v1/vaults"); - throw new Error("Live vaults API not yet implemented"); - } - return Promise.resolve(MOCK_VAULTS.map(({ historicalEpochs, userPosition, ...summary }) => summary)); +export function fetchVaults(token?: string | null): Promise { + return apiGet("/api/v1/vaults", token ?? null); } -export async function fetchVaultDetail(id: string, _token?: string | null): Promise { - if (process.env.NEXT_PUBLIC_VAULTS === "live") { - // TODO: return apiGet(`/api/v1/vaults/${id}`, token); - throw new Error("Live vaults API not yet implemented"); +export async function fetchVaultDetail(id: string, token?: string | null): Promise { + try { + return await apiGet(`/api/v1/vaults/${encodeURIComponent(id)}`, token ?? null); + } catch (e) { + if (e instanceof ApiError && e.status === 404) return null; + throw e; } - return Promise.resolve(MOCK_VAULTS.find(v => v.id === id) ?? null); } export async function depositToVault( - id: string, - amount: number, + _id: string, + _amount: number, _token: string ): Promise<{ tx_id: string }> { - if (process.env.NEXT_PUBLIC_VAULTS === "live") { - // TODO: return apiPost<{tx_id:string}>(`/api/v1/vaults/${id}/deposit`, { amount }, token); - throw new Error("Live vaults API not yet implemented"); - } - // Mock: simulate a 600ms network round-trip - await new Promise(r => setTimeout(r, 600)); - return { tx_id: `mock-tx-deposit-${id}-${amount.toFixed(0)}-${Date.now()}` }; + throw new Error("Vault deposits are not yet available"); } export async function withdrawFromVault( - id: string, - shares: number, + _id: string, + _shares: number, _token: string ): Promise<{ tx_id: string; queued: boolean }> { - if (process.env.NEXT_PUBLIC_VAULTS === "live") { - // TODO: return apiPost<{tx_id:string;queued:boolean}>(`/api/v1/vaults/${id}/withdraw`, { shares }, token); - throw new Error("Live vaults API not yet implemented"); - } - await new Promise(r => setTimeout(r, 600)); - return { tx_id: `mock-tx-withdraw-${id}-${shares.toFixed(0)}-${Date.now()}`, queued: true }; + throw new Error("Vault withdrawals are not yet available"); }