// Generated, do not edit. sha256:c7cd149bf7a3cd10c21ce3f8a14833aa6c606b7953811447bfbf09af17e22088 import { mkdirSync } from 'node:fs'; import { join } from 'node:path'; import { CONFIG } from '../shared/config.js'; import { priceFromTotal } from '../shared/units.js'; import { toSearchName } from '../shared/search-name.js'; import { initDatabase, closeDatabase, one } from './db.js'; import { portfolioDir, listPortfolios } from './portfolios.mjs'; import { hasFeature } from './license.mjs'; import { completeSetup } from './data/onboarding.js'; import { createEvent } from './data/events.js'; import { createValuation } from './data/valuations.js'; export const DEMO_MONTHS = 30; const BTC_EUR = { '2023-01': 21000, '2023-04': 27000, '2023-07': 26500, '2023-10': 32000, '2024-01': 39000, '2024-02': 48000, '2024-03': 63000, '2024-04': 59000, '2024-05': 62000, '2024-06': 58000, '2024-07': 59000, '2024-08': 54000, '2024-09': 57000, '2024-10': 63000, '2024-11': 88000, '2024-12': 92000, '2025-01': 97000, '2025-02': 84000, '2025-03': 78000, '2025-04': 82000, '2025-05': 92000, '2025-06': 91000, '2025-07': 100000, '2025-08': 97000, '2025-09': 96000, '2025-10': 98000, '2025-11': 82000, '2025-12': 76000, '2026-01': 79000, '2026-02': 74000, '2026-03': 72000, '2026-04': 78000, '2026-05': 84000, '2026-06': 88000, '2026-07': 92000, '2026-08': 90000, '2026-09': 94000, '2026-10': 96000, }; const MONTHS = Object.keys(BTC_EUR).sort(); const SAT = 1e8; const CENT = 100; const ACCOUNTS = Object.freeze({ exchange: { name: 'Exchange', kind: 'exchange' }, wallet: { name: 'Hardware wallet', kind: 'wallet' }, bank: { name: 'Bank', kind: 'bank' }, }); const iso = (y, m, day) => new Date(Date.UTC(y, m, day, 10, 0, 0)).toISOString(); function priceOn(at) { const key = at.slice(0, 7); const idx = MONTHS.findIndex(k => k >= key); const here = BTC_EUR[MONTHS[idx < 0 ? MONTHS.length - 1 : idx]]; const next = BTC_EUR[MONTHS[Math.min((idx < 0 ? MONTHS.length - 1 : idx) + 1, MONTHS.length - 1)]]; const frac = (Number(at.slice(8, 10)) - 1) / 30; return Math.round(here + (next - here) * frac); } export function demoRows(today = new Date()) { const rows = []; const pos = { exchange: 0, wallet: 0, bank: 0 }; const nu = today.toISOString(); const start = new Date(Date.UTC(today.getUTCFullYear(), today.getUTCMonth() - (DEMO_MONTHS - 1), 1)); const add = (r) => { if (r.at <= nu) rows.push(r); return r.at <= nu; }; for (let i = 0; i < DEMO_MONTHS; i++) { const y = start.getUTCFullYear(), m = start.getUTCMonth() + i; if (i === 0 && add({ kind: 'event', type: 'buy', asset: 'EUR', to: 'bank', at: iso(y, m, 1), amount: 4000 * CENT, fiatPrice: 1 })) pos.bank += 4000 * CENT; if (add({ kind: 'event', type: 'buy', asset: 'EUR', to: 'bank', at: iso(y, m, 1), amount: 900 * CENT, fiatPrice: 1 })) pos.bank += 900 * CENT; for (const day of [2, 9, 16, 23]) { const at = iso(y, m, day); const price = priceOn(at); const fee = 0.4; const amount = Math.round((75 - fee) / price * SAT); if (add({ kind: 'event', type: 'buy', asset: 'BTC', to: 'exchange', at, amount, fiatPrice: priceFromTotal(75, amount, 8, fee, 'buy'), fiatFee: fee })) { pos.exchange += amount; } } if (i % 4 === 3) { const amount = pos.exchange - 100_000; if (amount > 0 && add({ kind: 'event', type: 'transfer', asset: 'BTC', from: 'exchange', to: 'wallet', at: iso(y, m, 26), amount })) { pos.exchange -= amount; pos.wallet += amount; } } if (i === 20) { const amount = 2_000_000; if (add({ kind: 'event', type: 'transfer', asset: 'BTC', from: 'wallet', to: 'exchange', at: iso(y, m, 10), amount })) { pos.wallet -= amount; pos.exchange += amount; const at = iso(y, m, 11); if (add({ kind: 'event', type: 'sell', asset: 'BTC', from: 'exchange', at, amount, fiatPrice: priceOn(at), fiatFee: 2 })) pos.exchange -= amount; } } if (m % 3 === 2) { const amount = Math.round(pos.bank * 0.015 / 4); if (amount > 0 && add({ kind: 'event', type: 'interest', asset: 'EUR', to: 'bank', at: iso(y, m, 28), amount, fiatPrice: 1 })) pos.bank += amount; } const peil = (account, asset) => { const at = new Date(Date.UTC(y, m, 28, 22, 0, 0)).toISOString(); add({ kind: 'valuation', asset, account, at, valuedOn: at.slice(0, 10), balance: pos[account], fiatRate: asset === 'EUR' ? 1 : priceOn(at) }); }; if (m % 6 === 5) { peil('bank', 'EUR'); if (pos.wallet > 0) peil('wallet', 'BTC'); } if (m % 12 === 11 && pos.exchange > 0) peil('exchange', 'BTC'); } return rows; } export async function seedDemo(today = new Date()) { const accounts = Object.values(ACCOUNTS); await completeSetup({ assets: ['BTC'], persona: 'broad', accounts, widgets: ['standing', 'spot'] }); const assetId = (symbol) => one(`SELECT id FROM Asset WHERE symbol = ?`, [symbol]).id; const accountId = (key) => one(`SELECT id FROM Account WHERE searchName = ?`, [toSearchName(ACCOUNTS[key].name)]).id; const ids = { BTC: assetId('BTC'), EUR: assetId('EUR') }; for (const r of demoRows(today)) { if (r.kind === 'event') { await createEvent({ assetId: ids[r.asset], type: r.type, occurredAt: r.at, amount: r.amount, fiatPrice: r.fiatPrice ?? null, fiatFee: r.fiatFee ?? null, fromAccountId: r.from ? accountId(r.from) : null, toAccountId: r.to ? accountId(r.to) : null, source: 'manual', }); } else { await createValuation({ assetId: ids[r.asset], accountId: accountId(r.account), balance: r.balance, fiatRate: r.fiatRate, valuedOn: r.valuedOn, rateAt: r.at, source: 'manual' }); } } } export async function createDemoPortfolio(today = new Date()) { const map = portfolioDir(CONFIG.PORTFOLIO.DEMO_NAME); mkdirSync(map, { recursive: true }); initDatabase(join(map, 'db.sqlite')); try { await seedDemo(today); } finally { closeDatabase(); } } export async function ensureDemo(layout) { if (!layout.fresh || hasFeature('portfolios') || listPortfolios().some(p => p.demo)) return false; await createDemoPortfolio(); return true; }