// Wallet — HCR + HCC balances + transaction history. // Source: GET /api/v1/healthcoin/wallet + GET /api/v1/hcc/wallet // GET /api/v1/healthcoin/transactions?limit=200 const Wallet = () => { const [tab, setTab] = React.useState('all'); const [hcr, setHcr] = React.useState(undefined); // undefined=loading, null=missing, object=loaded const [hcc, setHcc] = React.useState(undefined); const [txns, setTxns] = React.useState(null); React.useEffect(() => { let dead = false; const get = async (path) => { try { const r = await fetch(window.CHVault.apiRoot + path, { credentials: 'include', headers: { 'Accept': 'application/json' } }); if (r.status === 404) return null; if (!r.ok) throw 0; return await r.json(); } catch { return null; } }; (async () => { const [a, b, hcrTx, hccTx] = await Promise.all([ get('/api/v1/healthcoin/wallet'), get('/api/v1/hcc/wallet'), get('/api/v1/healthcoin/transactions?limit=200'), get('/api/v1/hcc/transactions?limit=200'), ]); if (dead) return; setHcr(a); setHcc(b); // Normalize both streams into one list, tagged with coin. // HCR shape: { transactions: [{tx_hash, tx_type, amount, timestamp(float seconds), direction, reward_reason}], total } // HCC shape: { events: [{event_id, tier, final_pulses, final_hcc, era, created_at}], count } const hcrRows = hcrTx ? (Array.isArray(hcrTx) ? hcrTx : (hcrTx.transactions || hcrTx.items || [])) : []; const hccRows = hccTx ? (Array.isArray(hccTx) ? hccTx : (hccTx.events || hccTx.transactions || hccTx.items || [])) : []; const epochToISO = (sec) => { if (sec == null) return null; const n = Number(sec); if (!Number.isFinite(n)) return null; // Heuristic: if it's < 1e12 it's seconds, else already ms. return new Date(n < 1e12 ? n * 1000 : n).toISOString(); }; const merged = [].sort((x, y) => { const tx = new Date(x.recorded_at || 0).getTime(); const ty = new Date(y.recorded_at || 0).getTime(); return ty - tx; }); setTxns(merged); })(); return () => { dead = true; }; }, []); // HCR (/api/v1/healthcoin/wallet) returns { balance, balance_usd, total_earned, … } // HCC (/api/v1/hcc/wallet) returns { balance_hcc, balance_pulses, total_mined_hcc, … } const toNum = (x) => { if (x == null) return null; const n = Number(x); return Number.isFinite(n) ? n : null; }; const hcrBal = hcr && toNum(hcr.balance ?? hcr.hcr_balance ?? hcr.amount); const hcrUsd = hcr && toNum(hcr.balance_usd ?? hcr.usd_value); const hcrEarned = hcr && toNum(hcr.total_earned); const hccBal = hcc && toNum(hcc.balance_hcc ?? hcc.balance ?? hcc.hcc_balance ?? hcc.amount); const hccPulses = hcc && toNum(hcc.balance_pulses); const hccMined = hcc && toNum(hcc.total_mined_hcc ?? hcc.total_mined); const hccUsd = hcc && toNum(hcc.usd_value ?? hcc.amount_usd ?? hcc.balance_usd); const filtered = (txns || []).filter(t => { if (tab === 'all') return true; return (t.coin || t.currency || 'HCR').toLowerCase() === tab; }); const kindColor = (k) => k === 'mint' ? 'var(--accent-clinical)' : k === 'hcc' ? 'var(--accent-plasma)' : k === 'redeem' ? 'var(--bracket-poor)' : 'var(--accent-coral)'; const kindLabel = (k) => ({ mint: 'Mint', hcc: 'HCC', redeem: 'Redeem', reverse: 'Reverse' })[k] || k; return (
Wallet

Your reserve and your data economy.

HCR is what you've earned through axis-lift. HCC is what researchers paid you to access opt-in records. Two coins. One wallet.
window.openQuickLog && window.openQuickLog(null) }} /> 0 ? `${Number(hccMined).toLocaleString(undefined, { maximumFractionDigits: 6 })} HCC lifetime mined` : null} actions={[ { label: 'Cash out', primary: true, href: 'https://hc.exchange/redeem' }, { label: 'Trade on hc.exchange', href: 'https://hc.exchange' }, { label: 'Mining stats', href: 'https://hc.exchange/mining' }, ]} emptyTitle="No HCC yet." emptyDetail="HCC is paid out when you opt your records into a researcher cohort and the query is accepted. Browse offers under Research." ctaPrimary={{ label: 'Browse research offers', href: 'https://hc.exchange/research' }} />
Activity · last 200

Transaction history

{['all', 'hcr', 'hcc'].map(t => ( ))}
{txns === null && (
Loading transactions…
)} {txns && filtered.length === 0 && txns.length === 0 && (
No transactions yet.
The first mint will appear here the moment your first signal event is accepted.
)} {txns && txns.length > 0 && filtered.length === 0 && (
No {tab.toUpperCase()} transactions in the last 200. Try the "All" or "{tab === 'hcr' ? 'HCC' : 'HCR'}" tab.
)} {filtered.map((t, i) => { const coin = (t.coin || t.currency || 'HCR').toUpperCase(); const kind = t.kind || t.type || (Number(t.amount || 0) < 0 ? 'redeem' : 'mint'); const amtNum = Number(t.amount ?? t.value ?? 0); const when = t.recorded_at || t.created_at || (t.timestamp ? new Date((Number(t.timestamp) < 1e12 ? Number(t.timestamp) * 1000 : Number(t.timestamp))).toISOString() : null); const precision = coin === 'HCC' ? 6 : 2; return (
{kindLabel(kind)}
{t.description || t.detail || (t.signal_id ? 'Signal · ' + t.signal_id : 'Transaction')}
{when ? new Date(when).toLocaleString() : ''}{t.source ? ' · ' + t.source : ''}
{coin}
{amtNum > 0 ? '+' : ''}{amtNum.toLocaleString(undefined, { maximumFractionDigits: precision })}
); })}
); }; const BalanceCard = ({ tint, eyebrow, subtitle, value, unitLabel, secondary, actions, loading, emptyTitle, emptyDetail, ctaPrimary }) => { const hasBalance = !loading && value != null && !Number.isNaN(Number(value)); const renderAction = (a, i) => { const style = { textDecoration: 'none', padding: '10px 16px', borderRadius: 8, background: a.primary ? tint : 'transparent', color: a.primary ? 'white' : 'var(--fg-1)', border: a.primary ? 'none' : '1px solid var(--border-1)', fontWeight: 600, fontSize: 13, cursor: 'pointer' }; if (a.href) return {a.label}; if (a.onClick) return ; return ; }; return (
{eyebrow}
{subtitle}
{loading ? (
Loading wallet…
) : hasBalance ? (
{Number(value).toLocaleString(undefined, { maximumFractionDigits: 6 })}
{unitLabel}
{secondary &&
{secondary}
}
) : (
{emptyTitle}
{emptyDetail}
)}
{hasBalance && (actions || []).map(renderAction)} {!hasBalance && ctaPrimary && renderAction({ ...ctaPrimary, primary: true }, 'cta')}
); }; window.Wallet = Wallet;