/** * Per-account USD balance (from verified Bitcoin deposits), LUX loyalty points, * and claimed txids — all keyed by CyberLux handle (same origin = one account). */ export type AccountLedgerRow = { usd: number; lux: number; claimedTxids: string[]; }; const LEDGER_KEY = "cyberlux-account-ledger-v1"; /** Pre–per-account migration keys (device-wide). */ const LEGACY_USD_KEY = "cyberlux-usd-store-credit"; const LEGACY_TX_KEY = "cyberlux-btc-claimed-txids"; function emptyRow(): AccountLedgerRow { return { usd: 0, lux: 0, claimedTxids: [] }; } function loadLedgerMap(): Record { if (typeof window === "undefined") return {}; try { const raw = localStorage.getItem(LEDGER_KEY); if (!raw) return {}; const o = JSON.parse(raw) as Record; if (!o || typeof o !== "object") return {}; const out: Record = {}; for (const [k, v] of Object.entries(o)) { if (!k || typeof v !== "object" || v === null) continue; const row = v as Record; const usd = typeof row.usd === "number" && Number.isFinite(row.usd) ? Math.max(0, Math.round(row.usd * 100) / 100) : 0; const lux = typeof row.lux === "number" && Number.isFinite(row.lux) ? Math.max(0, Math.floor(row.lux)) : 0; const claimedTxids = Array.isArray(row.claimedTxids) ? row.claimedTxids.map((t) => String(t)).filter((t) => /^[a-fA-F0-9]{64}$/.test(t)) : []; out[k.toLowerCase()] = { usd, lux, claimedTxids: [...new Set(claimedTxids)] }; } return out; } catch { return {}; } } function saveLedgerMap(m: Record): void { if (typeof window === "undefined") return; localStorage.setItem(LEDGER_KEY, JSON.stringify(m)); } function accountKey(username: string): string { return username.trim().toLowerCase(); } export function getLedgerRow(username: string | null): AccountLedgerRow { if (!username) return emptyRow(); const map = loadLedgerMap(); return map[accountKey(username)] ?? emptyRow(); } export function setLedgerRow(username: string, row: AccountLedgerRow): void { const map = loadLedgerMap(); map[accountKey(username)] = { usd: Math.max(0, Math.round(row.usd * 100) / 100), lux: Math.max(0, Math.floor(row.lux)), claimedTxids: [...new Set(row.claimedTxids.filter((t) => /^[a-fA-F0-9]{64}$/.test(t)))], }; saveLedgerMap(map); } /** * One-time: move old device-wide USD + txids into this handle’s ledger, then clear legacy keys. */ export function migrateLegacyDeviceLedgerIfNeeded(username: string): void { if (typeof window === "undefined" || !username.trim()) return; const key = accountKey(username); const map = loadLedgerMap(); const existing = map[key] ?? emptyRow(); if (existing.usd > 0 || existing.claimedTxids.length > 0) return; let legacyUsd = 0; try { const raw = localStorage.getItem(LEGACY_USD_KEY); if (raw) { const n = Number.parseFloat(raw); if (Number.isFinite(n)) legacyUsd = Math.max(0, Math.round(n * 100) / 100); } } catch { /* ignore */ } let legacyTx: string[] = []; try { const raw = localStorage.getItem(LEGACY_TX_KEY); if (raw) { const parsed = JSON.parse(raw) as unknown; if (Array.isArray(parsed)) { legacyTx = parsed.map((t) => String(t)).filter((t) => /^[a-fA-F0-9]{64}$/.test(t)); } } } catch { /* ignore */ } if (legacyUsd <= 0 && legacyTx.length === 0) return; map[key] = { usd: legacyUsd, lux: 0, claimedTxids: [...new Set(legacyTx)], }; saveLedgerMap(map); localStorage.removeItem(LEGACY_USD_KEY); localStorage.removeItem(LEGACY_TX_KEY); }