const state = { provider: 'giveth', projects: [], selected: null, intent: null }; const els = { status: document.querySelector('#session-status'), accessTitle: document.querySelector('#access-title'), accessDetail: document.querySelector('#verification-status') || document.querySelector('#access-detail'), grid: document.querySelector('#project-grid'), refresh: document.querySelector('#refresh-projects'), proofHelp: document.querySelector('#proof-help'), proofForm: document.querySelector('#proof-form'), txHash: document.querySelector('#tx-hash'), verifyBtn: document.querySelector('#verify-btn'), menuToggle: document.querySelector('#menu-toggle'), siteNav: document.querySelector('#site-nav'), minDonationLabel: document.querySelector('#min-donation-label'), preview: document.querySelector('#preview-unlock'), ledgerRefresh: document.querySelector('#refresh-ledger'), ledgerTotalUsd: document.querySelector('#ledger-total-usd'), ledgerDonationCount: document.querySelector('#ledger-donation-count'), ledgerProjectCount: document.querySelector('#ledger-project-count'), ledgerIntegrationCount: document.querySelector('#ledger-integration-count'), ledgerList: document.querySelector('#ledger-list') }; function setStatus(text, type) { if (!els.status) return; els.status.textContent = text; els.status.classList.toggle('is-open', type === 'open'); els.status.classList.toggle('is-closed', type === 'closed'); } function setAccess(title, detail) { if (!els.accessTitle || !els.accessDetail) return; els.accessTitle.textContent = title; els.accessDetail.textContent = detail; } function celebrateMercy(message = 'Yay. Mercy verified and added to the public ledger.') { const colors = ['#ec4899', '#3b82f6', '#10b981', '#f59e0b']; setAccess('Mercy verified', message); if (els.accessDetail) els.accessDetail.style.color = 'var(--accent-fresh)'; if (els.verifyBtn) { els.verifyBtn.classList.remove('animate-bounce'); void els.verifyBtn.offsetWidth; els.verifyBtn.classList.add('animate-bounce'); } const origin = els.verifyBtn?.getBoundingClientRect(); const baseX = origin ? origin.left + origin.width / 2 : window.innerWidth / 2; const baseY = origin ? origin.top + origin.height / 2 : window.innerHeight / 2; for (let index = 0; index < 28; index += 1) { const particle = document.createElement('span'); particle.className = 'mercy-particle'; particle.style.setProperty('--particle-color', colors[index % colors.length]); particle.style.setProperty('--x', `${baseX + (Math.random() - 0.5) * 120}px`); particle.style.setProperty('--y', `${baseY + (Math.random() - 0.5) * 32}px`); particle.style.setProperty('--drift', `${(Math.random() - 0.5) * 190}px`); document.body.appendChild(particle); window.setTimeout(() => particle.remove(), 1500); } } function setupMobileMenu() { if (!els.menuToggle || !els.siteNav) return; const setOpen = (isOpen) => { els.menuToggle.setAttribute('aria-expanded', String(isOpen)); els.menuToggle.setAttribute('aria-label', isOpen ? 'Close menu' : 'Open menu'); els.siteNav.classList.toggle('is-open', isOpen); }; els.menuToggle.addEventListener('click', () => { setOpen(els.menuToggle.getAttribute('aria-expanded') !== 'true'); }); els.siteNav.addEventListener('click', (event) => { if (event.target.closest('a')) setOpen(false); }); } function updateThreshold(minAmount) { if (!els.minDonationLabel) return; els.minDonationLabel.textContent = minAmount ? `Selected path: ${minAmount}. Donations still move directly to the charity; SoulWall verifies receipts and adds them to the shared impact ledger.` : 'From 1c gestures to $1,000,000 campaigns, every gated action can become a direct-to-charity contribution. The only limit to the good you can do is your imagination.'; } function escapeHtml(value) { return String(value ?? '') .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll('\'', '''); } async function fetchJson(url, options) { const response = await fetch(url, options); const payload = await response.json().catch(() => ({})); if (!response.ok && response.status !== 202) { const message = payload.message || payload.error || `Request failed: ${response.status}`; throw new Error(message); } return payload; } function normalizeProject(project) { return { provider: project.provider || state.provider || 'giveth', id: project.slug || project.id || project.nonprofitSlug || project.name, slug: project.slug || project.id || project.nonprofitSlug || '', name: project.name || project.slug || 'Verified project', description: project.description || 'Official provider project. Donate there, then return with proof.', url: project.donateUrl || project.url || project.website || project.websiteUrl || '', logoUrl: project.logoUrl || project.logo_url || '' }; } function charityCardClass(index) { return ['charity-card-water', 'charity-card-health', 'charity-card-cash'][index % 3]; } function renderProjects(projects) { if (!els.grid) return; if (!projects.length) { els.grid.innerHTML = '

No charity projects are configured yet.

'; return; } els.grid.innerHTML = ''; projects.map(normalizeProject).forEach((project, index) => { const card = document.createElement('article'); card.className = `charity-provider-card ${charityCardClass(index)}`; card.dataset.projectId = project.id; card.tabIndex = 0; card.setAttribute('role', 'button'); card.setAttribute('aria-label', `Select ${project.name}`); card.innerHTML = ` ${escapeHtml(project.provider.toUpperCase())} VERIFIED PROJECT

${escapeHtml(project.description)}

${escapeHtml(project.name)} Official provider checkout. Donation value routes to the nonprofit, not SoulWall. Polygon proof path Select `; card.addEventListener('click', () => selectProject(project, card)); card.addEventListener('keydown', (event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); selectProject(project, card); } }); els.grid.appendChild(card); }); } function findProjectBySlug(slug) { const normalized = String(slug || '').trim(); if (!normalized) return null; return state.projects.find((project) => project.slug === normalized || project.id === normalized) || state.projects.find((project) => project.slug.toLowerCase() === normalized.toLowerCase() || project.id.toLowerCase() === normalized.toLowerCase()) || null; } function markSelectedProject(project) { state.selected = project; document.querySelectorAll('.charity-provider-card').forEach((item) => { item.classList.toggle('is-selected', item.dataset.projectId === project.id); }); } function formatUsd(value) { const numeric = Number(value); if (!Number.isFinite(numeric)) return '$0.00'; return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: numeric >= 1000 ? 0 : 2 }).format(numeric); } function formatNativeAmount(value, currency) { const numeric = Number(value); const code = String(currency || '').trim().toUpperCase(); if (!Number.isFinite(numeric) || numeric <= 0 || !code || code === 'USD') return ''; return `${numeric.toLocaleString(undefined, { maximumFractionDigits: numeric >= 10 ? 2 : 4 })} ${code}`; } function formatLedgerAmount(row) { const usd = Number(row.amount_usd); const native = formatNativeAmount(row.amount_native, row.currency); const hasUsd = Number.isFinite(usd) && usd > 0; if (hasUsd && native) return `${formatUsd(usd)} / ${native}`; if (hasUsd) return formatUsd(usd); return native || formatUsd(0); } function formatLedgerTotal(totals) { const usd = formatUsd(totals.amountUsd); const nativeParts = Object.entries(totals.nativeByCurrency || {}) .map(([currency, amount]) => formatNativeAmount(amount, currency)) .filter(Boolean); if (!nativeParts.length) return usd; return `${usd} / ${nativeParts.join(' + ')}`; } function renderLedger(payload) { const totals = payload?.totals || {}; els.ledgerTotalUsd.textContent = formatLedgerTotal(totals); els.ledgerDonationCount.textContent = Number(totals.donations || 0).toLocaleString(); els.ledgerProjectCount.textContent = Number(totals.projects || 0).toLocaleString(); els.ledgerIntegrationCount.textContent = Number(totals.integrations || 0).toLocaleString(); const recent = Array.isArray(payload?.recent) ? payload.recent : []; if (!recent.length) { els.ledgerList.innerHTML = `

${payload?.configured === false ? 'Ledger storage is not configured yet.' : 'No confirmed donation pings have reached the public archive yet.'}

`; return; } els.ledgerList.innerHTML = recent.map((row) => { const project = row.project_name || row.project_slug || 'Verified project'; const provider = row.provider || 'provider'; const origin = row.integration_origin || row.client_id || 'SoulWall'; const stamp = row.confirmed_at ? new Date(row.confirmed_at).toLocaleString() : 'confirmed'; return `
${escapeHtml(project)} ${escapeHtml(provider)} / ${escapeHtml(origin)} / ${escapeHtml(stamp)}
${escapeHtml(formatLedgerAmount(row))}
`; }).join(''); } async function loadLedger() { if (!els.ledgerList) return; els.ledgerList.innerHTML = '

Loading public archive.

'; try { const payload = await fetchJson('/api/soulwall/ledger'); renderLedger(payload); } catch (error) { els.ledgerList.innerHTML = `

${escapeHtml(error.message)}

`; } } async function pingLedger(payload) { try { await fetchJson('/api/soulwall/telemetry', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload) }); await loadLedger(); } catch (error) { console.warn('[SoulWall] Ledger ping failed:', error); } } async function loadSession() { try { const session = await fetchJson('/api/soulwall/session'); if (session.access) { setStatus('Protocol online', 'open'); setAccess('Demo ready', 'Paste a Polygon receipt to inspect proof status and update the public ledger.'); return; } setStatus('Protocol online', 'open'); setAccess('Demo ready', 'Paste a Polygon receipt and watch the ledger light up.'); } catch (error) { setStatus('Protocol API check', 'closed'); setAccess('Verification API unavailable', error.message); } } async function loadProjects() { if (!els.grid) return; els.grid.innerHTML = '

Loading charity projects.

'; try { const payload = await fetchJson('/api/soulwall/charities'); state.provider = payload.provider || 'giveth'; state.projects = (payload.charities || []).map(normalizeProject); renderProjects(state.projects); } catch (error) { els.grid.innerHTML = `

${escapeHtml(error.message)}

`; } } async function selectProject(project, card) { markSelectedProject(project); setAccess('Project selected', project.name); if (els.proofHelp) els.proofHelp.textContent = 'Donate on the official provider page, then paste the Polygon transaction hash.'; updateThreshold(project.currency ? `${project.currency} proof path` : 'charity proof path'); try { state.intent = await fetchJson('/api/soulwall/intent', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ provider: project.provider, nonprofitSlug: project.slug || project.id }) }); } catch (error) { state.intent = null; } if (project.url) window.open(project.url, '_blank', 'noopener,noreferrer'); } async function verifyProof(event) { event.preventDefault(); const txHash = els.txHash.value.trim(); if (!txHash) { setAccess('Transaction hash required', 'Paste the Polygon transaction hash from the provider page.'); return; } setAccess('Checking receipt', state.selected ? 'Looking for your donation on the selected charity path.' : 'Scanning configured charity cards for a matching proof.'); try { const payload = await fetchJson('/api/soulwall/giveth-verify', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ txHash, projectSlug: state.selected?.slug || state.selected?.id || '', partnerDonationId: state.intent?.partnerDonationId || '' }) }); if (payload.unlocked) { const verifiedProject = findProjectBySlug(payload.projectSlug) || state.selected || normalizeProject({ provider: payload.provider, slug: payload.projectSlug, name: payload.projectName || payload.projectSlug || 'Verified project' }); if (verifiedProject?.id) markSelectedProject(verifiedProject); setStatus('Protocol online', 'open'); await loadLedger(); celebrateMercy(`${verifiedProject.name} accepted and recorded in the public ledger.`); return; } if (payload.proofRecognized || payload.duplicate) { setStatus('Protocol online', 'open'); await loadLedger(); celebrateMercy(payload.message || 'This transaction has already been recorded by SoulWall.'); return; } setAccess('Receipt not found yet', payload.message || 'Give the provider a moment, then try again.'); } catch (error) { setAccess('Demo check needs a minute', error.message); } } async function previewUnlock() { setAccess('Preview unlock requested', 'Checking local preview settings.'); try { await fetchJson('/api/soulwall/unlock-preview', { method: 'POST' }); await loadSession(); } catch (error) { setAccess('Preview unavailable', error.message); } } if (els.refresh) els.refresh.addEventListener('click', loadProjects); if (els.proofForm) els.proofForm.addEventListener('submit', verifyProof); if (els.preview) els.preview.addEventListener('click', previewUnlock); if (els.ledgerRefresh) els.ledgerRefresh.addEventListener('click', loadLedger); updateThreshold(''); setupMobileMenu(); loadSession(); loadProjects(); loadLedger();